From 1744e123b4e80eac6a5d57196ed5c807f3e2048f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 31 Aug 2023 13:22:30 -0400 Subject: [PATCH] initial commit; --- .gitattributes | 1 + .gitignore | 66 ++ AUTHORS.md | 14 + Constants.cs | 210 ++++ DMR/DMRDefines.cs | 84 ++ DMR/EMB.cs | 114 ++ DMR/EmbeddedData.cs | 380 +++++++ DMR/FullLC.cs | 192 ++++ DMR/LC.cs | 245 +++++ DMR/PrivacyLC.cs | 144 +++ DMR/SlotType.cs | 108 ++ EDAC/BPTC19696.cs | 409 ++++++++ EDAC/CRC.cs | 523 ++++++++++ EDAC/Golay2087.cs | 295 ++++++ EDAC/Hamming.cs | 444 ++++++++ EDAC/QR1676.cs | 149 +++ EDAC/RS129.cs | 166 +++ FneBase.cs | 915 ++++++++++++++++ FneMaster.cs | 1166 +++++++++++++++++++++ FnePeer.cs | 711 +++++++++++++ FneUtils.cs | 775 ++++++++++++++ LICENSE | 661 ++++++++++++ NXDN/NXDNDefines.cs | 61 ++ P25/P25Defines.cs | 211 ++++ Properties/AssemblyInfo.cs | 49 + README.md | 8 + RtpExtensionHeader.cs | 89 ++ RtpFNEHeader.cs | 128 +++ RtpHeader.cs | 165 +++ Udp.cs | 220 ++++ Utility/CommandOptions.cs | 2027 ++++++++++++++++++++++++++++++++++++ Utility/SemVersion.cs | 688 ++++++++++++ fnecore.csproj | 10 + 33 files changed, 11428 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 AUTHORS.md create mode 100644 Constants.cs create mode 100644 DMR/DMRDefines.cs create mode 100644 DMR/EMB.cs create mode 100644 DMR/EmbeddedData.cs create mode 100644 DMR/FullLC.cs create mode 100644 DMR/LC.cs create mode 100644 DMR/PrivacyLC.cs create mode 100644 DMR/SlotType.cs create mode 100644 EDAC/BPTC19696.cs create mode 100644 EDAC/CRC.cs create mode 100644 EDAC/Golay2087.cs create mode 100644 EDAC/Hamming.cs create mode 100644 EDAC/QR1676.cs create mode 100644 EDAC/RS129.cs create mode 100644 FneBase.cs create mode 100644 FneMaster.cs create mode 100644 FnePeer.cs create mode 100644 FneUtils.cs create mode 100644 LICENSE create mode 100644 NXDN/NXDNDefines.cs create mode 100644 P25/P25Defines.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 README.md create mode 100644 RtpExtensionHeader.cs create mode 100644 RtpFNEHeader.cs create mode 100644 RtpHeader.cs create mode 100644 Udp.cs create mode 100644 Utility/CommandOptions.cs create mode 100644 Utility/SemVersion.cs create mode 100644 fnecore.csproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ebcd7c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +dvmhost + +# 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/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..cee43c8 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,14 @@ +# Digital Voice Modem FNE + +- Steven Jennison (https://github.com/sjennison) +- Natalie Moore (https://github.com/jelimoore) +- Bryan Biedenkapp (https://github.com/gatekeep) + +- Documentation Team + - Charles Bricker (https://github.com/ceb515) + - Connor Lovell (https://github.com/DevRanger) + +- Community Support Team + - Steven Jennison (https://github.com/sjennison) + - Charles Bricker (https://github.com/ceb515) + diff --git a/Constants.cs b/Constants.cs new file mode 100644 index 0000000..98e8d31 --- /dev/null +++ b/Constants.cs @@ -0,0 +1,210 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore +{ + /// + /// Used internally to identify the logging level. + /// + public enum LogLevel : byte + { + /// + /// Informational + /// + INFO = 0x00, + /// + /// Warning + /// + WARNING = 0x01, + /// + /// Error + /// + ERROR = 0x02, + /// + /// Debug + /// + DEBUG = 0x04, + /// + /// Fatal + /// + FATAL = 0x08 + } // public enum LogLevel : byte + + /// + /// Peer Connection State + /// + public enum ConnectionState + { + /// + /// Waiting on Login - Received the repeater login request + /// + WAITING_LOGIN, + /// + /// Waiting on Authorization - Sent the connection challenge to peer + /// + WAITING_AUTHORISATION, + /// + /// Waiting on Configuration + /// + WAITING_CONFIG, + /// + /// Running + /// + RUNNING, + } // public enum ConnectionState + + /// + /// + /// + public enum CallType : byte + { + /// + /// Group Call + /// + GROUP = 0x00, + /// + /// Private Call + /// + PRIVATE = 0x01, + } // public enum CallType : byte + + /// + /// + /// + public enum FrameType : byte + { + /// + /// + /// + VOICE = 0x00, + /// + /// + /// + VOICE_SYNC = 0x01, + /// + /// + /// + DATA_SYNC = 0x02, + + /// + /// + /// + TERMINATOR = 0xFF + } // public enum FrameType : byte + + /// + /// + /// + public enum DVMState : byte + { + /// + /// Idle + /// + IDLE = 0, + /// + /// DMR + /// + DMR = 1, + /// + /// P25 + /// + P25 = 2, + /// + /// NXDN + /// + NXDN = 3 + } // public enum DVMState : byte + + /// + /// This class defines commonly used protocol and internal constants. + /// + public sealed class Constants + { + public const uint InvalidTS = uint.MaxValue; + + public const uint RtpHeaderLengthBytes = 12; + public const uint RtpExtensionHeaderLengthBytes = 4; + public const uint RtpFNEHeaderLengthBytes = 16; + public const ushort RtpFNEHeaderExtLength = 4; // length of FNE header in 32-bit units + public const uint RtpGenericClockRate = 8000; + + public const byte DVMRtpPayloadType = 0x56; + public const byte DVMRtpControlPayloadType = 0x57; + public const byte DVMFrameStart = 0xFE; + + /* + ** Protocol Functions and Sub-Functions + */ + public const byte NET_SUBFUNC_NOP = 0xFF; // No Operation Sub-Function + + public const byte NET_FUNC_PROTOCOL = 0x00; // Network Protocol Function + public const byte NET_PROTOCOL_SUBFUNC_DMR = 0x00; // DMR + public const byte NET_PROTOCOL_SUBFUNC_P25 = 0x01; // P25 + public const byte NET_PROTOCOL_SUBFUNC_NXDN = 0x02; // NXDN + + public const byte NET_FUNC_MASTER = 0x01; // Network Master Function + public const byte NET_MASTER_SUBFUNC_WL_RID = 0x00; // Whitelist RIDs + public const byte NET_MASTER_SUBFUNC_BL_RID = 0x01; // Blacklist RIDs + public const byte NET_MASTER_SUBFUNC_ACTIVE_TGS = 0x02; // Active TGIDs + public const byte NET_MASTER_SUBFUNC_DEACTIVE_TGS = 0x03; // Deactive TGIDs + + public const byte NET_FUNC_RPTL = 0x60; // Repeater Login + public const byte NET_FUNC_RPTK = 0x61; // Repeater Authorisation + public const byte NET_FUNC_RPTC = 0x62; // Repeater Configuration + + public const byte NET_FUNC_RPT_CLOSING = 0x70; // Repeater Closing + public const byte NET_FUNC_MST_CLOSING = 0x71; // Master Closing + + public const byte NET_FUNC_PING = 0x74; // Ping + public const byte NET_FUNC_PONG = 0x75; // Pong + + public const byte NET_FUNC_GRANT = 0x7A; // Grant Request + + public const byte NET_FUNC_ACK = 0x7E; // Packet Acknowledge + public const byte NET_FUNC_NAK = 0x7F; // Packet Negative Acknowledge + + public const byte NET_FUNC_TRANSFER = 0x90; // Network Transfer Function + public const byte NET_TRANSFER_SUBFUNC_ACTIVITY = 0x01; // Activity Log Transfer + public const byte NET_TRANSFER_SUBFUNC_DIAG = 0x02; // Diagnostic Log Transfer + + /* + ** Protocol Tags (as strings) + */ + public const string TAG_DMR_DATA = "DMRD"; + public const string TAG_P25_DATA = "P25D"; + public const string TAG_NXDN_DATA = "NXDD"; + + public const string TAG_REPEATER_LOGIN = "RPTL"; + public const string TAG_REPEATER_AUTH = "RPTK"; + public const string TAG_REPEATER_CONFIG = "RPTC"; + + public const string TAG_REPEATER_PING = "RPTP"; + public const string TAG_REPEATER_GRANT = "RPTG"; + + /* + ** Timers + */ + public const double STREAM_TO = 0.360d; + } // public sealed class Constants +} // namespace fnecore diff --git a/DMR/DMRDefines.cs b/DMR/DMRDefines.cs new file mode 100644 index 0000000..dd7274d --- /dev/null +++ b/DMR/DMRDefines.cs @@ -0,0 +1,84 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.DMR +{ + /// + /// DMR Data Types + /// + public enum DMRDataType : byte + { + /// + /// Voice Privacy Indicator Header + /// + VOICE_PI_HEADER = 0x00, + /// + /// Voice Link Control Header + /// + VOICE_LC_HEADER = 0x01, + /// + /// Terminator with Link Control + /// + TERMINATOR_WITH_LC = 0x02, + /// + /// Control Signalling Block + /// + CSBK = 0x03, + /// + /// Data Header + /// + DATA_HEADER = 0x06, + /// + /// 1/2 Rate Data + /// + RATE_12_DATA = 0x07, + /// + /// 3/4 Rate Data + /// + RATE_34_DATA = 0x08, + /// + /// Idle Burst + /// + IDLE = 0x09, + /// + /// 1 Rate Data + /// + RATE_1_DATA = 0x0A, + } // public enum DMRDataType : byte + + /// + /// DMR Full-Link Opcodes + /// + public enum DMRFLCO : byte + { + /// + /// GRP VCH USER - Group Voice Channel User + /// + FLCO_GROUP = 0x00, + /// + /// UU VCH USER - Unit-to-Unit Voice Channel User + /// + FLCO_PRIVATE = 0x01, + } // public enum DMRFLCO : byte +} // namespace fnecore.DMR diff --git a/DMR/EMB.cs b/DMR/EMB.cs new file mode 100644 index 0000000..2bd6062 --- /dev/null +++ b/DMR/EMB.cs @@ -0,0 +1,114 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022-2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +using fnecore.EDAC; + +namespace fnecore.DMR +{ + /// + /// Represents DMR embedded signalling. + /// + public class EMB + { + /// + /// DMR access color code. + /// + public byte ColorCode; + + /// + /// Flag indicating whether the privacy indicator is set or not. + /// + public bool PI; + + /// + /// Link control start/stop. + /// + public byte LCSS; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public EMB() + { + ColorCode = 0; + PI = false; + LCSS = 0; + } + + /// + /// Decodes DMR embedded signalling data. + /// + /// + public void Decode(byte[] data) + { + if (data == null) + throw new ArgumentNullException("data"); + + byte[] DMREMB = new byte[2U]; + DMREMB[0U] = (byte)((data[13U] << 4) & 0xF0U); + DMREMB[0U] |= (byte)((data[14U] >> 4) & 0x0FU); + DMREMB[1U] = (byte)((data[18U] << 4) & 0xF0U); + DMREMB[1U] |= (byte)((data[19U] >> 4) & 0x0FU); + + // decode QR (16,7,6) FEC + QR1676.Decode(DMREMB); + + ColorCode = (byte)((DMREMB[0U] >> 4) & 0x0FU); + PI = (DMREMB[0U] & 0x08U) == 0x08U; + LCSS = (byte)((DMREMB[0U] >> 1) & 0x03U); + } + + /// + /// Encodes DMR embedded signalling data. + /// + /// + public void Encode(ref byte[] data) + { + if (data == null) + throw new ArgumentNullException("data"); + + byte[] DMREMB = new byte[2U]; + DMREMB[0U] = (byte)((ColorCode << 4) & 0xF0U); + DMREMB[0U] |= (byte)(PI ? 0x08U : 0x00U); + DMREMB[0U] |= (byte)((LCSS << 1) & 0x06U); + DMREMB[1U] = 0x00; + + // encode QR (16,7,6) FEC + QR1676.Encode(ref DMREMB); + + data[13U] = (byte)((data[13U] & 0xF0U) | ((DMREMB[0U] >> 4) & 0x0FU)); + data[14U] = (byte)((data[14U] & 0x0FU) | ((DMREMB[0U] << 4) & 0xF0U)); + data[18U] = (byte)((data[18U] & 0xF0U) | ((DMREMB[1U] >> 4) & 0x0FU)); + data[19U] = (byte)((data[19U] & 0x0FU) | ((DMREMB[1U] << 4) & 0xF0U)); + } + } // public class EMB +} // namespace fnecore.DMR diff --git a/DMR/EmbeddedData.cs b/DMR/EmbeddedData.cs new file mode 100644 index 0000000..39bd9ac --- /dev/null +++ b/DMR/EmbeddedData.cs @@ -0,0 +1,380 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +using fnecore.EDAC; + +namespace fnecore.DMR +{ + /// + /// + /// + public enum EmbeddedLCState + { + LCS_NONE, + LCS_FIRST, + LCS_SECOND, + LCS_THIRD + }; + + /// + /// Represents DMR embedded data. + /// + public class EmbeddedData + { + private EmbeddedLCState state; + bool[] data; + bool[] raw; + + /// + /// Flag indicating whether or not the embedded data is valid. + /// + public bool IsValid + { + get; + private set; + } + + /// + /// Full-link control opcode + /// + public byte FLCO; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public EmbeddedData() + { + IsValid = false; + FLCO = (byte)DMRFLCO.FLCO_GROUP; + state = EmbeddedLCState.LCS_NONE; + data = new bool[72]; + raw = new bool[128]; + } + + /// + /// Unpack and error check an embedded LC. + /// + private void DecodeEmbeddedData() + { + // The data is unpacked downwards in columns + bool[] data = new bool[128U]; + + uint b = 0U; + for (uint a = 0U; a < 128U; a++) { + data[b] = this.raw[a]; + b += 16U; + if (b > 127U) + b -= 127U; + } + + // Hamming (16,11,4) check each row except the last one + for (uint a = 0U; a < 112U; a += 16U) { + if (!Hamming.decode16114(data, (int)a)) + return; + } + + // Check the parity bits + for (uint 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 (uint a = 0U; a < 11U; a++, b++) + this.data[b] = data[a]; + for (uint a = 16U; a < 27U; a++, b++) + this.data[b] = data[a]; + for (uint a = 32U; a < 42U; a++, b++) + this.data[b] = data[a]; + for (uint a = 48U; a < 58U; a++, b++) + this.data[b] = data[a]; + for (uint a = 64U; a < 74U; a++, b++) + this.data[b] = data[a]; + for (uint a = 80U; a < 90U; a++, b++) + this.data[b] = data[a]; + for (uint a = 96U; a < 106U; a++, b++) + this.data[b] = data[a]; + + // Extract the 5 bit CRC + uint 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 (!CRC.CheckFiveBit(this.data, crc)) + return; + + IsValid = true; + + // Extract the FLCO + byte flco = 0; + FneUtils.BitsToByteBE(this.data, 0, ref flco); + FLCO = (byte)(flco & 0x3FU); + } + + /// + /// Pack and FEC for an embedded LC. + /// + private void EncodeEmbeddedData() + { + uint crc = 0; + CRC.EncodeFiveBit(this.data, ref crc); + + bool[] data = new bool[128U]; + + 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; + + uint b = 0U; + for (uint a = 0U; a < 11U; a++, b++) + data[a] = this.data[b]; + for (uint a = 16U; a < 27U; a++, b++) + data[a] = this.data[b]; + for (uint a = 32U; a < 42U; a++, b++) + data[a] = this.data[b]; + for (uint a = 48U; a < 58U; a++, b++) + data[a] = this.data[b]; + for (uint a = 64U; a < 74U; a++, b++) + data[a] = this.data[b]; + for (uint a = 80U; a < 90U; a++, b++) + data[a] = this.data[b]; + for (uint a = 96U; a < 106U; a++, b++) + data[a] = this.data[b]; + + // Hamming (16,11,4) check each row except the last one + for (uint a = 0U; a < 112U; a += 16U) + Hamming.encode16114(ref data, (int)a); + + // Add the parity bits for each column + for (uint 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 (uint a = 0U; a < 128U; a++) { + this.raw[a] = data[b]; + b += 16U; + if (b > 127U) + b -= 127U; + } + } + + /// + /// Add LC data (which may consist of 4 blocks) to the data store. + /// + /// + /// + /// + public bool AddData(ref byte[] data, byte lcss) + { + if (data == null) + throw new NullReferenceException("data"); + + bool[] rawData = new bool[40U]; + FneUtils.ByteToBitsBE(data[14U], ref rawData, 0); + FneUtils.ByteToBitsBE(data[15U], ref rawData, 8); + FneUtils.ByteToBitsBE(data[16U], ref rawData, 16); + FneUtils.ByteToBitsBE(data[17U], ref rawData, 24); + FneUtils.ByteToBitsBE(data[18U], ref rawData, 32); + + // Is this the first block of a 4 block embedded LC ? + if (lcss == 1U) { + for (uint a = 0U; a < 32U; a++) + this.raw[a] = rawData[a + 4U]; + + // Show we are ready for the next LC block + state = EmbeddedLCState.LCS_FIRST; + IsValid = false; + + return false; + } + + // Is this the 2nd block of a 4 block embedded LC ? + if (lcss == 3U && state == EmbeddedLCState.LCS_FIRST) { + for (uint a = 0U; a < 32U; a++) + this.raw[a + 32U] = rawData[a + 4U]; + + // Show we are ready for the next LC block + state = EmbeddedLCState.LCS_SECOND; + + return false; + } + + // Is this the 3rd block of a 4 block embedded LC ? + if (lcss == 3U && state == EmbeddedLCState.LCS_SECOND) { + for (uint a = 0U; a < 32U; a++) + this.raw[a + 64U] = rawData[a + 4U]; + + // Show we are ready for the final LC block + state = EmbeddedLCState.LCS_THIRD; + + return false; + } + + // Is this the final block of a 4 block embedded LC ? + if (lcss == 2U && state == EmbeddedLCState.LCS_THIRD) { + for (uint a = 0U; a < 32U; a++) + this.raw[a + 96U] = rawData[a + 4U]; + + // Show that we're not ready for any more data + state = EmbeddedLCState.LCS_NONE; + + // Process the complete data block + DecodeEmbeddedData(); + if (IsValid) + EncodeEmbeddedData(); + + return IsValid; + } + + return false; + } + + /// + /// + /// + /// + /// + /// + public byte GetData(ref byte[] data, byte n) + { + if (data == null) + throw new NullReferenceException("data"); + + if (n >= 1U && n < 5U) { + n--; + + bool[] bits = new bool[40U]; + Buffer.BlockCopy(this.raw, n * 32, bits, 4, 32 * sizeof(bool)); + + byte[] bytes = new byte[5U]; + FneUtils.BitsToByteBE(bits, 0, ref bytes[0U]); + FneUtils.BitsToByteBE(bits, 8, ref bytes[1U]); + FneUtils.BitsToByteBE(bits, 16, ref bytes[2U]); + FneUtils.BitsToByteBE(bits, 24, ref bytes[3U]); + FneUtils.BitsToByteBE(bits, 32, ref bytes[4U]); + + data[14U] = (byte)((data[14U] & 0xF0U) | (bytes[0U] & 0x0FU)); + data[15U] = bytes[1U]; + data[16U] = bytes[2U]; + data[17U] = bytes[3U]; + data[18U] = (byte)((data[18U] & 0x0FU) | (bytes[4U] & 0xF0U)); + + switch (n) { + case 0: + return 1; + case 3: + return 2; + default: + return 3; + } + } + else { + data[14U] &= (byte)0xF0U; + data[15U] = (byte)0x00U; + data[16U] = (byte)0x00U; + data[17U] = (byte)0x00U; + data[18U] &= (byte)0x0FU; + + return 0; + } + } + + /// Sets link control data. + /// + public void SetLC(LC lc) + { + lc.GetData(ref data); + + FLCO = lc.FLCO; + IsValid = true; + + EncodeEmbeddedData(); + } + + /// Gets link control data. + /// + public LC GetLC() + { + if (!IsValid) + return null; + + if (FLCO != (byte)DMRFLCO.FLCO_GROUP && FLCO != (byte)DMRFLCO.FLCO_PRIVATE) + return null; + + return new LC(data); + } + + /// + /// + /// + /// + /// + public bool GetRawData(ref byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + + if (!IsValid) + return false; + + FneUtils.BitsToByteBE(this.data, 0, ref data[0U]); + FneUtils.BitsToByteBE(this.data, 8, ref data[1U]); + FneUtils.BitsToByteBE(this.data, 16, ref data[2U]); + FneUtils.BitsToByteBE(this.data, 24, ref data[3U]); + FneUtils.BitsToByteBE(this.data, 32, ref data[4U]); + FneUtils.BitsToByteBE(this.data, 40, ref data[5U]); + FneUtils.BitsToByteBE(this.data, 48, ref data[6U]); + FneUtils.BitsToByteBE(this.data, 56, ref data[7U]); + FneUtils.BitsToByteBE(this.data, 64, ref data[8U]); + + return true; + } + + /// + /// Helper to reset data values to defaults. + /// + public void Reset() + { + state = EmbeddedLCState.LCS_NONE; + IsValid = false; + } + } // public class EmbeddedData +} // namespace fnecore.DMR diff --git a/DMR/FullLC.cs b/DMR/FullLC.cs new file mode 100644 index 0000000..a7c5900 --- /dev/null +++ b/DMR/FullLC.cs @@ -0,0 +1,192 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +using fnecore.EDAC; + +namespace fnecore.DMR +{ + /// + /// Represents full DMR link control. + /// + public sealed class FullLC + { + private static BPTC19696 bptc = new BPTC19696(); + + private static readonly byte[] VOICE_LC_HEADER_CRC_MASK = new byte[3] { 0x96, 0x96, 0x96 }; + private static readonly byte[] TERMINATOR_WITH_LC_CRC_MASK = new byte[3] { 0x99, 0x99, 0x99 }; + private static readonly byte[] PI_HEADER_CRC_MASK = new byte[2] { 0x69, 0x69 }; + + /* + ** Methods + */ + + /// + /// Decode DMR full-link control data. + /// + /// + /// + /// + public static LC Decode(byte[] data, DMRDataType type) + { + if (data == null) + throw new NullReferenceException("data"); + + // decode BPTC (196,96) FEC + byte[] lcData = new byte[12]; + bptc.Decode(data, out lcData); + + switch (type) { + case DMRDataType.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 DMRDataType.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: + // unsupported LC type + return null; + } + + // check RS (12,9) FEC + if (!RS129.Check(lcData)) + return null; + + return new LC(lcData); + } + + /// + /// Encode DMR full-link control data. + /// + /// + /// + /// + public static void Encode(LC lc, ref byte[] data, DMRDataType type) + { + if (lc == null) + throw new NullReferenceException("lc"); + if (data == null) + throw new NullReferenceException("data"); + + byte[] lcData = new byte[12]; + lc.GetData(ref lcData); + + // encode RS (12,9) FEC + byte[] parity = new byte[4]; + RS129.Encode(lcData, 9, ref parity); + + switch (type) { + case DMRDataType.VOICE_LC_HEADER: + lcData[9U] = (byte)(parity[2U] ^ VOICE_LC_HEADER_CRC_MASK[0U]); + lcData[10U] = (byte)(parity[1U] ^ VOICE_LC_HEADER_CRC_MASK[1U]); + lcData[11U] = (byte)(parity[0U] ^ VOICE_LC_HEADER_CRC_MASK[2U]); + break; + + case DMRDataType.TERMINATOR_WITH_LC: + lcData[9U] = (byte)(parity[2U] ^ TERMINATOR_WITH_LC_CRC_MASK[0U]); + lcData[10U] = (byte)(parity[1U] ^ TERMINATOR_WITH_LC_CRC_MASK[1U]); + lcData[11U] = (byte)(parity[0U] ^ TERMINATOR_WITH_LC_CRC_MASK[2U]); + break; + + default: + // unsupported LC type + return; + } + + // encode BPTC (196,96) FEC + bptc.Encode(lcData, out data); + } + + /// + /// Decode DMR privacy control data. + /// + /// + /// + /// + public static PrivacyLC DecodePI(byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + + // decode BPTC (196,96) FEC + byte[] lcData = new byte[12]; + bptc.Decode(data, out lcData); + + // make sure the CRC-CCITT 16 was actually included (the network tends to zero the CRC) + if (lcData[10U] != 0x00U && lcData[11U] != 0x00U) { + // validate the CRC-CCITT 16 + lcData[10U] ^= PI_HEADER_CRC_MASK[0U]; + lcData[11U] ^= PI_HEADER_CRC_MASK[1U]; + + if (CRC.CheckCCITT162(lcData, 12)) + return null; + + // restore the checksum + lcData[10U] ^= PI_HEADER_CRC_MASK[0U]; + lcData[11U] ^= PI_HEADER_CRC_MASK[1U]; + } + + return new PrivacyLC(lcData); + } + + /// + /// Encode DMR privacy control data. + /// + /// + /// + /// + public static void EncodePI(PrivacyLC lc, ref byte[] data) + { + if (lc == null) + throw new NullReferenceException("lc"); + if (data == null) + throw new NullReferenceException("data"); + + byte[] lcData = new byte[12]; + lc.GetData(ref lcData); + + // compute CRC-CCITT 16 + lcData[10U] ^= PI_HEADER_CRC_MASK[0U]; + lcData[11U] ^= PI_HEADER_CRC_MASK[1U]; + + CRC.AddCCITT162(ref lcData, 12); + + // restore the checksum + lcData[10U] ^= PI_HEADER_CRC_MASK[0U]; + lcData[11U] ^= PI_HEADER_CRC_MASK[1U]; + + // encode BPTC (196,96) FEC + bptc.Encode(lcData, out data); + } + } // public class LC +} // namespace fnecore.DMR diff --git a/DMR/LC.cs b/DMR/LC.cs new file mode 100644 index 0000000..c1c5bfd --- /dev/null +++ b/DMR/LC.cs @@ -0,0 +1,245 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.DMR +{ + /// + /// Represents DMR link control data. + /// + public class LC + { + private bool R; + + /// + /// Flag indicating whether link protection is enabled. + /// + public bool PF; + + /// + /// Full-link control opcode. + /// + public byte FLCO; + + /// + /// Feature ID. + /// + public byte FID; + + /// + /// Source ID. + /// + public uint SrcId; + + /// + /// Destination ID. + /// + public uint DstId; + + /** Service Options */ + /// + /// Flag indicating the emergency bits are set. + /// + public bool Emergency; + + /// + /// Flag indicating that encryption is enabled. + /// + public bool Encrypted; + + /// + /// Flag indicating broadcast operation. + /// + public bool Broadcast; + + /// + /// Flag indicating OVCM operation. + /// + public bool OVCM; + + /// + /// Priority level for the traffic. + /// + public byte Priority; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public LC() + { + PF = false; + + FLCO = 0; + FID = 0; + + SrcId = 0; + DstId = 0; + + Emergency = false; + Encrypted = false; + Broadcast = false; + OVCM = false; + Priority = 2; + + R = false; + } + + /// + /// Initializes a new instance of the class. + /// + /// + public LC(byte[] bytes) + { + PF = (bytes[0U] & 0x80U) == 0x80U; + R = (bytes[0U] & 0x40U) == 0x40U; + + FLCO = (byte)(bytes[0U] & 0x3FU); + + FID = bytes[1U]; + + Emergency = (bytes[2U] & 0x80U) == 0x80U; // Emergency Flag + Encrypted = (bytes[2U] & 0x40U) == 0x40U; // Encryption Flag + Broadcast = (bytes[2U] & 0x08U) == 0x08U; // Broadcast Flag + OVCM = (bytes[2U] & 0x04U) == 0x04U; // OVCM Flag + Priority = (byte)(bytes[2U] & 0x03U); // Priority + + DstId = (uint)(bytes[3U] << 16 | bytes[4U] << 8 | bytes[5U]); // Destination Address + SrcId = (uint)(bytes[6U] << 16 | bytes[7U] << 8 | bytes[8U]); // Source Address + } + + /// + /// Initializes a new instance of the class. + /// + /// + public LC(bool[] bits) + { + PF = bits[0U]; + R = bits[1U]; + + byte temp1 = 0, temp2 = 0, temp3 = 0; + FneUtils.BitsToByteBE(bits, 0, ref temp1); + FLCO = (byte)(temp1 & 0x3FU); + + FneUtils.BitsToByteBE(bits, 8, ref temp2); + FID = temp2; + + FneUtils.BitsToByteBE(bits, 16, ref temp3); + + Emergency = (temp3 & 0x80U) == 0x80U; // Emergency Flag + Encrypted = (temp3 & 0x40U) == 0x40U; // Encryption Flag + Broadcast = (temp3 & 0x08U) == 0x08U; // Broadcast Flag + OVCM = (temp3 & 0x04U) == 0x04U; // OVCM Flag + Priority = (byte)(temp3 & 0x03U); // Priority + + byte d1 = 0, d2 = 0, d3 = 0; + FneUtils.BitsToByteBE(bits, 24, ref d1); + FneUtils.BitsToByteBE(bits, 32, ref d2); + FneUtils.BitsToByteBE(bits, 40, ref d3); + + byte s1 = 0, s2 = 0, s3 = 0; + FneUtils.BitsToByteBE(bits, 48, ref s1); + FneUtils.BitsToByteBE(bits, 56, ref s2); + FneUtils.BitsToByteBE(bits, 64, ref s3); + + SrcId = (uint)(s1 << 16 | s2 << 8 | s3); // Source Address + DstId = (uint)(d1 << 16 | d2 << 8 | d3); // Destination Address + } + + /// + /// Gets LC data as bytes. + /// + /// + public byte[] GetBytes() + { + byte[] lcData = new byte[12]; + GetData(ref lcData); + + return lcData; + } + + /// + /// Gets LC data as bytes. + /// + /// + public void GetData(ref byte[] bytes) + { + if (bytes == null) + throw new NullReferenceException("bytes"); + + bytes[0U] = FLCO; + + if (PF) + bytes[0U] |= (byte)0x80U; + + if (R) + bytes[0U] |= (byte)0x40U; + + bytes[1U] = FID; + + bytes[2U] = (byte)((Emergency ? 0x80U : 0x00U) + // Emergency Flag + (Encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (Broadcast ? 0x08U : 0x00U) + // Broadcast Flag + (OVCM ? 0x04U : 0x00U) + // OVCM Flag + (Priority & 0x03U)); // Priority + + bytes[3U] = (byte)(DstId >> 16); // Destination Address + bytes[4U] = (byte)(DstId >> 8); // .. + bytes[5U] = (byte)(DstId >> 0); // .. + + bytes[6U] = (byte)(SrcId >> 16); // Source Address + bytes[7U] = (byte)(SrcId >> 8); // .. + bytes[8U] = (byte)(SrcId >> 0); // .. + } + + /// + /// + /// + /// + public void GetData(ref bool[] bits) + { + if (bits == null) + throw new NullReferenceException("bits"); + + byte[] bytes = new byte[9U]; + GetData(ref bytes); + + FneUtils.ByteToBitsBE(bytes[0U], ref bits, 0); + FneUtils.ByteToBitsBE(bytes[1U], ref bits, 8); + FneUtils.ByteToBitsBE(bytes[2U], ref bits, 16); + FneUtils.ByteToBitsBE(bytes[3U], ref bits, 24); + FneUtils.ByteToBitsBE(bytes[4U], ref bits, 32); + FneUtils.ByteToBitsBE(bytes[5U], ref bits, 40); + FneUtils.ByteToBitsBE(bytes[6U], ref bits, 48); + FneUtils.ByteToBitsBE(bytes[7U], ref bits, 56); + FneUtils.ByteToBitsBE(bytes[8U], ref bits, 64); + } + } // public class LC +} // namespace fnecore.DMR diff --git a/DMR/PrivacyLC.cs b/DMR/PrivacyLC.cs new file mode 100644 index 0000000..25844a1 --- /dev/null +++ b/DMR/PrivacyLC.cs @@ -0,0 +1,144 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.DMR +{ + /// + /// Represents DMR privacy indicator link control data. + /// + public class PrivacyLC + { + private byte[] mi; + + /// + /// Feature ID. + /// + public byte FID; + + /// + /// Destination ID. + /// + public uint DstId; + + /** Service Options */ + /// + /// Flag indicating a group/talkgroup operation. + /// + public bool Group; + + /** Encryption Data */ + /// + /// Encryption algorithm ID. + /// + public byte AlgId; + /// + /// Encryption key ID. + /// + public uint KId; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public PrivacyLC() + { + mi = new byte[4]; + + FID = 0; + + DstId = 0; + + Group = false; + + AlgId = 0; + KId = 0; + } + + /// + /// Initializes a new instance of the class. + /// + /// + public PrivacyLC(byte[] bytes) + { + mi = new byte[4]; + + Group = (bytes[0U] & 0x20U) == 0x20U; + AlgId = (byte)(bytes[0U] & 7); // Algorithm ID + + FID = bytes[1U]; + KId = bytes[2U]; + + mi[0U] = bytes[3U]; + mi[1U] = bytes[4U]; + mi[2U] = bytes[5U]; + mi[3U] = bytes[6U]; + + DstId = (uint)(bytes[7U] << 16 | bytes[8U] << 8 | bytes[9U]); // Destination Address + } + + /// + /// Gets LC data as bytes. + /// + /// + public byte[] GetBytes() + { + byte[] lcData = new byte[12]; + GetData(ref lcData); + + return lcData; + } + + /// + /// Gets LC data as bytes. + /// + /// + public void GetData(ref byte[] bytes) + { + if (bytes == null) + throw new NullReferenceException("bytes"); + + bytes[0U] = (byte)((Group ? 0x20U : 0x00U) + + (AlgId & 0x07U)); // Algorithm ID + + bytes[1U] = FID; + bytes[2U] = (byte)KId; + + bytes[3U] = mi[0U]; + bytes[4U] = mi[1U]; + bytes[5U] = mi[2U]; + bytes[6U] = mi[3U]; + + bytes[7U] = (byte)(DstId >> 16); // Destination Address + bytes[8U] = (byte)(DstId >> 8); // .. + bytes[9U] = (byte)(DstId >> 0); // .. + } + } // public class PrivacyLC +} // namespace fnecore.DMR diff --git a/DMR/SlotType.cs b/DMR/SlotType.cs new file mode 100644 index 0000000..181f2a5 --- /dev/null +++ b/DMR/SlotType.cs @@ -0,0 +1,108 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +using fnecore.EDAC; + +namespace fnecore.DMR +{ + /// + /// Represents DMR slot type. + /// + public class SlotType + { + /// + /// DMR access color code. + /// + public byte ColorCode; + + /// + /// Slot data type. + /// + public byte DataType; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public SlotType() + { + ColorCode = 0; + DataType = 0; + } + + /// + /// Initializes a new instance of the class. + /// + /// + public SlotType(byte[] bytes) + { + byte[] DMRSlotType = new byte[3U]; + DMRSlotType[0U] = (byte)((bytes[12U] << 2) & 0xFCU); + DMRSlotType[0U] |= (byte)((bytes[13U] >> 6) & 0x03U); + + DMRSlotType[1U] = (byte)((bytes[13U] << 2) & 0xC0U); + DMRSlotType[1U] |= (byte)((bytes[19U] << 2) & 0x3CU); + DMRSlotType[1U] |= (byte)((bytes[20U] >> 6) & 0x03U); + + DMRSlotType[2U] = (byte)((bytes[20U] << 2) & 0xF0U); + + Golay2087 golay2087 = new Golay2087(); + byte code = golay2087.Decode(DMRSlotType); + + ColorCode = (byte)((code >> 4) & 0x0FU); + DataType = (byte)((code >> 0) & 0x0FU); + } + + /// + /// Gets data as bytes. + /// + /// + public void GetData(ref byte[] bytes) + { + if (bytes == null) + throw new NullReferenceException("bytes"); + + byte[] DMRSlotType = new byte[3U]; + DMRSlotType[0U] = (byte)((ColorCode << 4) & 0xF0U); + DMRSlotType[0U] |= (byte)((DataType << 0) & 0x0FU); + DMRSlotType[1U] = (byte)0x00U; + DMRSlotType[2U] = (byte)0x00U; + + Golay2087 golay2087 = new Golay2087(); + golay2087.Encode(ref DMRSlotType); + + bytes[12U] = (byte)((bytes[12U] & 0xC0U) | ((DMRSlotType[0U] >> 2) & 0x3FU)); + bytes[13U] = (byte)((bytes[13U] & 0x0FU) | ((DMRSlotType[0U] << 6) & 0xC0U) | ((DMRSlotType[1U] >> 2) & 0x30U)); + bytes[19U] = (byte)((bytes[19U] & 0xF0U) | ((DMRSlotType[1U] >> 2) & 0x0FU)); + bytes[20U] = (byte)((bytes[20U] & 0x03U) | ((DMRSlotType[1U] << 6) & 0xC0U) | ((DMRSlotType[2U] >> 2) & 0x3CU)); + } + } // public class SlotType +} // namespace fnecore.DMR diff --git a/EDAC/BPTC19696.cs b/EDAC/BPTC19696.cs new file mode 100644 index 0000000..6aecf2f --- /dev/null +++ b/EDAC/BPTC19696.cs @@ -0,0 +1,409 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.EDAC +{ + /// + /// Implements Block Product Turbo Code (196,96) FEC. + /// + public sealed class BPTC19696 + { + private bool[] rawData; + private bool[] deInterData; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public BPTC19696() + { + rawData = new bool[196]; + deInterData = new bool[196]; + } + + /// + /// Decode BPTC (196,96) FEC. + /// + /// + /// + public void Decode(byte[] _in, out byte[] _out) + { + _out = null; + if (_in == null) + throw new NullReferenceException("_in"); + + // Get the raw binary + DecodeExtractBinary(_in); + + // Deinterleave + DecodeDeInterleave(); + + // Error check + DecodeErrorCheck(); + + // Extract Data + _out = DecodeExtractData(); + } + + /// + /// Encode BPTC (196,96) FEC. + /// + /// + /// + public void Encode(byte[] _in, out byte[] _out) + { + _out = null; + if (_in == null) + throw new NullReferenceException("_in"); + + // Extract Data + EncodeExtractData(_in); + + // Error check + EncodeErrorCheck(); + + // Interleave + EncodeInterleave(); + + // Get the raw binary + _out = EncodeExtractBinary(); + } + + /// + /// + /// + /// + private void DecodeExtractBinary(byte[] data) + { + // First block + FneUtils.ByteToBitsBE(data[0U], ref rawData, 0); + FneUtils.ByteToBitsBE(data[1U], ref rawData, 8); + FneUtils.ByteToBitsBE(data[2U], ref rawData, 16); + FneUtils.ByteToBitsBE(data[3U], ref rawData, 24); + FneUtils.ByteToBitsBE(data[4U], ref rawData, 32); + FneUtils.ByteToBitsBE(data[5U], ref rawData, 40); + FneUtils.ByteToBitsBE(data[6U], ref rawData, 48); + FneUtils.ByteToBitsBE(data[7U], ref rawData, 56); + FneUtils.ByteToBitsBE(data[8U], ref rawData, 64); + FneUtils.ByteToBitsBE(data[9U], ref rawData, 72); + FneUtils.ByteToBitsBE(data[10U], ref rawData, 80); + FneUtils.ByteToBitsBE(data[11U], ref rawData, 88); + FneUtils.ByteToBitsBE(data[12U], ref rawData, 96); + + // Handle the two bits + bool[] bits = new bool[8]; + FneUtils.ByteToBitsBE(data[20U], ref bits, 0); + rawData[98U] = bits[6U]; + rawData[99U] = bits[7U]; + + // Second block + FneUtils.ByteToBitsBE(data[21U], ref rawData, 100); + FneUtils.ByteToBitsBE(data[22U], ref rawData, 108); + FneUtils.ByteToBitsBE(data[23U], ref rawData, 116); + FneUtils.ByteToBitsBE(data[24U], ref rawData, 124); + FneUtils.ByteToBitsBE(data[25U], ref rawData, 132); + FneUtils.ByteToBitsBE(data[26U], ref rawData, 140); + FneUtils.ByteToBitsBE(data[27U], ref rawData, 148); + FneUtils.ByteToBitsBE(data[28U], ref rawData, 156); + FneUtils.ByteToBitsBE(data[29U], ref rawData, 164); + FneUtils.ByteToBitsBE(data[30U], ref rawData, 172); + FneUtils.ByteToBitsBE(data[31U], ref rawData, 180); + FneUtils.ByteToBitsBE(data[32U], ref rawData, 188); + } + + /// + /// + /// + private void DecodeErrorCheck() + { + bool fixing; + uint count = 0U; + do + { + fixing = false; + + // Run through each of the 15 columns + bool[] col = new bool[13]; + for (uint c = 0U; c < 15U; c++) + { + uint pos = c + 1U; + for (uint a = 0U; a < 13U; a++) + { + col[a] = deInterData[pos]; + pos = pos + 15U; + } + + if (Hamming.decode1393(col)) + { + //uint pos = c + 1U; + pos = c + 1U; // bryanb: this may be a bad port... + for (uint a = 0U; a < 13U; a++) + { + deInterData[pos] = col[a]; + pos = pos + 15U; + } + + fixing = true; + } + } + + // Run through each of the 9 rows containing data + for (uint r = 0U; r < 9U; r++) + { + uint pos = (r * 15U) + 1U; + if (Hamming.decode15113_2(deInterData, (int)pos)) + fixing = true; + } + + count++; + } while (fixing && count < 5U); + } + + /// + /// + /// + private void DecodeDeInterleave() + { + for (uint i = 0U; i < 196U; i++) + deInterData[i] = false; + + // The first bit is R(3) which is not used so can be ignored + for (uint a = 0U; a < 196U; a++) + { + // Calculate the interleave sequence + uint interleaveSequence = (a * 181U) % 196U; + // Shuffle the data + deInterData[a] = rawData[interleaveSequence]; + } + } + + /// + /// + /// + /// + private byte[] DecodeExtractData() + { + bool[] bData = new bool[96]; + uint pos = 0U; + for (uint a = 4U; a <= 11U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 16U; a <= 26U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 31U; a <= 41U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 46U; a <= 56U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 61U; a <= 71U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 76U; a <= 86U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 91U; a <= 101U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 106U; a <= 116U; a++, pos++) + bData[pos] = deInterData[a]; + + for (uint a = 121U; a <= 131U; a++, pos++) + bData[pos] = deInterData[a]; + + byte[] data = new byte[12]; + FneUtils.BitsToByteBE(bData, 0, ref data[0]); + FneUtils.BitsToByteBE(bData, 8, ref data[1]); + FneUtils.BitsToByteBE(bData, 16, ref data[2]); + FneUtils.BitsToByteBE(bData, 24, ref data[3]); + FneUtils.BitsToByteBE(bData, 32, ref data[4]); + FneUtils.BitsToByteBE(bData, 40, ref data[5]); + FneUtils.BitsToByteBE(bData, 48, ref data[6]); + FneUtils.BitsToByteBE(bData, 56, ref data[7]); + FneUtils.BitsToByteBE(bData, 64, ref data[8]); + FneUtils.BitsToByteBE(bData, 72, ref data[9]); + FneUtils.BitsToByteBE(bData, 80, ref data[10]); + FneUtils.BitsToByteBE(bData, 88, ref data[11]); + + return data; + } + + /// + /// + /// + /// + private void EncodeExtractData(byte[] data) + { + bool[] bData = new bool[96]; + FneUtils.ByteToBitsBE(data[0U], ref bData, 0); + FneUtils.ByteToBitsBE(data[1U], ref bData, 8); + FneUtils.ByteToBitsBE(data[2U], ref bData, 16); + FneUtils.ByteToBitsBE(data[3U], ref bData, 24); + FneUtils.ByteToBitsBE(data[4U], ref bData, 32); + FneUtils.ByteToBitsBE(data[5U], ref bData, 40); + FneUtils.ByteToBitsBE(data[6U], ref bData, 48); + FneUtils.ByteToBitsBE(data[7U], ref bData, 56); + FneUtils.ByteToBitsBE(data[8U], ref bData, 64); + FneUtils.ByteToBitsBE(data[9U], ref bData, 72); + FneUtils.ByteToBitsBE(data[10U], ref bData, 80); + FneUtils.ByteToBitsBE(data[11U], ref bData, 88); + + for (uint i = 0U; i < 196U; i++) + deInterData[i] = false; + + uint pos = 0U; + for (uint a = 4U; a <= 11U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 16U; a <= 26U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 31U; a <= 41U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 46U; a <= 56U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 61U; a <= 71U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 76U; a <= 86U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 91U; a <= 101U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 106U; a <= 116U; a++, pos++) + deInterData[a] = bData[pos]; + + for (uint a = 121U; a <= 131U; a++, pos++) + deInterData[a] = bData[pos]; + } + + /// + /// + /// + private void EncodeInterleave() + { + for (uint i = 0U; i < 196U; i++) + rawData[i] = false; + + // The first bit is R(3) which is not used so can be ignored + for (uint a = 0U; a < 196U; a++) + { + // Calculate the interleave sequence + uint interleaveSequence = (a * 181U) % 196U; + // Unshuffle the data + rawData[interleaveSequence] = deInterData[a]; + } + } + + /// + /// + /// + private void EncodeErrorCheck() + { + // Run through each of the 9 rows containing data + for (uint r = 0U; r < 9U; r++) + { + uint pos = (r * 15U) + 1U; + Hamming.encode15113_2(ref deInterData, (int)pos); + } + + // Run through each of the 15 columns + bool[] col = new bool[13]; + for (uint c = 0U; c < 15U; c++) + { + uint pos = c + 1U; + for (uint a = 0U; a < 13U; a++) + { + col[a] = deInterData[pos]; + pos = pos + 15U; + } + + Hamming.encode1393(ref col); + + pos = c + 1U; + for (uint a = 0U; a < 13U; a++) + { + deInterData[pos] = col[a]; + pos = pos + 15U; + } + } + } + + /// + /// + /// + /// + private byte[] EncodeExtractBinary() + { + byte[] data = new byte[33]; + + // First block + FneUtils.BitsToByteBE(rawData, 0, ref data[0]); + FneUtils.BitsToByteBE(rawData, 8, ref data[1]); + FneUtils.BitsToByteBE(rawData, 16, ref data[2]); + FneUtils.BitsToByteBE(rawData, 24, ref data[3]); + FneUtils.BitsToByteBE(rawData, 32, ref data[4]); + FneUtils.BitsToByteBE(rawData, 40, ref data[5]); + FneUtils.BitsToByteBE(rawData, 48, ref data[6]); + FneUtils.BitsToByteBE(rawData, 56, ref data[7]); + FneUtils.BitsToByteBE(rawData, 64, ref data[8]); + FneUtils.BitsToByteBE(rawData, 72, ref data[9]); + FneUtils.BitsToByteBE(rawData, 80, ref data[10]); + FneUtils.BitsToByteBE(rawData, 88, ref data[11]); + + // Handle the two bits + byte val = 0x00; + FneUtils.BitsToByteBE(rawData, 96, ref val); + data[12U] = (byte)((data[12U] & 0x3FU) | ((val >> 0) & 0xC0U)); + data[20U] = (byte)((data[20U] & 0xFCU) | ((val >> 4) & 0x03U)); + + // Second block + FneUtils.BitsToByteBE(rawData, 100, ref data[21]); + FneUtils.BitsToByteBE(rawData, 108, ref data[22]); + FneUtils.BitsToByteBE(rawData, 116, ref data[23]); + FneUtils.BitsToByteBE(rawData, 124, ref data[24]); + FneUtils.BitsToByteBE(rawData, 132, ref data[25]); + FneUtils.BitsToByteBE(rawData, 140, ref data[26]); + FneUtils.BitsToByteBE(rawData, 148, ref data[27]); + FneUtils.BitsToByteBE(rawData, 156, ref data[28]); + FneUtils.BitsToByteBE(rawData, 164, ref data[29]); + FneUtils.BitsToByteBE(rawData, 172, ref data[30]); + FneUtils.BitsToByteBE(rawData, 180, ref data[31]); + FneUtils.BitsToByteBE(rawData, 188, ref data[32]); + + return data; + } + } // public sealed class BPTC19696 +} // namespace fnecore.EDAC diff --git a/EDAC/CRC.cs b/EDAC/CRC.cs new file mode 100644 index 0000000..c5e85b5 --- /dev/null +++ b/EDAC/CRC.cs @@ -0,0 +1,523 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; +using System.Runtime.InteropServices; + +namespace fnecore.EDAC +{ + /// + /// Implements various Cyclic Redundancy Check routines. + /// + public sealed class CRC + { + public static readonly byte[] CRC8_TABLE = new byte[257] { + 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 }; + + public static readonly ushort[] CRC9_TABLE = new ushort[135] { + 0x1E7, 0x1F3, 0x1F9, 0x1FC, 0x0D2, 0x045, 0x122, 0x0BD, 0x15E, 0x083, + 0x141, 0x1A0, 0x0FC, 0x052, 0x005, 0x102, 0x0AD, 0x156, 0x087, 0x143, + 0x1A1, 0x1D0, 0x0C4, 0x04E, 0x00B, 0x105, 0x182, 0x0ED, 0x176, 0x097, + 0x14B, 0x1A5, 0x1D2, 0x0C5, 0x162, 0x09D, 0x14E, 0x08B, 0x145, 0x1A2, + 0x0FD, 0x17E, 0x093, 0x149, 0x1A4, 0x0FE, 0x053, 0x129, 0x194, 0x0E6, + 0x05F, 0x12F, 0x197, 0x1CB, 0x1E5, 0x1F2, 0x0D5, 0x16A, 0x099, 0x14C, + 0x08A, 0x069, 0x134, 0x0B6, 0x077, 0x13B, 0x19D, 0x1CE, 0x0CB, 0x165, + 0x1B2, 0x0F5, 0x17A, 0x091, 0x148, 0x088, 0x068, 0x018, 0x020, 0x03C, + 0x032, 0x035, 0x11A, 0x0A1, 0x150, 0x084, 0x06E, 0x01B, 0x10D, 0x186, + 0x0EF, 0x177, 0x1BB, 0x1DD, 0x1EE, 0x0DB, 0x16D, 0x1B6, 0x0F7, 0x17B, + 0x1BD, 0x1DE, 0x0C3, 0x161, 0x1B0, 0x0F4, 0x056, 0x007, 0x103, 0x181, + 0x1C0, 0x0CC, 0x04A, 0x009, 0x104, 0x0AE, 0x07B, 0x13D, 0x19E, 0x0E3, + 0x171, 0x1B8, 0x0F0, 0x054, 0x006, 0x02F, 0x117, 0x18B, 0x1C5, 0x1E2, + 0x0DD, 0x16E, 0x09B, 0x14D, 0x1A6 }; + + public static readonly ushort[] CCITT16_TABLE1 = new ushort[256] { + 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, + 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, + 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, + 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, + 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, + 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5, + 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, + 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974, + 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB, + 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, + 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, + 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, + 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, + 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1, + 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738, + 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, + 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7, + 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF, + 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, + 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, + 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, + 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, + 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134, + 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C, + 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, + 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, + 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232, + 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, + 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, + 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, + 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, + 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78 }; + + public static readonly ushort[] CCITT16_TABLE2 = new ushort[256] { + 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 }; + + public static readonly uint[] CRC32_TABLE = new uint[256] { + 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, + 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, + 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, + 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, + 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, + 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, + 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, + 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, + 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, + 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, + 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, + 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, + 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, + 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, + 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, + 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, + 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, + 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, + 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, + 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, + 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, + 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, + 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, + 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, + 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, + 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, + 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, + 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, + 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, + 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, + 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, + 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 }; + + /* + ** Structures + ** + ** bryanb: Please don't modify these following structures, they are specially designed to abuse + ** how the .NET CLR handles memory management, such that these effectively emulate C/C++ unions. + */ +#pragma warning disable CS0649 + /// + /// + /// + private struct DoubleByte + { + /// + /// + /// + public byte B0; + /// + /// + /// + public byte B1; + } // private struct DoubleByte + + /// + /// + /// + [StructLayout(LayoutKind.Explicit, Size = 2)] + private struct UShortUnion + { + [FieldOffset(0)] + public ushort crc16; + [FieldOffset(0)] + public DoubleByte crc8; + } // private struct UShortUnion + + /// + /// + /// + private struct QuadByte + { + /// + /// + /// + public byte B0; + /// + /// + /// + public byte B1; + /// + /// + /// + public byte B2; + /// + /// + /// + public byte B3; + } // private struct QuadByte + + /// + /// + /// + [StructLayout(LayoutKind.Explicit, Size = 4)] + private struct UIntUnion + { + [FieldOffset(0)] + public uint crc32; + [FieldOffset(0)] + public QuadByte crc8; + } // private struct UIntUnion +#pragma warning restore CS0649 + /* + ** Methods + */ + + /// + /// Check 5-bit CRC. + /// + /// Boolean bit array. + /// Computed CRC to check. + /// True, if CRC is valid, otherwise false. + public static bool CheckFiveBit(bool[] _in, uint tcrc) + { + if (_in == null) + throw new NullReferenceException("_in"); + + uint crc = 0x0; + EncodeFiveBit(_in, ref crc); + + return crc == tcrc; + } + + /// + /// Encode 5-bit CRC. + /// + /// Boolean bit array. + /// Computed CRC. + public static void EncodeFiveBit(bool[] _in, ref uint tcrc) + { + if (_in == null) + throw new NullReferenceException("_in"); + + ushort total = 0; + for (uint i = 0U; i < 72U; i += 8U) { + byte c = 0x0; + FneUtils.BitsToByteBE(_in, (int)i, ref c); + total += c; + } + + total %= (byte)(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. + public static bool CheckCCITT162(byte[] _in, uint length) + { + if (_in == null) + throw new NullReferenceException("_in"); + if (length > 2) + throw new ArgumentOutOfRangeException("length"); + + UShortUnion union = new UShortUnion(); + union.crc16 = 0; + + for (uint i = 0U; i < (length - 2U); i++) + union.crc16 = (ushort)((union.crc8.B0 << 8) ^ CCITT16_TABLE2[union.crc8.B1 ^ _in[i]]); + + union.crc16 = (ushort)(~union.crc16); + + return union.crc8.B0 == _in[length - 1U] && union.crc8.B1 == _in[length - 2U]; + } + + /// + /// Encode 16-bit CRC-CCITT. + /// + /// This uses polynomial 0x1021. + /// Input byte array. + /// Length of byte array. + public static void AddCCITT162(ref byte[] _in, uint length) + { + if (_in == null) + throw new NullReferenceException("_in"); + if (length > 2) + throw new ArgumentOutOfRangeException("length"); + + UShortUnion union = new UShortUnion(); + union.crc16 = 0; + + for (uint i = 0U; i < (length - 2U); i++) + union.crc16 = (ushort)((union.crc8.B0 << 8) ^ CCITT16_TABLE2[union.crc8.B1 ^ _in[i]]); + + union.crc16 = (ushort)(~union.crc16); + + _in[length - 1U] = union.crc8.B0; + _in[length - 2U] = union.crc8.B1; + } + + /// + /// Check 16-bit CRC-CCITT. + /// + /// This uses polynomial 0x1189. + /// Input byte array. + /// Length of byte array. + /// True, if CRC is valid, otherwise false. + public static bool CheckCCITT161(byte[] _in, uint length) + { + if (_in == null) + throw new NullReferenceException("_in"); + if (length > 2) + throw new ArgumentOutOfRangeException("length"); + + UShortUnion union = new UShortUnion(); + union.crc16 = 0xFFFF; + + for (uint i = 0U; i < (length - 2U); i++) + union.crc16 = (ushort)(union.crc8.B1 ^ CCITT16_TABLE1[union.crc8.B0 ^ _in[i]]); + + union.crc16 = (ushort)(~union.crc16); + + return union.crc8.B0 == _in[length - 1U] && union.crc8.B1 == _in[length - 2U]; + } + + /// + /// Encode 16-bit CRC-CCITT. + /// + /// This uses polynomial 0x1189. + /// Input byte array. + /// Length of byte array. + public static void AddCCITT161(ref byte[] _in, uint length) + { + if (_in == null) + throw new NullReferenceException("_in"); + if (length > 2) + throw new ArgumentOutOfRangeException("length"); + + UShortUnion union = new UShortUnion(); + union.crc16 = 0xFFFF; + + for (uint i = 0U; i < (length - 2U); i++) + union.crc16 = (ushort)(union.crc8.B1 ^ CCITT16_TABLE1[union.crc8.B0 ^ _in[i]]); + + union.crc16 = (ushort)(~union.crc16); + + _in[length - 2U] = union.crc8.B0; + _in[length - 1U] = union.crc8.B1; + } + + /// + /// Check 32-bit CRC. + /// + /// Input byte array. + /// Length of byte array. + /// True, if CRC is valid, otherwise false. + public static bool CheckCRC32(byte[] _in, uint length) + { + if (_in == null) + throw new NullReferenceException("_in"); + if (length > 4) + throw new ArgumentOutOfRangeException("length"); + + UIntUnion union = new UIntUnion(); + union.crc32 = 0; + + uint i = 0; + for (uint j = (length - 4U); j-- > 0; i++) { + uint idx = ((union.crc32 >> 24) ^ _in[i]) & 0xFFU; + union.crc32 = (CRC32_TABLE[idx] ^ (union.crc32 << 8)) & 0xFFFFFFFFU; + } + + union.crc32 = ~union.crc32; + union.crc32 &= 0xFFFFFFFFU; + + return union.crc8.B0 == _in[length - 1U] && union.crc8.B1 == _in[length - 2U] && union.crc8.B2 == _in[length - 3U] && union.crc8.B3 == _in[length - 4U]; + } + + /// + /// Encode 32-bit CRC. + /// + /// Input byte array. + /// Length of byte array. + public static void AddCRC32(ref byte[] _in, uint length) + { + if (_in == null) + throw new NullReferenceException("_in"); + if (length > 4) + throw new ArgumentOutOfRangeException("length"); + + UIntUnion union = new UIntUnion(); + union.crc32 = 0; + + uint i = 0; + for (uint j = (length - 4U); j-- > 0; i++) + { + uint idx = ((union.crc32 >> 24) ^ _in[i]) & 0xFFU; + union.crc32 = (CRC32_TABLE[idx] ^ (union.crc32 << 8)) & 0xFFFFFFFFU; + } + + union.crc32 = ~union.crc32; + union.crc32 &= 0xFFFFFFFFU; + + _in[length - 1U] = union.crc8.B0; + _in[length - 2U] = union.crc8.B1; + _in[length - 3U] = union.crc8.B2; + _in[length - 4U] = union.crc8.B3; + } + + /// + /// Generate 8-bit CRC. + /// + /// Input byte array. + /// Length of byte array. + /// Calculated 8-bit CRC value. + public static byte Crc8(byte[] _in, uint length) + { + if (_in == null) + throw new NullReferenceException("_in"); + + byte crc = 0; + + for (uint i = 0U; i < length; i++) + crc = CRC8_TABLE[crc ^ _in[i]]; + + return crc; + } + + /// + /// Generate 9-bit CRC. + /// + /// Input byte array. + /// Length of byte array in bits. + /// Calculated 9-bit CRC value. + public static ushort Crc9(byte[] _in, uint bitLength) + { + if (_in == null) + throw new NullReferenceException("_in"); + + ushort crc = 0; + + for (uint i = 0; i < bitLength; i++) { + bool b = FneUtils.ReadBit(_in, i); + if (b) { + if (i < 7U) { + crc ^= CRC9_TABLE[i]; + } else if (i > 15) { + crc ^= CRC9_TABLE[i - 9]; + } + } + } + + // crc = ~crc; + crc &= (ushort)0x1FFU; + crc ^= (ushort)0x1FFU; + + return crc; + } + + /// + /// + /// + /// Input byte array. + /// Length of byte array in bits. + /// + public static ushort CreateCRC16(byte[] _in, uint bitLength) + { + ushort crc = (ushort)0xFFFFU; + + for (uint i = 0U; i < bitLength; i++) { + bool bit1 = FneUtils.ReadBit(_in, i); + bool bit2 = (crc & 0x8000U) == 0x8000U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= (ushort)0x1021U; + } + + return (ushort)(crc & 0xFFFFU); + } + } // public sealed class CRC +} // namespace fnecore.EDAC diff --git a/EDAC/Golay2087.cs b/EDAC/Golay2087.cs new file mode 100644 index 0000000..a66d8c4 --- /dev/null +++ b/EDAC/Golay2087.cs @@ -0,0 +1,295 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.EDAC +{ + /// + /// Implements Golay (20,8,7) forward error correction. + /// + public sealed class Golay2087 + { + public static readonly uint[] ENCODING_TABLE_2087 = new uint[256] { + 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 }; + + public static readonly uint[] DECODING_TABLE_1987 = new uint[2048] { + 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 }; + + public const uint X18 = 0x00040000; /* vector representation of X^{18} */ + public const uint X11 = 0x00000800; /* vector representation of X^{11} */ + public const uint MASK8 = 0xfffff800; /* auxiliary vector for testing */ + public const uint GENPOL = 0x00000c75; /* generator polinomial, g(x) */ + + /* + ** Methods + */ + /// + /// Decode Golay (20,8,7) FEC. + /// + /// Golay FEC encoded data byte array + /// + public byte Decode(byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + + uint code = (uint)((data[0U] << 11) + (data[1U] << 3) + (data[2U] >> 5)); + uint syndrome = GetSyndrome1987(code); + uint error_pattern = DECODING_TABLE_1987[syndrome]; + + if (error_pattern != 0x00U) + code ^= error_pattern; + + return (byte)(code >> 11); + } + + /// + /// Encode Golay (20,8,7) FEC. + /// + /// Data to encode with Golay FEC. + public void Encode(ref byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + + uint value = data[0U]; + + uint cksum = ENCODING_TABLE_2087[value]; + + data[1U] = (byte)(cksum & 0xFFU); + data[2U] = (byte)(cksum >> 8); + } + + /// + /// + /// + /// + /// 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. + /// + /// + /// + private uint GetSyndrome1987(uint pattern) + { + uint aux = X18; + + if (pattern >= X11) { + while ((pattern & MASK8) > 0) { + while (!((aux & pattern) > 0)) + aux = aux >> 1; + + pattern ^= (aux / X11) * GENPOL; + } + } + + return pattern; + } + } // public sealed class Golay2087 +} // namespace fnecore.EDAC diff --git a/EDAC/Hamming.cs b/EDAC/Hamming.cs new file mode 100644 index 0000000..572d2ce --- /dev/null +++ b/EDAC/Hamming.cs @@ -0,0 +1,444 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.EDAC +{ + /// + /// Implements Hamming (15,11,3), (13,9,3), (10,6,3), (16,11,4) and + // (17,12,3) forward error correction. + /// + public sealed class Hamming + { + /* + ** Methods + */ + + /// + /// Decode Hamming (15,11,3). + /// + /// Boolean bit array. + /// + /// True, if bit errors are detected, otherwise false. + public static bool decode15113_1(bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the parity it should have + bool c0 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[6 + offset]; + bool c1 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[7 + offset] ^ d[8 + offset] ^ d[9 + offset]; + bool c2 = d[0 + offset] ^ d[1 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset] ^ d[10 + offset]; + bool c3 = d[0 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[9 + offset] ^ d[10 + offset]; + + byte n = 0; + n |= (byte)((c0 != d[11 + offset]) ? 0x01U : 0x00U); + n |= (byte)((c1 != d[12 + offset]) ? 0x02U : 0x00U); + n |= (byte)((c2 != d[13 + offset]) ? 0x04U : 0x00U); + n |= (byte)((c3 != d[14 + offset]) ? 0x08U : 0x00U); + + switch (n) + { + // Parity bit errors + case 0x01: d[11 + offset] = !d[11 + offset]; return true; + case 0x02: d[12 + offset] = !d[12 + offset]; return true; + case 0x04: d[13 + offset] = !d[13 + offset]; return true; + case 0x08: d[14 + offset] = !d[14 + offset]; return true; + + // Data bit errors + case 0x0F: d[0 + offset] = !d[0 + offset]; return true; + case 0x07: d[1 + offset] = !d[1 + offset]; return true; + case 0x0B: d[2 + offset] = !d[2 + offset]; return true; + case 0x03: d[3 + offset] = !d[3 + offset]; return true; + case 0x0D: d[4 + offset] = !d[4 + offset]; return true; + case 0x05: d[5 + offset] = !d[5 + offset]; return true; + case 0x09: d[6 + offset] = !d[6 + offset]; return true; + case 0x0E: d[7 + offset] = !d[7 + offset]; return true; + case 0x06: d[8 + offset] = !d[8 + offset]; return true; + case 0x0A: d[9 + offset] = !d[9 + offset]; return true; + case 0x0C: d[10 + offset] = !d[10 + offset]; return true; + + // No bit errors + default: return false; + } + } + + /// + /// Encode Hamming (15,11,3). + /// + /// Boolean bit array. + /// + public static void encode15113_1(ref bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this row should have + d[11 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[6 + offset]; + d[12 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[7 + offset] ^ d[8 + offset] ^ d[9 + offset]; + d[13 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset] ^ d[10 + offset]; + d[14 + offset] = d[0 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[9 + offset] ^ d[10 + offset]; + } + + /// + /// Decode Hamming (15,11,3). + /// + /// Boolean bit array. + /// + /// True, if bit errors are detected, otherwise false. + public static bool decode15113_2(bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this row should have + bool c0 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset]; + bool c1 = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[9 + offset]; + bool c2 = d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[9 + offset] ^ d[10 + offset]; + bool c3 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[10 + offset]; + + byte n = 0x00; + n |= (byte)((c0 != d[11 + offset]) ? 0x01U : 0x00U); + n |= (byte)((c1 != d[12 + offset]) ? 0x02U : 0x00U); + n |= (byte)((c2 != d[13 + offset]) ? 0x04U : 0x00U); + n |= (byte)((c3 != d[14 + offset]) ? 0x08U : 0x00U); + + switch (n) + { + // Parity bit errors + case 0x01: d[11 + offset] = !d[11 + offset]; return true; + case 0x02: d[12 + offset] = !d[12 + offset]; return true; + case 0x04: d[13 + offset] = !d[13 + offset]; return true; + case 0x08: d[14 + offset] = !d[14 + offset]; return true; + + // Data bit errors + case 0x09: d[0 + offset] = !d[0 + offset]; return true; + case 0x0B: d[1 + offset] = !d[1 + offset]; return true; + case 0x0F: d[2 + offset] = !d[2 + offset]; return true; + case 0x07: d[3 + offset] = !d[3 + offset]; return true; + case 0x0E: d[4 + offset] = !d[4 + offset]; return true; + case 0x05: d[5 + offset] = !d[5 + offset]; return true; + case 0x0A: d[6 + offset] = !d[6 + offset]; return true; + case 0x0D: d[7 + offset] = !d[7 + offset]; return true; + case 0x03: d[8 + offset] = !d[8 + offset]; return true; + case 0x06: d[9 + offset] = !d[9 + offset]; return true; + case 0x0C: d[10 + offset] = !d[10 + offset]; return true; + + // No bit errors + default: return false; + } + } + + /// + /// Encode Hamming (15,11,3). + /// + /// Boolean bit array. + /// + public static void encode15113_2(ref bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this row should have + d[11 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset]; + d[12 + offset] = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[9 + offset]; + d[13 + offset] = d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[9 + offset] ^ d[10 + offset]; + d[14 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[10 + offset]; + } + + /// + /// Decode Hamming (13,9,3). + /// + /// Boolean bit array. + /// True, if bit errors are detected, otherwise false. + public static bool decode1393(bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this column should have + bool c0 = d[0 + offset] ^ d[1 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[6 + offset]; + bool c1 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset]; + bool c2 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset]; + bool c3 = d[0 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[8 + offset]; + + byte n = 0x00; + n |= (byte)((c0 != d[9 + offset]) ? 0x01U : 0x00U); + n |= (byte)((c1 != d[10 + offset]) ? 0x02U : 0x00U); + n |= (byte)((c2 != d[11 + offset]) ? 0x04U : 0x00U); + n |= (byte)((c3 != d[12 + offset]) ? 0x08U : 0x00U); + + switch (n) + { + // Parity bit errors + case 0x01: d[9 + offset] = !d[9 + offset]; return true; + case 0x02: d[10 + offset] = !d[10 + offset]; return true; + case 0x04: d[11 + offset] = !d[11 + offset]; return true; + case 0x08: d[12 + offset] = !d[12 + offset]; return true; + + // Data bit erros + case 0x0F: d[0 + offset] = !d[0 + offset]; return true; + case 0x07: d[1 + offset] = !d[1 + offset]; return true; + case 0x0E: d[2 + offset] = !d[2 + offset]; return true; + case 0x05: d[3 + offset] = !d[3 + offset]; return true; + case 0x0A: d[4 + offset] = !d[4 + offset]; return true; + case 0x0D: d[5 + offset] = !d[5 + offset]; return true; + case 0x03: d[6 + offset] = !d[6 + offset]; return true; + case 0x06: d[7 + offset] = !d[7 + offset]; return true; + case 0x0C: d[8 + offset] = !d[8 + offset]; return true; + + // No bit errors + default: return false; + } + } + + /// + /// Encode Hamming (13,9,3). + /// + /// Boolean bit array. + /// + public static void encode1393(ref bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this column should have + d[9 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[6 + offset]; + d[10 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset]; + d[11 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset]; + d[12 + offset] = d[0 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[8 + offset]; + } + + /// + /// Decode Hamming (10,6,3). + /// + /// Boolean bit array. + /// + /// True, if bit errors are detected, otherwise false. + public static bool decode1063(bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this column should have + bool c0 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[5 + offset]; + bool c1 = d[0 + offset] ^ d[1 + offset] ^ d[3 + offset] ^ d[5 + offset]; + bool c2 = d[0 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset]; + bool c3 = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset]; + + byte n = 0x00; + n |= (byte)((c0 != d[6 + offset]) ? 0x01U : 0x00U); + n |= (byte)((c1 != d[7 + offset]) ? 0x02U : 0x00U); + n |= (byte)((c2 != d[8 + offset]) ? 0x04U : 0x00U); + n |= (byte)((c3 != d[9 + offset]) ? 0x08U : 0x00U); + + switch (n) + { + // Parity bit errors + case 0x01: d[6 + offset] = !d[6 + offset]; return true; + case 0x02: d[7 + offset] = !d[7 + offset]; return true; + case 0x04: d[8 + offset] = !d[8 + offset]; return true; + case 0x08: d[9 + offset] = !d[9 + offset]; return true; + + // Data bit erros + case 0x07: d[0 + offset] = !d[0 + offset]; return true; + case 0x0B: d[1 + offset] = !d[1 + offset]; return true; + case 0x0D: d[2 + offset] = !d[2 + offset]; return true; + case 0x0E: d[3 + offset] = !d[3 + offset]; return true; + case 0x0C: d[4 + offset] = !d[4 + offset]; return true; + case 0x03: d[5 + offset] = !d[5 + offset]; return true; + + // No bit errors + default: return false; + } + } + + /// + /// Encode Hamming (10,6,3). + /// + /// Boolean bit array. + /// + public static void encode1063(ref bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this column should have + d[6 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[5 + offset]; + d[7 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[3 + offset] ^ d[5 + offset]; + d[8 + offset] = d[0 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset]; + d[9 + offset] = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset]; + } + + /// + /// 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. + public static bool decode16114(bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this column should have + bool c0 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset]; + bool c1 = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[9 + offset]; + bool c2 = d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[9 + offset] ^ d[10 + offset]; + bool c3 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[10 + offset]; + bool c4 = d[0 + offset] ^ d[2 + offset] ^ d[5 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[9 + offset] ^ d[10 + offset]; + + // Compare these with the actual bits + byte n = 0x00; + n |= (byte)((c0 != d[11 + offset]) ? 0x01U : 0x00U); + n |= (byte)((c1 != d[12 + offset]) ? 0x02U : 0x00U); + n |= (byte)((c2 != d[13 + offset]) ? 0x04U : 0x00U); + n |= (byte)((c3 != d[14 + offset]) ? 0x08U : 0x00U); + n |= (byte)((c4 != d[15 + offset]) ? 0x10U : 0x00U); + + switch (n) + { + // Parity bit errors + case 0x01: d[11 + offset] = !d[11 + offset]; return true; + case 0x02: d[12 + offset] = !d[12 + offset]; return true; + case 0x04: d[13 + offset] = !d[13 + offset]; return true; + case 0x08: d[14 + offset] = !d[14 + offset]; return true; + case 0x10: d[15 + offset] = !d[15 + offset]; return true; + + // Data bit errors + case 0x19: d[0 + offset] = !d[0 + offset]; return true; + case 0x0B: d[1 + offset] = !d[1 + offset]; return true; + case 0x1F: d[2 + offset] = !d[2 + offset]; return true; + case 0x07: d[3 + offset] = !d[3 + offset]; return true; + case 0x0E: d[4 + offset] = !d[4 + offset]; return true; + case 0x15: d[5 + offset] = !d[5 + offset]; return true; + case 0x1A: d[6 + offset] = !d[6 + offset]; return true; + case 0x0D: d[7 + offset] = !d[7 + offset]; return true; + case 0x13: d[8 + offset] = !d[8 + offset]; return true; + case 0x16: d[9 + offset] = !d[9 + offset]; return true; + case 0x1C: d[10 + offset] = !d[10 + offset]; return true; + + // No bit errors + case 0x00: return true; + + // Unrecoverable errors + default: return false; + } + } + + /// + /// Encode Hamming (10,6,3). + /// + /// Boolean bit array. + /// + public static void encode16114(ref bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + d[11 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[8 + offset]; + d[12 + offset] = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[9 + offset]; + d[13 + offset] = d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[9 + offset] ^ d[10 + offset]; + d[14 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[4 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[10 + offset]; + d[15 + offset] = d[0 + offset] ^ d[2 + offset] ^ d[5 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[9 + offset] ^ d[10 + offset]; + } + + /// + /// 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. + public static bool decode17123(bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + // Calculate the checksum this column should have + bool c0 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[9 + offset]; + bool c1 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[7 + offset] ^ d[8 + offset] ^ d[10 + offset]; + bool c2 = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[8 + offset] ^ d[9 + offset] ^ d[11 + offset]; + bool c3 = d[0 + offset] ^ d[1 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[10 + offset]; + bool c4 = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[5 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[11 + offset]; + + // Compare these with the actual bits + byte n = 0x00; + n |= (byte)((c0 != d[12 + offset]) ? 0x01U : 0x00U); + n |= (byte)((c1 != d[13 + offset]) ? 0x02U : 0x00U); + n |= (byte)((c2 != d[14 + offset]) ? 0x04U : 0x00U); + n |= (byte)((c3 != d[15 + offset]) ? 0x08U : 0x00U); + n |= (byte)((c4 != d[16 + offset]) ? 0x10U : 0x00U); + + switch (n) + { + // Parity bit errors + case 0x01: d[12 + offset] = !d[12 + offset]; return true; + case 0x02: d[13 + offset] = !d[13 + offset]; return true; + case 0x04: d[14 + offset] = !d[14 + offset]; return true; + case 0x08: d[15 + offset] = !d[15 + offset]; return true; + case 0x10: d[16 + offset] = !d[16 + offset]; return true; + + // Data bit errors + case 0x1B: d[0 + offset] = !d[0 + offset]; return true; + case 0x1F: d[1 + offset] = !d[1 + offset]; return true; + case 0x17: d[2 + offset] = !d[2 + offset]; return true; + case 0x07: d[3 + offset] = !d[3 + offset]; return true; + case 0x0E: d[4 + offset] = !d[4 + offset]; return true; + case 0x1C: d[5 + offset] = !d[5 + offset]; return true; + case 0x11: d[6 + offset] = !d[6 + offset]; return true; + case 0x0B: d[7 + offset] = !d[7 + offset]; return true; + case 0x16: d[8 + offset] = !d[8 + offset]; return true; + case 0x05: d[9 + offset] = !d[9 + offset]; return true; + case 0x0A: d[10 + offset] = !d[10 + offset]; return true; + case 0x14: d[11 + offset] = !d[11 + offset]; return true; + + // No bit errors + case 0x00: return true; + + // Unrecoverable errors + default: return false; + } + } + + /// + /// Encode Hamming (17,12,3). + /// + /// Boolean bit array. + /// + public static void encode17123(ref bool[] d, int offset = 0) + { + if (d == null) + throw new NullReferenceException("d"); + + d[12 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[6 + offset] ^ d[7 + offset] ^ d[9 + offset]; + d[13 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[7 + offset] ^ d[8 + offset] ^ d[10 + offset]; + d[14 + offset] = d[1 + offset] ^ d[2 + offset] ^ d[3 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[8 + offset] ^ d[9 + offset] ^ d[11 + offset]; + d[15 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[4 + offset] ^ d[5 + offset] ^ d[7 + offset] ^ d[10 + offset]; + d[16 + offset] = d[0 + offset] ^ d[1 + offset] ^ d[2 + offset] ^ d[5 + offset] ^ d[6 + offset] ^ d[8 + offset] ^ d[11 + offset]; + } + } // public sealed class Hamming +} // namespace fnecore.EDAC diff --git a/EDAC/QR1676.cs b/EDAC/QR1676.cs new file mode 100644 index 0000000..9997654 --- /dev/null +++ b/EDAC/QR1676.cs @@ -0,0 +1,149 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.EDAC +{ + /// + /// Implements Quadratic residue (16,7,6) forward error correction. + /// + public sealed class QR1676 + { + private static readonly uint[] ENCODING_TABLE_1676 = new uint[128] { + 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 }; + + private static readonly uint[] DECODING_TABLE_1576 = new uint[256] { + 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 }; + + private const uint X14 = 0x00004000; /* vector representation of X^{14} */ + private const uint X8 = 0x00000100; /* vector representation of X^{8} */ + private const uint MASK7 = 0xffffff00; /* auxiliary vector for testing */ + private const uint GENPOL = 0x00000139; /* generator polinomial, g(x) */ + + /* + ** Methods + */ + + /// + /// Decode QR (16,7,6) FEC. + /// + /// + /// + public static byte Decode(byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + + uint code = (uint)((data[0U] << 7) + (data[1U] >> 1)); + uint syndrome = GetSyndrome1576(code); + uint error_pattern = DECODING_TABLE_1576[syndrome]; + + code ^= error_pattern; + + return (byte)(code >> 7); + } + + /// + /// Encode QR (16,7,6) FEC. + /// + /// Compute the EMB against a precomputed list of correct words. + /// + public static void Encode(ref byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + + uint value = (uint)((data[0U] >> 1) & 0x7FU); + uint cksum = ENCODING_TABLE_1676[value]; + + data[0U] = (byte)(cksum >> 8); + data[1U] = (byte)(cksum & 0xFFU); + } + + /// + /// + /// + /// + /// 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. + /// + /// + /// + private static uint GetSyndrome1576(uint pattern) + { + uint aux = X14; + if (pattern >= X8) + { + while ((pattern & MASK7) > 0) + { + while (!((aux & pattern) > 0)) + aux = aux >> 1; + + pattern ^= (aux / X8) * GENPOL; + } + } + + return pattern; + } + } // public sealed class QR1676 +} // namespace fnecore.EDAC diff --git a/EDAC/RS129.cs b/EDAC/RS129.cs new file mode 100644 index 0000000..4b86de2 --- /dev/null +++ b/EDAC/RS129.cs @@ -0,0 +1,166 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.EDAC +{ + /// + /// Implements Reed-Solomon (12,9) forward error correction. + /// + public sealed class RS129 + { + private const uint NPAR = 3U; + + /* Maximum degree of various polynomials. */ + private const uint MAXDEG = NPAR * 2U; + + /* Generator Polynomial */ + private static readonly byte[] POLY = new byte[12] { 64, 56, 14, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; + + private static readonly byte[] EXP_TABLE = new byte[512] { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1D, 0x3A, 0x74, 0xE8, 0xCD, 0x87, 0x13, 0x26, + 0x4C, 0x98, 0x2D, 0x5A, 0xB4, 0x75, 0xEA, 0xC9, 0x8F, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, + 0x9D, 0x27, 0x4E, 0x9C, 0x25, 0x4A, 0x94, 0x35, 0x6A, 0xD4, 0xB5, 0x77, 0xEE, 0xC1, 0x9F, 0x23, + 0x46, 0x8C, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x5D, 0xBA, 0x69, 0xD2, 0xB9, 0x6F, 0xDE, 0xA1, + 0x5F, 0xBE, 0x61, 0xC2, 0x99, 0x2F, 0x5E, 0xBC, 0x65, 0xCA, 0x89, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, + 0xFD, 0xE7, 0xD3, 0xBB, 0x6B, 0xD6, 0xB1, 0x7F, 0xFE, 0xE1, 0xDF, 0xA3, 0x5B, 0xB6, 0x71, 0xE2, + 0xD9, 0xAF, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0D, 0x1A, 0x34, 0x68, 0xD0, 0xBD, 0x67, 0xCE, + 0x81, 0x1F, 0x3E, 0x7C, 0xF8, 0xED, 0xC7, 0x93, 0x3B, 0x76, 0xEC, 0xC5, 0x97, 0x33, 0x66, 0xCC, + 0x85, 0x17, 0x2E, 0x5C, 0xB8, 0x6D, 0xDA, 0xA9, 0x4F, 0x9E, 0x21, 0x42, 0x84, 0x15, 0x2A, 0x54, + 0xA8, 0x4D, 0x9A, 0x29, 0x52, 0xA4, 0x55, 0xAA, 0x49, 0x92, 0x39, 0x72, 0xE4, 0xD5, 0xB7, 0x73, + 0xE6, 0xD1, 0xBF, 0x63, 0xC6, 0x91, 0x3F, 0x7E, 0xFC, 0xE5, 0xD7, 0xB3, 0x7B, 0xF6, 0xF1, 0xFF, + 0xE3, 0xDB, 0xAB, 0x4B, 0x96, 0x31, 0x62, 0xC4, 0x95, 0x37, 0x6E, 0xDC, 0xA5, 0x57, 0xAE, 0x41, + 0x82, 0x19, 0x32, 0x64, 0xC8, 0x8D, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xDD, 0xA7, 0x53, 0xA6, + 0x51, 0xA2, 0x59, 0xB2, 0x79, 0xF2, 0xF9, 0xEF, 0xC3, 0x9B, 0x2B, 0x56, 0xAC, 0x45, 0x8A, 0x09, + 0x12, 0x24, 0x48, 0x90, 0x3D, 0x7A, 0xF4, 0xF5, 0xF7, 0xF3, 0xFB, 0xEB, 0xCB, 0x8B, 0x0B, 0x16, + 0x2C, 0x58, 0xB0, 0x7D, 0xFA, 0xE9, 0xCF, 0x83, 0x1B, 0x36, 0x6C, 0xD8, 0xAD, 0x47, 0x8E, 0x01, + 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1D, 0x3A, 0x74, 0xE8, 0xCD, 0x87, 0x13, 0x26, 0x4C, + 0x98, 0x2D, 0x5A, 0xB4, 0x75, 0xEA, 0xC9, 0x8F, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x9D, + 0x27, 0x4E, 0x9C, 0x25, 0x4A, 0x94, 0x35, 0x6A, 0xD4, 0xB5, 0x77, 0xEE, 0xC1, 0x9F, 0x23, 0x46, + 0x8C, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x5D, 0xBA, 0x69, 0xD2, 0xB9, 0x6F, 0xDE, 0xA1, 0x5F, + 0xBE, 0x61, 0xC2, 0x99, 0x2F, 0x5E, 0xBC, 0x65, 0xCA, 0x89, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, 0xFD, + 0xE7, 0xD3, 0xBB, 0x6B, 0xD6, 0xB1, 0x7F, 0xFE, 0xE1, 0xDF, 0xA3, 0x5B, 0xB6, 0x71, 0xE2, 0xD9, + 0xAF, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0D, 0x1A, 0x34, 0x68, 0xD0, 0xBD, 0x67, 0xCE, 0x81, + 0x1F, 0x3E, 0x7C, 0xF8, 0xED, 0xC7, 0x93, 0x3B, 0x76, 0xEC, 0xC5, 0x97, 0x33, 0x66, 0xCC, 0x85, + 0x17, 0x2E, 0x5C, 0xB8, 0x6D, 0xDA, 0xA9, 0x4F, 0x9E, 0x21, 0x42, 0x84, 0x15, 0x2A, 0x54, 0xA8, + 0x4D, 0x9A, 0x29, 0x52, 0xA4, 0x55, 0xAA, 0x49, 0x92, 0x39, 0x72, 0xE4, 0xD5, 0xB7, 0x73, 0xE6, + 0xD1, 0xBF, 0x63, 0xC6, 0x91, 0x3F, 0x7E, 0xFC, 0xE5, 0xD7, 0xB3, 0x7B, 0xF6, 0xF1, 0xFF, 0xE3, + 0xDB, 0xAB, 0x4B, 0x96, 0x31, 0x62, 0xC4, 0x95, 0x37, 0x6E, 0xDC, 0xA5, 0x57, 0xAE, 0x41, 0x82, + 0x19, 0x32, 0x64, 0xC8, 0x8D, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xDD, 0xA7, 0x53, 0xA6, 0x51, + 0xA2, 0x59, 0xB2, 0x79, 0xF2, 0xF9, 0xEF, 0xC3, 0x9B, 0x2B, 0x56, 0xAC, 0x45, 0x8A, 0x09, 0x12, + 0x24, 0x48, 0x90, 0x3D, 0x7A, 0xF4, 0xF5, 0xF7, 0xF3, 0xFB, 0xEB, 0xCB, 0x8B, 0x0B, 0x16, 0x2C, + 0x58, 0xB0, 0x7D, 0xFA, 0xE9, 0xCF, 0x83, 0x1B, 0x36, 0x6C, 0xD8, 0xAD, 0x47, 0x8E, 0x01, 0x00 }; + + private static readonly byte[] LOG_TABLE = new byte[256] { + 0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1A, 0xC6, 0x03, 0xDF, 0x33, 0xEE, 0x1B, 0x68, 0xC7, 0x4B, + 0x04, 0x64, 0xE0, 0x0E, 0x34, 0x8D, 0xEF, 0x81, 0x1C, 0xC1, 0x69, 0xF8, 0xC8, 0x08, 0x4C, 0x71, + 0x05, 0x8A, 0x65, 0x2F, 0xE1, 0x24, 0x0F, 0x21, 0x35, 0x93, 0x8E, 0xDA, 0xF0, 0x12, 0x82, 0x45, + 0x1D, 0xB5, 0xC2, 0x7D, 0x6A, 0x27, 0xF9, 0xB9, 0xC9, 0x9A, 0x09, 0x78, 0x4D, 0xE4, 0x72, 0xA6, + 0x06, 0xBF, 0x8B, 0x62, 0x66, 0xDD, 0x30, 0xFD, 0xE2, 0x98, 0x25, 0xB3, 0x10, 0x91, 0x22, 0x88, + 0x36, 0xD0, 0x94, 0xCE, 0x8F, 0x96, 0xDB, 0xBD, 0xF1, 0xD2, 0x13, 0x5C, 0x83, 0x38, 0x46, 0x40, + 0x1E, 0x42, 0xB6, 0xA3, 0xC3, 0x48, 0x7E, 0x6E, 0x6B, 0x3A, 0x28, 0x54, 0xFA, 0x85, 0xBA, 0x3D, + 0xCA, 0x5E, 0x9B, 0x9F, 0x0A, 0x15, 0x79, 0x2B, 0x4E, 0xD4, 0xE5, 0xAC, 0x73, 0xF3, 0xA7, 0x57, + 0x07, 0x70, 0xC0, 0xF7, 0x8C, 0x80, 0x63, 0x0D, 0x67, 0x4A, 0xDE, 0xED, 0x31, 0xC5, 0xFE, 0x18, + 0xE3, 0xA5, 0x99, 0x77, 0x26, 0xB8, 0xB4, 0x7C, 0x11, 0x44, 0x92, 0xD9, 0x23, 0x20, 0x89, 0x2E, + 0x37, 0x3F, 0xD1, 0x5B, 0x95, 0xBC, 0xCF, 0xCD, 0x90, 0x87, 0x97, 0xB2, 0xDC, 0xFC, 0xBE, 0x61, + 0xF2, 0x56, 0xD3, 0xAB, 0x14, 0x2A, 0x5D, 0x9E, 0x84, 0x3C, 0x39, 0x53, 0x47, 0x6D, 0x41, 0xA2, + 0x1F, 0x2D, 0x43, 0xD8, 0xB7, 0x7B, 0xA4, 0x76, 0xC4, 0x17, 0x49, 0xEC, 0x7F, 0x0C, 0x6F, 0xF6, + 0x6C, 0xA1, 0x3B, 0x52, 0x29, 0x9D, 0x55, 0xAA, 0xFB, 0x60, 0x86, 0xB1, 0xBB, 0xCC, 0x3E, 0x5A, + 0xCB, 0x59, 0x5F, 0xB0, 0x9C, 0xA9, 0xA0, 0x51, 0x0B, 0xF5, 0x16, 0xEB, 0x7A, 0x75, 0x2C, 0xD7, + 0x4F, 0xAE, 0xD5, 0xE9, 0xE6, 0xE7, 0xAD, 0xE8, 0x74, 0xD6, 0xF4, 0xEA, 0xA8, 0x50, 0x58, 0xAF }; + + /* + ** Methods + */ + + /// + /// Check RS (12,9) FEC. + /// + /// + /// + public static bool Check(byte[] _in) + { + if (_in == null) + throw new NullReferenceException("_in"); + + byte[] parity = new byte[4]; + Encode(_in, 9, ref parity); + + return _in[9] == parity[2] && _in[10] == parity[1] && _in[11] == parity[0]; + } + + /// + /// 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. + /// + /// + /// + /// + public static void Encode(byte[] msg, uint nbytes, ref byte[] parity) + { + if (msg == null) + throw new NullReferenceException("msg"); + if (parity == null) + throw new NullReferenceException("parity"); + + for (uint i = 0U; i < NPAR + 1U; i++) + parity[i] = 0x00; + + for (uint i = 0U; i 0; j--) + parity[j] = (byte)(parity[j - 1] ^ gmult(POLY[j], dbyte)); + + parity[0] = gmult(POLY[0], dbyte); + } + } + + /// + /// + /// + /// Multiplication using logarithms. + /// + /// + /// + private static byte gmult(byte a, byte b) + { + if (a == 0U || b == 0U) + return 0; + + uint i = LOG_TABLE[a]; + uint j = LOG_TABLE[b]; + + return EXP_TABLE[i + j]; + } + } // public sealed class RS129 +} // namespace fnecore.EDAC diff --git a/FneBase.cs b/FneBase.cs new file mode 100644 index 0000000..001ec38 --- /dev/null +++ b/FneBase.cs @@ -0,0 +1,915 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022-2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; +using System.Net; +using System.Security.Cryptography; + +using fnecore.DMR; +using fnecore.P25; +using fnecore.NXDN; +using fnecore.EDAC; + +namespace fnecore +{ + /// + /// Structure containing detailed information about a connected peer. + /// + public class PeerDetails + { + /// + /// Identity + /// + public string Identity; + /// + /// Receive Frequency + /// + public uint RxFrequency; + /// + /// Transmit Frequency + /// + public uint TxFrequency; + + /// + /// Software Identifier + /// + public string Software; + + /* + ** System Information + */ + /// + /// Latitude + /// + public double Latitude; + /// + /// Longitude + /// + public double Longitude; + /// + /// Height + /// + public int Height; + /// + /// Location + /// + public string Location; + + /* + ** Channel Data + */ + /// + /// Transmit Offset (Mhz) + /// + public float TxOffsetMhz; + /// + /// Channel Bandwidth (Khz) + /// + public float ChBandwidthKhz; + /// + /// Channel ID + /// + public byte ChannelID; + /// + /// Channel Number + /// + public uint ChannelNo; + /// + /// Transmit Power + /// + public uint TxPower; + + /* + ** REST API + */ + /// + /// REST API Password + /// + public string Password; + /// + /// REST API Port + /// + public int Port; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public PeerDetails() + { + /* stub */ + } + } // public class PeerDetails + + /// + /// Structure containing information about a connected peer. + /// + public class PeerInformation + { + /// + /// Peer ID + /// + public uint PeerID; + + /// + /// Stream ID + /// + public uint StreamID; + + /// + /// RTP Packet Sequence + /// + public ushort PacketSequence; + /// + /// Next expected RTP Packet Sequence + /// + public ushort NextPacketSequence; + + /// + /// Peer IP EndPoint + /// + public IPEndPoint EndPoint; + + /// + /// Salt value used for authentication. + /// + public uint Salt; + + /// + /// Connection State + /// + public ConnectionState State; + + /// + /// Flag indicating peer is "connected". + /// + public bool Connection; + + /// + /// Number of pings received. + /// + public int PingsReceived; + /// + /// Date/Time of last ping. + /// + public DateTime LastPing; + + /// + /// Peer Details Structure + /// + public PeerDetails Details = null; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public PeerInformation() + { + Details = new PeerDetails(); + } + } // public class PeerInformation + + /// + /// Callback used to validate incoming DMR data. + /// + /// Peer ID + /// Source Address + /// Destination Address + /// Slot Number + /// Call Type (Group or Private) + /// Frame Type + /// DMR Data Type + /// Stream ID + /// Raw message data + /// True, if data stream is valid, otherwise false. + public delegate bool DMRDataValidate(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId, byte[] message); + /// + /// Event used to process incoming DMR data. + /// + public class DMRDataReceivedEvent : EventArgs + { + /// + /// Peer ID + /// + public uint PeerId { get; } + /// + /// Source Address + /// + public uint SrcId { get; } + /// + /// Destination Address + /// + public uint DstId { get; } + /// + /// Slot Number + /// + public byte Slot { get; } + /// + /// Call Type (Group or Private) + /// + public CallType CallType { get; } + /// + /// Frame Type + /// + public FrameType FrameType { get; } + /// + /// DMR Data Type + /// + public DMRDataType DataType { get; } + /// + /// + /// + public byte n { get; } + /// + /// RTP Packet Sequence + /// + public ushort PacketSequence { get; } + /// + /// Stream ID + /// + public uint StreamId { get; } + /// + /// Raw message data + /// + public byte[] Data { get; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + private DMRDataReceivedEvent() + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// Peer ID + /// Source Address + /// Destination Address + /// Slot Number + /// Call Type (Group or Private) + /// Frame Type + /// DMR Data Type + /// + /// RTP Packet Sequence + /// Stream ID + /// Raw message data + public DMRDataReceivedEvent(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, byte n, ushort pktSeq, uint streamId, byte[] data) : base() + { + this.PeerId = peerId; + this.SrcId = srcId; + this.DstId = dstId; + this.Slot = slot; + this.CallType = callType; + this.FrameType = frameType; + this.DataType = dataType; + this.n = n; + this.PacketSequence = pktSeq; + this.StreamId = streamId; + + this.Data = new byte[data.Length]; + Buffer.BlockCopy(data, 0, Data, 0, data.Length); + } + } // public class DMRDataReceivedEvent : EventArgs + + /// + /// Callback used to validate incoming P25 data. + /// + /// Peer ID + /// Source Address + /// Destination Address + /// Call Type (Group or Private) + /// P25 DUID + /// Frame Type + /// Stream ID + /// Raw message data + /// True, if data stream is valid, otherwise false. + public delegate bool P25DataValidate(uint peerId, uint srcId, uint dstId, CallType callType, P25DUID duid, FrameType frameType, uint streamId, byte[] message); + /// + /// Event used to process incoming P25 data. + /// + public class P25DataReceivedEvent : EventArgs + { + /// + /// Peer ID + /// + public uint PeerId { get; } + /// + /// Source Address + /// + public uint SrcId { get; } + /// + /// Destination Address + /// + public uint DstId { get; } + /// + /// Call Type (Group or Private) + /// + public CallType CallType { get; } + /// + /// P25 DUID + /// + public P25DUID DUID { get; } + /// + /// Frame Type + /// + public FrameType FrameType { get; } + /// + /// RTP Packet Sequence + /// + public ushort PacketSequence { get; } + /// + /// Stream ID + /// + public uint StreamId { get; } + /// + /// Raw message data + /// + public byte[] Data { get; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + private P25DataReceivedEvent() + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// Peer ID + /// Source Address + /// Destination Address + /// Call Type (Group or Private) + /// P25 DUID + /// Frame Type + /// RTP Packet Sequence + /// Stream ID + /// Raw message data + public P25DataReceivedEvent(uint peerId, uint srcId, uint dstId, CallType callType, P25DUID duid, FrameType frameType, ushort pktSeq, uint streamId, byte[] data) : base() + { + this.PeerId = peerId; + this.SrcId = srcId; + this.DstId = dstId; + this.CallType = callType; + this.DUID = duid; + this.FrameType = frameType; + this.PacketSequence = pktSeq; + this.StreamId = streamId; + + this.Data = new byte[data.Length]; + Buffer.BlockCopy(data, 0, Data, 0, data.Length); + } + } // public class P25DataReceivedEvent : EventArgs + + /// + /// Callback used to validate incoming NXDN data. + /// + /// Peer ID + /// Source Address + /// Destination Address + /// Call Type (Group or Private) + /// NXDN Message Type + /// Frame Type + /// Stream ID + /// Raw message data + /// True, if data stream is valid, otherwise false. + public delegate bool NXDNDataValidate(uint peerId, uint srcId, uint dstId, CallType callType, NXDNMessageType messageType, FrameType frameType, uint streamId, byte[] message); + /// + /// Event used to process incoming NXDN data. + /// + public class NXDNDataReceivedEvent : EventArgs + { + /// + /// Peer ID + /// + public uint PeerId { get; } + /// + /// Source Address + /// + public uint SrcId { get; } + /// + /// Destination Address + /// + public uint DstId { get; } + /// + /// Call Type (Group or Private) + /// + public CallType CallType { get; } + /// + /// NXDN Message Type + /// + public NXDNMessageType MessageType { get; } + /// + /// Frame Type + /// + public FrameType FrameType { get; } + /// + /// RTP Packet Sequence + /// + public ushort PacketSequence { get; } + /// + /// Stream ID + /// + public uint StreamId { get; } + /// + /// Raw message data + /// + public byte[] Data { get; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + private NXDNDataReceivedEvent() + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// Peer ID + /// Source Address + /// Destination Address + /// Call Type (Group or Private) + /// NXDN Message Type + /// Frame Type + /// RTP Packet Sequence + /// Stream ID + /// Raw message data + public NXDNDataReceivedEvent(uint peerId, uint srcId, uint dstId, CallType callType, NXDNMessageType messageType, FrameType frameType, ushort pktSeq, uint streamId, byte[] data) : base() + { + this.PeerId = peerId; + this.SrcId = srcId; + this.DstId = dstId; + this.CallType = callType; + this.MessageType = messageType; + this.FrameType = frameType; + this.PacketSequence = pktSeq; + this.StreamId = streamId; + + this.Data = new byte[data.Length]; + Buffer.BlockCopy(data, 0, Data, 0, data.Length); + } + } // public class NXDNDataReceivedEvent : EventArgs + + /// + /// Callback used to process whether or not a peer is being ignored for traffic. + /// + /// Peer ID + /// Source Address + /// Destination Address + /// Slot Number + /// Call Type (Group or Private) + /// Frame Type + /// DMR Data Type + /// Stream ID + /// True, if peer is ignored, otherwise false. + public delegate bool PeerIgnored(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId); + /// + /// Event when a peer connects. + /// + public class PeerConnectedEvent : EventArgs + { + /// + /// Peer ID + /// + public uint PeerId { get; } + /// + /// Peer Information + /// + public PeerInformation Information { get; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + /// Peer ID + /// Peer Information + public PeerConnectedEvent(uint peerId, PeerInformation peer) : base() + { + this.PeerId = peerId; + this.Information = peer; + } + } // public class PeerConnectedEvent : EventArgs + + /// + /// Type of FNE instance. + /// + public enum FneType : byte + { + /// + /// Master + /// + MASTER, + /// + /// Peer + /// + PEER, + /// + /// Unknown (should never happen) + /// + UNKNOWN = 0xFF + } // public enum FneType : byte + + + /// + /// This class implements some base functionality for all other FNE network classes. + /// + public abstract class FneBase + { + protected readonly string systemName = string.Empty; + protected readonly uint peerId = 0; + + protected static Random rand = null; + + protected bool isStarted = false; + protected FneType fneType; + + /* + ** Properties + */ + + /// + /// Gets the system name for this . + /// + public string SystemName => systemName; + + /// + /// Gets the peer ID for this . + /// + public uint PeerId => peerId; + + /// + /// Flag indicating whether this is running. + /// + public bool IsStarted => isStarted; + + /// + /// Gets the this is. + /// + public FneType FneType => fneType; + + /// + /// Gets/sets the interval that peers will need to ping the master. + /// + public int PingTime + { + get; + set; + } + + /// + /// Get/sets the current logging level of the instance. + /// + public LogLevel LogLevel + { + get; + set; + } + + /// + /// Get/sets a flag that enables dumping the raw recieved packets to the log. + /// + /// This will also require the be set to DEBUG. + public bool RawPacketTrace + { + get; + set; + } + + /* + ** Events/Callbacks + */ + + /// + /// Callback action that handles validating a DMR call stream. + /// + public DMRDataValidate DMRDataValidate = null; + /// + /// Event action that handles processing a DMR call stream. + /// + public event EventHandler DMRDataReceived; + + /// + /// Callback action that handles validating a P25 call stream. + /// + public P25DataValidate P25DataValidate = null; + /// + /// Event action that handles preprocessing a P25 call stream. + /// + public event EventHandler P25DataPreprocess; + /// + /// Event action that handles processing a P25 call stream. + /// + public event EventHandler P25DataReceived; + + /// + /// Callback action that handles validating a NXDN call stream. + /// + public NXDNDataValidate NXDNDataValidate = null; + /// + /// Event action that handles processing a NXDN call stream. + /// + public event EventHandler NXDNDataReceived; + + /// + /// Callback action that handles verifying if a peer is ignored for a call stream. + /// + public PeerIgnored PeerIgnored = null; + /// + /// Event action that handles when a peer connects. + /// + public event EventHandler PeerConnected; + /// + /// Callback action that handles when a peer disconnects. + /// + public Action PeerDisconnected = null; + + /// + /// Callback action that handles internal logging. + /// + public Action Logger; + + /* + ** Methods + */ + + /// + /// Static initializer for the class. + /// + static FneBase() + { + int seed = 0; + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + byte[] intBytes = new byte[4]; + rng.GetBytes(intBytes); + seed = BitConverter.ToInt32(intBytes, 0); + } + + rand = new Random(seed); + rand.Next(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + protected FneBase(string systemName, uint peerId) + { + this.systemName = systemName; + this.peerId = peerId; + + this.fneType = FneType.UNKNOWN; + + // set a default "noop" logger + Logger = (LogLevel level, string message) => { }; + } + + /// + /// Starts the main execution loop for this . + /// + public abstract void Start(); + + /// + /// Stops the main execution loop for this . + /// + public abstract void Stop(); + + /// + /// Helper to generate a new stream ID. + /// + /// + public static uint CreateStreamID() + { + return (uint)rand.Next(int.MinValue, int.MaxValue); + } + + /// + /// Helper to just quickly generate opcode tuples (mainly for brevity). + /// + /// Function + /// Sub-Function + /// + public static Tuple CreateOpcode(byte func, byte subFunc = Constants.NET_SUBFUNC_NOP) + { + return new Tuple(func, subFunc); + } + + /// + /// Helper to fire the logging action. + /// + /// + /// + protected void Log(LogLevel logLevel, string message) + { + byte level = (byte)logLevel; + if (level <= (byte)LogLevel) + Logger(logLevel, message); + } + + /// + /// Helper to read and process a FNE RTP frame. + /// + /// Raw UDP socket frame. + /// Length of payload message. + /// RTP Header. + /// RTP FNE Header. + protected byte[] ReadFrame(UdpFrame frame, out int messageLength, out RtpHeader rtpHeader, out RtpFNEHeader fneHeader) + { + int length = frame.Message.Length; + messageLength = -1; + rtpHeader = null; + fneHeader = null; + + // read message from socket + if (length > 0) + { + if (length < Constants.RtpHeaderLengthBytes + Constants.RtpExtensionHeaderLengthBytes) + { + Log(LogLevel.ERROR, $"Message received from network is malformed! " + + $"{Constants.RtpHeaderLengthBytes + Constants.RtpExtensionHeaderLengthBytes} bytes != {frame.Message.Length} bytes"); + return null; + } + + // decode RTP header + rtpHeader = new RtpHeader(); + if (!rtpHeader.Decode(frame.Message)) + { + Log(LogLevel.ERROR, $"Invalid RTP packet received from network"); + return null; + } + + // ensure the RTP header has extension header (otherwise abort) + if (!rtpHeader.Extension) + { + Log(LogLevel.ERROR, "Invalid RTP header received from network"); + return null; + } + + // ensure payload type is correct + if ((rtpHeader.PayloadType != Constants.DVMRtpPayloadType) && + (rtpHeader.PayloadType != Constants.DVMRtpControlPayloadType)) + { + Log(LogLevel.ERROR, "Invalid RTP payload type received from network"); + return null; + } + + // decode FNE RTP header + fneHeader = new RtpFNEHeader(); + if (!fneHeader.Decode(frame.Message)) + { + Log(LogLevel.ERROR, "Invalid RTP packet received from network"); + return null; + } + + // copy message + messageLength = (int)fneHeader.MessageLength; + byte[] message = new byte[messageLength]; + Buffer.BlockCopy(frame.Message, (int)(Constants.RtpHeaderLengthBytes + Constants.RtpExtensionHeaderLengthBytes + Constants.RtpFNEHeaderLengthBytes), + message, 0, messageLength); + + ushort calc = CRC.CreateCRC16(message, (uint)(messageLength * 8)); + if (calc != fneHeader.CRC) + { + Log(LogLevel.ERROR, "Failed CRC CCITT-162 check"); + messageLength = -1; + return null; + } + + return message; + } + + return null; + } + + /// + /// Helper to generate and write a FNE RTP frame. + /// + /// Payload message. + /// Peer ID. + /// Synchronization Source ID. + /// FNE Network Opcode. + /// RTP Packet Sequence. + /// Stream ID. + /// + protected byte[] WriteFrame(byte[] message, uint peerId, uint ssrc, Tuple opcode, ushort pktSeq, uint streamId) + { + byte[] buffer = new byte[message.Length + Constants.RtpHeaderLengthBytes + Constants.RtpExtensionHeaderLengthBytes + Constants.RtpFNEHeaderLengthBytes]; + FneUtils.Memset(buffer, 0, buffer.Length); + + RtpHeader header = new RtpHeader(); + header.Extension = true; + header.PayloadType = Constants.DVMRtpPayloadType; + header.Sequence = pktSeq; + header.SSRC = ssrc; + + header.Encode(ref buffer); + + RtpFNEHeader fneHeader = new RtpFNEHeader(); + fneHeader.CRC = CRC.CreateCRC16(message, (uint)(message.Length * 8)); + fneHeader.StreamID = streamId; + fneHeader.PeerID = peerId; + fneHeader.MessageLength = (uint)message.Length; + + fneHeader.Function = opcode.Item1; + fneHeader.SubFunction = opcode.Item2; + + fneHeader.Encode(ref buffer); + + Buffer.BlockCopy(message, 0, buffer, (int)(Constants.RtpHeaderLengthBytes + Constants.RtpExtensionHeaderLengthBytes + Constants.RtpFNEHeaderLengthBytes), + message.Length); + return buffer; + } + + /// + /// Helper to fire the DMR data received event. + /// + /// instance + protected void FireDMRDataReceived(DMRDataReceivedEvent e) + { + if (DMRDataReceived != null) + DMRDataReceived.Invoke(this, e); + } + + /// + /// Helper to fire the P25 data pre-process event. + /// + /// instance + protected void FireP25DataPreprocess(P25DataReceivedEvent e) + { + if (P25DataPreprocess != null) + P25DataPreprocess.Invoke(this, e); + } + + /// + /// Helper to fire the P25 data received event. + /// + /// instance + protected void FireP25DataReceived(P25DataReceivedEvent e) + { + if (P25DataReceived != null) + P25DataReceived.Invoke(this, e); + } + + /// + /// Helper to fire the NXDN data received event. + /// + /// instance + protected void FireNXDNDataReceived(NXDNDataReceivedEvent e) + { + if (NXDNDataReceived != null) + NXDNDataReceived.Invoke(this, e); + } + + /// + /// Helper to fire the peer connected event. + /// + /// instance + protected void FirePeerConnected(PeerConnectedEvent e) + { + if (PeerConnected != null) + PeerConnected.Invoke(this, e); + } + } // public abstract class FneBase +} // namespace fnecore diff --git a/FneMaster.cs b/FneMaster.cs new file mode 100644 index 0000000..ddb4cf8 --- /dev/null +++ b/FneMaster.cs @@ -0,0 +1,1166 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022-2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using System.Text; +using System.Text.Json; // thanks .NET Core... + +using fnecore.DMR; +using fnecore.P25; +using fnecore.NXDN; +using fnecore.EDAC; + +namespace fnecore +{ + /// + /// Event used to process incoming grant request data. + /// + public class GrantRequestEvent : EventArgs + { + /// + /// Peer ID + /// + public uint PeerId { get; } + /// + /// DVM Mode + /// + public DVMState Mode { get; } + /// + /// Source Address + /// + public uint SrcId { get; } + /// + /// Destination Address + /// + public uint DstId { get; } + /// + /// Slot Number + /// + public byte Slot { get; } + /// + /// Flag indicating a unit-to-unit (private call) request. + /// + public bool UnitToUnit { get; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + private GrantRequestEvent() + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// Peer ID + /// DVM Mode State + /// Source Address + /// Destination Address + /// Slot Number + /// Unit-to-Unit (Private Call) + public GrantRequestEvent(uint peerId, DVMState mode, uint srcId, uint dstId, byte slot, bool unitToUnit) : base() + { + this.PeerId = peerId; + this.Mode = mode; + this.SrcId = srcId; + this.DstId = dstId; + this.Slot = slot; + this.UnitToUnit = unitToUnit; + } + } // public class GrantRequestEvent : EventArgs + + /// + /// Implements an FNE "master". + /// + public class FneMaster : FneBase + { + private UdpListener server = null; + + private bool abortListening = false; + + private CancellationTokenSource listenCancelToken = new CancellationTokenSource(); + private Task listenTask = null; + private CancellationTokenSource maintainenceCancelToken = new CancellationTokenSource(); + private Task maintainenceTask = null; + + private Dictionary peers; + + /* + ** Properties + */ + + /// + /// Gets the for this . + /// + public IPEndPoint EndPoint + { + get + { + if (server != null) + return server.EndPoint; + return null; + } + } + + /// + /// Dictionary of connected peers. + /// + public Dictionary Peers => peers; + + /// + /// Gets/sets the password used for connecting to this master. + /// + public string Passphrase + { + get; + set; + } + + /// + /// Flag indicating whether we are repeating to all connected peers. + /// + public bool Repeat + { + get; + set; + } + + /// + /// Flag indicating whether we are repeating DMR to all connected peers. + /// + public bool NoRepeatDMR + { + get; + set; + } + + /// + /// Flag indicating whether we are repeating P25 to all connected peers. + /// + public bool NoRepeatP25 + { + get; + set; + } + + /// + /// Flag indicating whether we are repeating NXDN to all connected peers. + /// + public bool NoRepeatNXDN + { + get; + set; + } + + /// + /// Gets/sets how many pings are missed before we give up and force a reregister. + /// + public int MaxMissed + { + get; + set; + } + + /// + /// Flag indicating whether peer activity transfers are allowed. + /// + public bool AllowActivityTransfer + { + get; + set; + } + + /// + /// Flag indicating whether peer diagnostic transfers are allowed. + /// + public bool AllowDiagnosticTransfer + { + get; + set; + } + + /* + ** Events/Callbacks + */ + + /// + /// Event action that handles processing a grant request. + /// + public event EventHandler GrantRequestReceived; + + /// + /// Event action that handles peer activity transfers. + /// + public Action ActivityTransfer = null; + + /// + /// Event action that handles peer diagnostic transfers. + /// + public Action DiagnosticTransfer = null; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public FneMaster(string systemName, uint peerId, int port) : this(systemName, peerId, new IPEndPoint(IPAddress.Any, port)) + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public FneMaster(string systemName, uint peerId, IPEndPoint endpoint) : base(systemName, peerId) + { + fneType = FneType.MASTER; + + server = new UdpListener(endpoint); + peers = new Dictionary(); + + Passphrase = string.Empty; + Repeat = true; + NoRepeatDMR = false; + NoRepeatP25 = false; + NoRepeatNXDN = false; + AllowActivityTransfer = false; + AllowDiagnosticTransfer = false; + } + + /// + /// Starts the main execution loop for this . + /// + public override void Start() + { + if (isStarted) + throw new InvalidOperationException("Cannot start listening when already started."); + + Logger(LogLevel.INFO, $"({systemName}) starting network services, {server.EndPoint}"); + + abortListening = false; + listenTask = Task.Factory.StartNew(Listen, listenCancelToken.Token); + maintainenceTask = Task.Factory.StartNew(Maintainence, maintainenceCancelToken.Token); + + isStarted = true; + } + + /// + /// Stops the main execution loop for this . + /// + public override void Stop() + { + if (!isStarted) + throw new InvalidOperationException("Cannot stop listening when not started."); + + Logger(LogLevel.INFO, $"({systemName}) stopping network services, {server.EndPoint}"); + + // stop UDP listen task + if (listenTask != null) + { + abortListening = true; + listenCancelToken.Cancel(); + + try + { + listenTask.GetAwaiter().GetResult(); + } + catch (OperationCanceledException) { /* stub */ } + finally + { + listenCancelToken.Dispose(); + } + } + + // stop maintainence task + if (maintainenceTask != null) + { + maintainenceCancelToken.Cancel(); + + try + { + maintainenceTask.GetAwaiter().GetResult(); + } + catch (OperationCanceledException) { /* stub */ } + finally + { + maintainenceCancelToken.Dispose(); + } + } + + isStarted = false; + } + + /// + /// Helper to find and get for the given peer ID. + /// + /// Peer ID + /// if connected, otherwise null. + public PeerInformation FindPeer(uint peerId) + { + if (peers.ContainsKey(peerId)) + return peers[peerId]; + return null; + } + + /// + /// Helper to send a raw UDP frame. + /// + /// UDP frame to send + public void Send(UdpFrame frame) + { + if (RawPacketTrace) + Log(LogLevel.DEBUG, $"({systemName}) Network Sent (to {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); + + server.Send(frame); + } + + /// + /// Helper to send a raw UDP frame. + /// + /// UDP frame to send + public async void SendAsync(UdpFrame frame) + { + if (RawPacketTrace) + Log(LogLevel.DEBUG, $"({systemName}) Network Sent (to {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); + + await server.SendAsync(frame); + } + + /// + /// Helper to send a raw message to the specified peer. + /// + /// + /// Byte array containing message to send + public void SendPeer(IPEndPoint endpoint, byte[] message) + { + Send(new UdpFrame() + { + Endpoint = endpoint, + Message = message + }); + } + + /// + /// Helper to send a data message to the specified peer. + /// + /// Peer ID + /// Opcode + /// Byte array containing message to send + /// + /// + public void SendPeer(uint peerId, Tuple opcode, byte[] message, ushort pktSeq, uint streamId = 0) + { + if (peers.ContainsKey(peerId)) + { + if (streamId == 0) + streamId = peers[peerId].StreamID; + byte[] data = WriteFrame(message, peerId, this.peerId, opcode, pktSeq, streamId); + SendPeer(peers[peerId].EndPoint, data); + } + } + + /// + /// Helper to send a data message to the specified peer. + /// + /// Peer ID + /// Opcode + /// Byte array containing message to send + /// + public void SendPeer(uint peerId, Tuple opcode, byte[] message, bool incPktSeq = false) + { + if (peers.ContainsKey(peerId)) + { + if (incPktSeq) { + peers[peerId].PacketSequence = ++peers[peerId].PacketSequence; + } + + SendPeer(peerId, opcode, message, peers[peerId].PacketSequence); + } + } + + /// + /// Helper to send a command message to the specified peer. + /// + /// + /// Peer ID + /// Opcode + /// + /// + /// Byte array containing message to send + public void SendPeerCommand(IPEndPoint endpoint, uint peerId, Tuple opcode, ushort pktSeq, uint streamId, byte[] message = null) + { + int messageLength = 0; + if (message != null) + messageLength = message.Length; + + byte[] buffer = new byte[messageLength + 6]; + if (message != null) + Buffer.BlockCopy(message, 0, buffer, 6, message.Length); + + byte[] frame = WriteFrame(buffer, peerId, this.peerId, opcode, pktSeq, streamId); + SendPeer(endpoint, frame); + } + + /// + /// Helper to send a command message to the specified peer. + /// + /// + /// Peer ID + /// Opcode + /// Byte array containing message to send + /// + public void SendPeerCommand(uint peerId, Tuple opcode, byte[] message = null, bool incPktSeq = false) + { + int messageLength = 0; + if (message != null) + messageLength = message.Length; + + byte[] buffer = new byte[messageLength + 6]; + if (message != null) + Buffer.BlockCopy(message, 0, buffer, 6, message.Length); + + SendPeer(peerId, opcode, buffer, incPktSeq); + } + + /// + /// Helper to send a ACK response to the specified peer. + /// + /// Peer ID + public void SendPeerACK(uint peerId) + { + if (peers.ContainsKey(peerId)) + { + peers[peerId].PacketSequence = ++peers[peerId].PacketSequence; + + // send ping response to peer + SendPeerCommand(peerId, CreateOpcode(Constants.NET_FUNC_ACK)); + } + } + + /// + /// Helper to send a NAK response to the specified peer. + /// + /// Peer ID + /// Tag NAK'ed + public void SendPeerNAK(uint peerId, string tag) + { + if (peers.ContainsKey(peerId)) + { + peers[peerId].PacketSequence = ++peers[peerId].PacketSequence; + + // send ping response to peer + SendPeerCommand(peerId, CreateOpcode(Constants.NET_FUNC_NAK)); + Log(LogLevel.WARNING, $"({systemName}) {tag} from unauth PEER {peerId}"); + } + } + + /// + /// Helper to send a NAK response to the specified endpoint. + /// + /// IP endpoint + /// Tag NAK'ed + public void SendNAK(IPEndPoint endpoint, uint peerId, string tag) + { + byte[] buffer = new byte[10]; + FneUtils.WriteBytes(peerId, ref buffer, 6); + + Send(new UdpFrame() + { + Endpoint = endpoint, + Message = WriteFrame(buffer, peerId, this.peerId, CreateOpcode(Constants.NET_FUNC_NAK), 0, CreateStreamID()) + }); + Log(LogLevel.WARNING, $"({systemName}) {tag} from unconnected PEER {endpoint.Address.ToString()}:{endpoint.Port}"); + } + + /// + /// Helper to send a raw message to the connected peers. + /// + /// Opcode + /// Byte array containing message to send + /// RTP Packet Sequence + public void SendPeers(Tuple opcode, byte[] message, uint pktSeq = uint.MaxValue) + { + foreach (PeerInformation peer in peers.Values) + { + if (pktSeq > ushort.MaxValue) + pktSeq = peer.PacketSequence; + + byte[] data = WriteFrame(message, peer.PeerID, this.peerId, opcode, (ushort)pktSeq, peer.StreamID); + SendAsync(new UdpFrame() + { + Endpoint = peer.EndPoint, + Message = data + }); + } + } + + /// + /// Internal helper to compare authorization hashes. + /// + /// FNE message frame + /// Peer Information + private bool CompareAuthHash(byte[] message, PeerInformation info) + { + // get the hash in the frame message + byte[] hash = new byte[message.Length - 8]; + Buffer.BlockCopy(message, 8, hash, 0, hash.Length); + + // calculate our own hash + byte[] inBuf = new byte[4 + Passphrase.Length]; + FneUtils.WriteBytes(info.Salt, ref inBuf, 0); + FneUtils.StringToBytes(Passphrase, inBuf, 4, Passphrase.Length); + byte[] outHash = FneUtils.sha256_hash(inBuf); + + // compare hashes + if (hash.Length == outHash.Length) + { + bool res = true; + for (int i = 0; i < hash.Length; i++) + { + if (hash[i] != outHash[i]) + { + res = false; + break; + } + } + + return res; + } + + return false; + } + + /// + /// + /// + private async void Listen() + { + CancellationToken ct = listenCancelToken.Token; + ct.ThrowIfCancellationRequested(); + + while (!abortListening) + { + try + { + UdpFrame frame = await server.Receive(); + if (RawPacketTrace) + Log(LogLevel.DEBUG, $"({systemName}) Network Received (from {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); + + // decode RTP frame + if (frame.Message.Length <= 0) + continue; + + RtpHeader rtpHeader; + RtpFNEHeader fneHeader; + int messageLength = 0; + byte[] message = ReadFrame(frame, out messageLength, out rtpHeader, out fneHeader); + if (message == null) + { + Log(LogLevel.ERROR, $"({systemName}) Malformed packet (from {frame.Endpoint}); failed to decode RTP frame"); + continue; + } + + if (message.Length < 1) + { + Log(LogLevel.WARNING, $"({systemName}) Malformed packet (from {frame.Endpoint}) -- {FneUtils.HexDump(message, 0)}"); + continue; + } + + uint peerId = fneHeader.PeerID; + uint streamId = fneHeader.StreamID; + + // update current peer stream ID + if (peerId > 0 && peers.ContainsKey(peerId) && streamId != 0) + { + ushort pktSeq = rtpHeader.Sequence; + + if ((peers[peerId].StreamID == streamId) && (pktSeq != peers[peerId].NextPacketSequence)) + Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} Stream {streamId} out-of-sequence; {pktSeq} != {peers[peerId].NextPacketSequence}"); + + peers[peerId].StreamID = streamId; + peers[peerId].PacketSequence = pktSeq; + peers[peerId].NextPacketSequence = (ushort)(pktSeq + 1); + if (peers[peerId].NextPacketSequence > ushort.MaxValue) + peers[peerId].NextPacketSequence = 0; + } + + // if we don't have a stream ID and are receiving call data -- throw an error and discard + if (streamId == 0 && fneHeader.Function == Constants.NET_FUNC_PROTOCOL) + { + Log(LogLevel.ERROR, $"({systemName}) PEER {peerId} Malformed packet (no stream ID for call?)"); + continue; + } + + // process incoming message frame opcodes + switch (fneHeader.Function) + { + case Constants.NET_FUNC_PROTOCOL: + { + if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really) + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + byte seqNo = message[4]; + uint srcId = FneUtils.Bytes3ToUInt32(message, 5); + uint dstId = FneUtils.Bytes3ToUInt32(message, 8); + + byte bits = message[15]; + byte slot = (byte)(((bits & 0x80) == 0x80) ? 1 : 0); + CallType callType = ((bits & 0x40) == 0x40) ? CallType.PRIVATE : CallType.GROUP; + FrameType frameType = (FrameType)((bits & 0x30) >> 4); + + DMRDataType dataType = DMRDataType.IDLE; + if ((bits & 0x20) == 0x20) + dataType = (DMRDataType)(bits & ~(0x20)); + + byte n = (byte)(bits & 0xF); + + // is the stream valid? + bool ret = true; + if (DMRDataValidate != null) + ret = DMRDataValidate(peerId, srcId, dstId, slot, callType, frameType, dataType, streamId, message); + + if (ret) + { + // is this the peer being ignored? + if (PeerIgnored != null) + ret = PeerIgnored(peerId, srcId, dstId, slot, callType, frameType, dataType, streamId); + else + ret = false; + + if (ret) + continue; +#if DEBUG + Log(LogLevel.DEBUG, $"{systemName} DMRD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} TS {slot} [STREAM ID {streamId}] PKT SEQ {rtpHeader.Sequence}"); +#endif + // are we repeating to connected peers? + if (Repeat && !NoRepeatDMR) + { + foreach (KeyValuePair kvp in peers) + { + // don't repeat to the peer sending the data... + if (kvp.Key != peerId) + { + // is this peer being ignored + if (PeerIgnored != null) + ret = PeerIgnored(peerId, srcId, dstId, slot, callType, frameType, dataType, streamId); + else + ret = false; + + if (!ret) + SendPeer(kvp.Key, CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_DMR), message, rtpHeader.Sequence); + } + } + } + + // perform any userland actions with the data + FireDMRDataReceived(new DMRDataReceivedEvent(peerId, srcId, dstId, slot, callType, frameType, dataType, n, rtpHeader.Sequence, streamId, message)); + } + } + else + SendPeerNAK(peerId, Constants.TAG_DMR_DATA); + } + } + else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_P25) // Encapsulated P25 data frame + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really) + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + uint srcId = FneUtils.Bytes3ToUInt32(message, 5); + uint dstId = FneUtils.Bytes3ToUInt32(message, 8); + CallType callType = (message[4] == P25Defines.LC_PRIVATE) ? CallType.PRIVATE : CallType.GROUP; + P25DUID duid = (P25DUID)message[22]; + FrameType frameType = ((duid != P25DUID.TDU) && (duid != P25DUID.TDULC)) ? FrameType.VOICE : FrameType.TERMINATOR; + + // is the stream valid? + bool ret = true; + if (P25DataValidate != null) + ret = P25DataValidate(peerId, srcId, dstId, callType, duid, frameType, streamId, message); + + if (ret) + { + // pre-process P25 data... + FireP25DataPreprocess(new P25DataReceivedEvent(peerId, srcId, dstId, callType, duid, frameType, rtpHeader.Sequence, streamId, message)); + + // is this the peer being ignored? + if (PeerIgnored != null) + ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); + else + ret = false; + + if (ret) + continue; +#if DEBUG + Log(LogLevel.DEBUG, $"{systemName} P25D: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}] PKT SEQ {rtpHeader.Sequence}"); +#endif + // are we repeating to connected peers? + if (Repeat && !NoRepeatP25) + { + foreach (KeyValuePair kvp in peers) + { + // don't repeat to the peer sending the data... + if (kvp.Key != peerId) + { + // is this peer being ignored + if (PeerIgnored != null) + ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); + else + ret = false; + + if (!ret) + SendPeer(kvp.Key, CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), message, rtpHeader.Sequence); + } + } + } + + // perform any userland actions with the data + FireP25DataReceived(new P25DataReceivedEvent(peerId, srcId, dstId, callType, duid, frameType, rtpHeader.Sequence, streamId, message)); + } + } + else + SendPeerNAK(peerId, Constants.TAG_P25_DATA); + } + } + else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_NXDN) // Encapsulated NXDN data frame + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really) + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + NXDNMessageType messageType = (NXDNMessageType)message[4]; + uint srcId = FneUtils.Bytes3ToUInt32(message, 5); + uint dstId = FneUtils.Bytes3ToUInt32(message, 8); + + byte bits = message[15]; + CallType callType = ((bits & 0x40) == 0x40) ? CallType.PRIVATE : CallType.GROUP; + FrameType frameType = (messageType != NXDNMessageType.MESSAGE_TYPE_TX_REL) ? FrameType.VOICE : FrameType.TERMINATOR; + + // is the stream valid? + bool ret = true; + if (NXDNDataValidate != null) + ret = NXDNDataValidate(peerId, srcId, dstId, callType, messageType, frameType, streamId, message); + + if (ret) + { + // is this the peer being ignored? + if (PeerIgnored != null) + ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); + else + ret = false; + + if (ret) + continue; +#if DEBUG + Log(LogLevel.DEBUG, $"{systemName} NXDD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}] PKT SEQ {rtpHeader.Sequence}"); +#endif + // are we repeating to connected peers? + if (Repeat && !NoRepeatNXDN) + { + foreach (KeyValuePair kvp in peers) + { + // don't repeat to the peer sending the data... + if (kvp.Key != peerId) + { + // is this peer being ignored + if (PeerIgnored != null) + ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); + else + ret = false; + + if (!ret) + SendPeer(kvp.Key, CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_NXDN), message, rtpHeader.Sequence); + } + } + } + + // perform any userland actions with the data + FireNXDNDataReceived(new NXDNDataReceivedEvent(peerId, srcId, dstId, callType, messageType, frameType, rtpHeader.Sequence, streamId, message)); + } + } + else + SendPeerNAK(peerId, Constants.TAG_NXDN_DATA); + } + } + else + Log(LogLevel.ERROR, $"({systemName}) Unknown protocol opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); + } + break; + + case Constants.NET_FUNC_RPTL: // Repeater Login + { + if (peerId > 0 && !peers.ContainsKey(peerId)) + { + PeerInformation info = new PeerInformation(); + info.PeerID = peerId; + info.EndPoint = frame.Endpoint; + info.PacketSequence = rtpHeader.Sequence; + info.NextPacketSequence = ++rtpHeader.Sequence; + info.StreamID = streamId; + + info.Salt = (uint)rand.Next(-2147483648, 2147483647); + + Log(LogLevel.INFO, $"({systemName}) Repeater logging in with PEER {peerId}, {info.EndPoint}"); + + byte[] buffer = new byte[4]; + FneUtils.WriteBytes(info.Salt, ref buffer, 0); + + SendPeerCommand(frame.Endpoint, peerId, CreateOpcode(Constants.NET_FUNC_ACK), ++info.PacketSequence, streamId, buffer); + + info.State = ConnectionState.WAITING_AUTHORISATION; + peers.Add(peerId, info); + + Log(LogLevel.INFO, $"({systemName}) Challenge Response sent to PEER {peerId} for login {info.Salt}"); + } + else + { + SendNAK(frame.Endpoint, peerId, Constants.TAG_REPEATER_LOGIN); + + // check if the peer is in our peer list -- if he is, and he isn't in a running state, reset + // the login sequence + if (peerId > 0 && !peers.ContainsKey(peerId)) + { + PeerInformation info = new PeerInformation(); + if (info.State != ConnectionState.RUNNING) + if (peers.ContainsKey(peerId)) + peers.Remove(peerId); + } + } + } + break; + case Constants.NET_FUNC_RPTK: // Repeater Authentication + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + PeerInformation info = peers[peerId]; + info.LastPing = DateTime.Now; + + if (info.State == ConnectionState.WAITING_AUTHORISATION) + { + if (CompareAuthHash(message, info)) + { + info.State = ConnectionState.WAITING_CONFIG; + + SendPeerACK(peerId); + peers[peerId] = info; + Log(LogLevel.INFO, $"({systemName}) PEER {peerId} has completed the login exchange"); + } + else + { + Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} has failed the login exchange"); + SendPeerNAK(peerId, Constants.TAG_REPEATER_AUTH); + if (peers.ContainsKey(peerId)) + peers.Remove(peerId); + } + } + else + { + Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} tried login exchange in wrong state"); + SendPeerNAK(peerId, Constants.TAG_REPEATER_AUTH); + if (peers.ContainsKey(peerId)) + peers.Remove(peerId); + } + } + else + SendNAK(frame.Endpoint, peerId, Constants.TAG_REPEATER_AUTH); + } + break; + case Constants.NET_FUNC_RPTC: // Repeater Configuration + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + PeerInformation info = peers[peerId]; + info.LastPing = DateTime.Now; + + if (info.State == ConnectionState.WAITING_CONFIG) + { + string payload = FneUtils.BytesToString(message, 8, message.Length - 8); + try + { + JsonDocument json = JsonDocument.Parse(payload); + + // identity + info.Details.Identity = json.RootElement.GetProperty("identity").GetString(); + info.Details.RxFrequency = json.RootElement.GetProperty("rxFrequency").GetUInt32(); + info.Details.TxFrequency = json.RootElement.GetProperty("txFrequency").GetUInt32(); + + // system info + JsonElement sysInfo = json.RootElement.GetProperty("info"); + info.Details.Latitude = sysInfo.GetProperty("latitude").GetDouble(); + info.Details.Longitude = sysInfo.GetProperty("longitude").GetDouble(); + info.Details.Height = sysInfo.GetProperty("height").GetInt32(); + info.Details.Location = sysInfo.GetProperty("location").GetString(); + + // channel data + JsonElement channel = json.RootElement.GetProperty("channel"); + info.Details.TxPower = channel.GetProperty("txPower").GetUInt32(); + info.Details.TxOffsetMhz = (float)channel.GetProperty("txOffsetMhz").GetDouble(); + info.Details.ChBandwidthKhz = (float)channel.GetProperty("chBandwidthKhz").GetDouble(); + info.Details.ChannelID = channel.GetProperty("channelId").GetByte(); + info.Details.ChannelNo = channel.GetProperty("channelNo").GetUInt32(); + + // RCON + JsonElement rcon = json.RootElement.GetProperty("rcon"); + info.Details.Password = rcon.GetProperty("password").GetString(); + info.Details.Port = rcon.GetProperty("port").GetInt32(); + + info.Details.Software = json.RootElement.GetProperty("software").GetString(); + } + catch + { + const string outOfDate = "Old/Out of Date Peer"; + + // identity + info.Details.Identity = outOfDate; + info.Details.RxFrequency = 0; + info.Details.TxFrequency = 0; + + // system info + info.Details.Latitude = 0.0d; + info.Details.Longitude = 0.0d; + info.Details.Height = 0; + info.Details.Location = outOfDate; + + // channel data + info.Details.TxOffsetMhz = 0.0f; + info.Details.ChBandwidthKhz = 0.0f; + info.Details.ChannelID = 0; + info.Details.ChannelNo = 0; + info.Details.TxPower = 0; + + // RCON + info.Details.Password = "ABCD1234"; + info.Details.Port = 9990; // default port + + info.Details.Software = "Peer Software Did Not Send JSON Configuration"; + } + + info.State = ConnectionState.RUNNING; + info.Connection = true; + info.PingsReceived = 0; + info.LastPing = DateTime.Now; + + SendPeerACK(peerId); + Log(LogLevel.INFO, $"({systemName}) PEER {peerId} has completed the configuration exchange"); + peers[peerId] = info; + + // userland actions + FirePeerConnected(new PeerConnectedEvent(peerId, info)); + } + else + { + Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} tried configuration exchange in wrong state"); + SendPeerNAK(peerId, Constants.TAG_REPEATER_CONFIG); + if (peers.ContainsKey(peerId)) + peers.Remove(peerId); + } + } + else + SendNAK(frame.Endpoint, peerId, Constants.TAG_REPEATER_CONFIG); + } + break; + + case Constants.NET_FUNC_RPT_CLOSING: // Repeater Closing (Disconnect) + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really) + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + Log(LogLevel.INFO, $"({systemName}) PEER {peerId} is closing down"); + + SendPeerACK(peerId); + peers.Remove(peerId); + + // userland actions + if (PeerDisconnected != null) + PeerDisconnected(peerId); + } + } + } + break; + case Constants.NET_FUNC_PING: // Repeater Ping + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really) + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + PeerInformation peer = peers[peerId]; + peer.PingsReceived++; + peer.LastPing = DateTime.Now; + //peer.PacketSequence = ++peer.PacketSequence; + + peers[peerId] = peer; + + // send ping response to peer + SendPeerCommand(peerId, CreateOpcode(Constants.NET_FUNC_PONG), null, true); + Log(LogLevel.DEBUG, $"({systemName}) Received and answered RPTPING from PEER {peerId}"); + } + else + SendPeerNAK(peerId, Constants.TAG_REPEATER_PING); + } + } + break; + + case Constants.NET_FUNC_GRANT: // Repeater Grant Request + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really) + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + uint srcId = FneUtils.ToUInt32(message, 11); + uint dstId = FneUtils.ToUInt32(message, 15); + byte slot = (byte)(message[19] & 0x07); + bool unitToUnit = (bool)((message[19] & 0x80) == 0x80); + DVMState mode = (DVMState)message[20]; + + if (GrantRequestReceived != null) + GrantRequestReceived(this, new GrantRequestEvent(peerId, mode, srcId, dstId, slot, unitToUnit)); + } + else + SendPeerNAK(peerId, Constants.TAG_REPEATER_GRANT); + } + } + break; + + case Constants.NET_FUNC_TRANSFER: + { + if (fneHeader.SubFunction == Constants.NET_TRANSFER_SUBFUNC_ACTIVITY) // Peer Activity Log Transfer + { + // can we do activity transfers? + if (AllowActivityTransfer) + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + byte[] buffer = new byte[messageLength - 11]; + Buffer.BlockCopy(message, 11, buffer, 0, buffer.Length); + + string msg = Encoding.ASCII.GetString(buffer); + if (ActivityTransfer != null) + ActivityTransfer(peerId, msg); + } + } + } + } + else if (fneHeader.SubFunction == Constants.NET_TRANSFER_SUBFUNC_DIAG) // Peer Diagnostic Log Transfer + { + // can we do diagnostic transfers? + if (AllowDiagnosticTransfer) + { + if (peerId > 0 && peers.ContainsKey(peerId)) + { + // validate peer (simple validation really) + if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) + { + byte[] buffer = new byte[messageLength - 11]; + Buffer.BlockCopy(message, 11, buffer, 0, buffer.Length); + + string msg = Encoding.ASCII.GetString(buffer); + if (DiagnosticTransfer != null) + DiagnosticTransfer(peerId, msg); + } + } + } + } + else + Log(LogLevel.ERROR, $"({systemName}) Unknown transfer opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); + } + break; + + default: + Log(LogLevel.ERROR, $"({systemName}) Unknown opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); + break; + } + } + catch (SocketException se) + { + Log(LogLevel.FATAL, $"({systemName}) SOCKET ERROR: {se.SocketErrorCode}; {se.Message}"); + } + + if (ct.IsCancellationRequested) + abortListening = true; + } + } + + /// + /// Internal maintainence routine. + /// + private async void Maintainence() + { + CancellationToken ct = maintainenceCancelToken.Token; + while (!abortListening) + { + // check to see if any peers have been quiet (no ping) longer than allowed + List peersToRemove = new List(); + foreach (KeyValuePair kvp in peers) + { + uint peerId = kvp.Key; + PeerInformation peer = kvp.Value; + + DateTime dt = peer.LastPing.AddSeconds(PingTime * MaxMissed); + if (dt < DateTime.Now) + { + Log(LogLevel.INFO, $"({systemName}) PEER {peerId} has timed out"); + peersToRemove.Add(peerId); + } + } + + // remove any peers + foreach (uint peerId in peersToRemove) + peers.Remove(peerId); + + try + { + await Task.Delay(PingTime * 1000, ct); + } + catch (TaskCanceledException) { /* stub */ } + } + } + } // public class FneMaster +} // namespace fnecore diff --git a/FnePeer.cs b/FnePeer.cs new file mode 100644 index 0000000..4361f1b --- /dev/null +++ b/FnePeer.cs @@ -0,0 +1,711 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022-2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using System.Text; +using System.Text.Json; + +using fnecore.DMR; +using fnecore.P25; +using fnecore.NXDN; + +namespace fnecore +{ + /// + /// Callback used to process a raw network frame. + /// + /// + /// Peer ID + /// Stream ID + /// True, if the frame was handled, otherwise false. + public delegate bool RawNetworkFrame(UdpFrame frame, uint peerId, uint streamId); + + /// + /// Implements an FNE "peer". + /// + public class FnePeer : FneBase + { + private const int MAX_MISSED_PEER_PINGS = 5; + + private UdpReceiver client = null; + + private bool abortListening = false; + + private CancellationTokenSource listenCancelToken = new CancellationTokenSource(); + private Task listenTask = null; + private CancellationTokenSource maintainenceCancelToken = new CancellationTokenSource(); + private Task maintainenceTask = null; + + private PeerInformation info; + private IPEndPoint masterEndpoint = null; + + private ushort currPktSeq = 0; + private uint streamId = 0; + + /* + ** Properties + */ + + /// + /// Gets/sets the peer information. + /// + public PeerInformation Information + { + get { return info; } + set { info = value; } + } + + /// + /// Gets/sets the password used for connecting to a master. + /// + public string Passphrase + { + get; + set; + } + + /// + /// Gets the number of pings sent. + /// + public int PingsSent + { + get; + private set; + } + + /// + /// Gets the number of pings acked. + /// + public int PingsAcked + { + get; + private set; + } + + /* + ** Events/Callbacks + */ + + /// + /// Event action that handles a raw network frame directly. + /// + public RawNetworkFrame NetworkFrameHandler = null; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public FnePeer(string systemName, uint peerId, string address, int port) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port)) + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public FnePeer(string systemName, uint peerId, IPEndPoint endpoint) : base(systemName, peerId) + { + fneType = FneType.PEER; + + masterEndpoint = endpoint; + client = new UdpReceiver(); + + info = new PeerInformation(); + info.PeerID = peerId; + info.Connection = false; + + PingsAcked = 0; + } + + /// + /// Starts the main execution loop for this . + /// + public override void Start() + { + if (isStarted) + throw new InvalidOperationException("Cannot start listening when already started."); + + Logger(LogLevel.INFO, $"({systemName}) starting network services, {masterEndpoint}"); + + // attempt initial connection + try + { + client.Connect(masterEndpoint); + } + catch (SocketException se) + { + Log(LogLevel.FATAL, $"({systemName}) SOCKET ERROR: {se.SocketErrorCode}; {se.Message}"); + } + + abortListening = false; + listenTask = Task.Factory.StartNew(Listen, listenCancelToken.Token); + maintainenceTask = Task.Factory.StartNew(Maintainence, maintainenceCancelToken.Token); + + isStarted = true; + } + + /// + /// Stops the main execution loop for this . + /// + public override void Stop() + { + if (!isStarted) + throw new InvalidOperationException("Cannot stop listening when not started."); + + Logger(LogLevel.INFO, $"({systemName}) stopping network services, {masterEndpoint}"); + + // stop UDP listen task + if (listenTask != null) + { + abortListening = true; + listenCancelToken.Cancel(); + + try + { + listenTask.GetAwaiter().GetResult(); + } + catch (OperationCanceledException) { /* stub */ } + finally + { + listenCancelToken.Dispose(); + } + } + + // stop maintainence task + if (maintainenceTask != null) + { + maintainenceCancelToken.Cancel(); + + try + { + maintainenceTask.GetAwaiter().GetResult(); + } + catch (OperationCanceledException) { /* stub */ } + finally + { + maintainenceCancelToken.Dispose(); + } + } + + isStarted = false; + } + + /// + /// Helper to send a raw UDP frame. + /// + /// UDP frame to send + public void Send(UdpFrame frame) + { + if (RawPacketTrace) + Log(LogLevel.DEBUG, $"({systemName}) Network Sent (to {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); + + client.Send(frame); + } + + /// + /// Helper to send a data message to the master. + /// + /// Opcode + /// Byte array containing message to send + /// RTP Packet Sequence + /// + public void SendMaster(Tuple opcode, byte[] message, ushort pktSeq, uint streamId = 0) + { + if (streamId == 0) + streamId = this.streamId; + + Send(new UdpFrame() + { + Endpoint = masterEndpoint, + Message = WriteFrame(message, peerId, this.peerId, opcode, pktSeq, streamId) + }); + } + + /// + /// Helper to send a data message to the master. + /// + /// Opcode + /// Byte array containing message to send + public void SendMaster(Tuple opcode, byte[] message) + { + SendMaster(opcode, message, pktSeq()); + } + + /// + /// Helper to update the RTP packet sequence. + /// + /// + /// RTP packet sequence. + public ushort pktSeq(bool reset = false) + { + if (reset) + { + currPktSeq = 0; + return currPktSeq; + } + + ushort curr = currPktSeq; + ++currPktSeq; + if (currPktSeq > ushort.MaxValue) + currPktSeq = 0; + + return curr; + } + + /// + /// Internal UDP listen routine. + /// + private async void Listen() + { + CancellationToken ct = listenCancelToken.Token; + ct.ThrowIfCancellationRequested(); + + while (!abortListening) + { + try + { + UdpFrame frame = await client.Receive(); + if (RawPacketTrace) + Log(LogLevel.DEBUG, $"Network Received (from {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); + + // decode RTP frame + if (frame.Message.Length <= 0) + continue; + + RtpHeader rtpHeader; + RtpFNEHeader fneHeader; + int messageLength = 0; + byte[] message = ReadFrame(frame, out messageLength, out rtpHeader, out fneHeader); + if (message == null) + { + Log(LogLevel.ERROR, $"({systemName}) Malformed packet (from {frame.Endpoint}); failed to decode RTP frame"); + continue; + } + + if (message.Length < 1) + { + Log(LogLevel.WARNING, $"({systemName}) Malformed packet (from {frame.Endpoint}) -- {FneUtils.HexDump(message, 0)}"); + continue; + } + + // validate frame endpoint + if (frame.Endpoint.ToString() == masterEndpoint.ToString()) + { + uint peerId = fneHeader.PeerID; + + if (streamId != fneHeader.StreamID) + pktSeq(true); + + // update current peer stream ID + streamId = fneHeader.StreamID; + + // see if the peer is defining its own frame handler, if it is try to handle the frame there + if (NetworkFrameHandler != null) + { + if (NetworkFrameHandler(frame, peerId, streamId)) + continue; + } + + // process incoming message frame opcodes + switch (fneHeader.Function) + { + case Constants.NET_FUNC_PROTOCOL: + { + if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame + { + if (peerId != this.peerId) + { + //Log(LogLevel.WARNING, $"({systemName}) PEER {peerId}; routed traffic, rewriting PEER {this.peerId}"); + peerId = this.peerId; + } + + // is this for our peer? + if (peerId == this.peerId) + { + byte seqNo = message[4]; + uint srcId = FneUtils.Bytes3ToUInt32(message, 5); + uint dstId = FneUtils.Bytes3ToUInt32(message, 8); + + byte bits = message[15]; + byte slot = (byte)(((bits & 0x80) == 0x80) ? 1 : 0); + CallType callType = ((bits & 0x40) == 0x40) ? CallType.PRIVATE : CallType.GROUP; + FrameType frameType = (FrameType)((bits & 0x30) >> 4); + + DMRDataType dataType = DMRDataType.IDLE; + if ((bits & 0x20) == 0x20) + dataType = (DMRDataType)(bits & ~(0x20)); + + byte n = (byte)(bits & 0xF); +#if DEBUG + Log(LogLevel.DEBUG, $"{systemName} DMRD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} TS {slot} [STREAM ID {streamId}]"); +#endif + // perform any userland actions with the data + FireDMRDataReceived(new DMRDataReceivedEvent(peerId, srcId, dstId, slot, callType, frameType, dataType, n, rtpHeader.Sequence, streamId, message)); + } + } + else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_P25) // Encapsulated P25 data frame + { + if (peerId != this.peerId) + { + //Log(LogLevel.WARNING, $"({systemName}) PEER {peerId}; routed traffic, rewriting PEER {this.peerId}"); + peerId = this.peerId; + } + + // is this for our peer? + if (peerId == this.peerId) + { + uint srcId = FneUtils.Bytes3ToUInt32(message, 5); + uint dstId = FneUtils.Bytes3ToUInt32(message, 8); + CallType callType = (message[4] == P25Defines.LC_PRIVATE) ? CallType.PRIVATE : CallType.GROUP; + P25DUID duid = (P25DUID)message[22]; + FrameType frameType = ((duid != P25DUID.TDU) && (duid != P25DUID.TDULC)) ? FrameType.VOICE : FrameType.TERMINATOR; +#if DEBUG + Log(LogLevel.DEBUG, $"{systemName} P25D: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]"); +#endif + // perform any userland actions with the data + FireP25DataReceived(new P25DataReceivedEvent(peerId, srcId, dstId, callType, duid, frameType, rtpHeader.Sequence, streamId, message)); + } + } + else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_NXDN) // Encapsulated NXDN data frame + { + if (peerId != this.peerId) + { + //Log(LogLevel.WARNING, $"({systemName}) PEER {peerId}; routed traffic, rewriting PEER {this.peerId}"); + peerId = this.peerId; + } + + // is this for our peer? + if (peerId == this.peerId) + { + NXDNMessageType messageType = (NXDNMessageType)message[4]; + uint srcId = FneUtils.Bytes3ToUInt32(message, 5); + uint dstId = FneUtils.Bytes3ToUInt32(message, 8); + + byte bits = message[15]; + CallType callType = ((bits & 0x40) == 0x40) ? CallType.PRIVATE : CallType.GROUP; + FrameType frameType = (messageType != NXDNMessageType.MESSAGE_TYPE_TX_REL) ? FrameType.VOICE : FrameType.TERMINATOR; +#if DEBUG + Log(LogLevel.DEBUG, $"{systemName} NXDD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]"); +#endif + // perform any userland actions with the data + FireNXDNDataReceived(new NXDNDataReceivedEvent(peerId, srcId, dstId, callType, messageType, frameType, rtpHeader.Sequence, streamId, message)); + } + } + else + { + Log(LogLevel.ERROR, $"({systemName}) Unknown protocol opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); + } + } + break; + + case Constants.NET_FUNC_MASTER: + { + /* stub */ + } + break; + + case Constants.NET_FUNC_NAK: // Master NAK + { + if (this.peerId == peerId) + { + info.Connection = false; + Log(LogLevel.DEBUG, $"({systemName}) PEER {this.peerId} MSTNAK received"); + } + } + break; + case Constants.NET_FUNC_ACK: // Repeater ACK + { + if (info.State == ConnectionState.WAITING_LOGIN) // Repeater Login + { + uint salt = FneUtils.ToUInt32(message, 6); + Log(LogLevel.INFO, $"({systemName}) PEER {this.peerId} login ACK received with ID {salt}"); + + info.Salt = salt; + + // calculate our own hash + byte[] inBuf = new byte[4 + Passphrase.Length]; + FneUtils.WriteBytes(info.Salt, ref inBuf, 0); + FneUtils.StringToBytes(Passphrase, inBuf, 4, Passphrase.Length); + byte[] calcHash = FneUtils.sha256_hash(inBuf); + + // send message to master + byte[] res = new byte[calcHash.Length + 8]; + FneUtils.StringToBytes(Constants.TAG_REPEATER_AUTH, res, 0, 4); + FneUtils.WriteBytes(peerId, ref res, 4); + Buffer.BlockCopy(calcHash, 0, res, 8, calcHash.Length); + SendMaster(CreateOpcode(Constants.NET_FUNC_RPTK), res); + + info.State = ConnectionState.WAITING_AUTHORISATION; + } + else if (info.State == ConnectionState.WAITING_AUTHORISATION) // Repeater Authorization + { + if (this.peerId == peerId) + { + string json = string.Empty; + using (MemoryStream stream = new MemoryStream()) + { + using (Utf8JsonWriter jsonWriter = new Utf8JsonWriter(stream)) + { + jsonWriter.WriteStartObject(); + + // identity + jsonWriter.WriteString("identity", info.Details.Identity); + jsonWriter.WriteNumber("rxFrequency", info.Details.RxFrequency); + jsonWriter.WriteNumber("txFrequency", info.Details.TxFrequency); + + // system info + { + jsonWriter.WritePropertyName("info"); + jsonWriter.WriteStartObject(); + + jsonWriter.WriteNumber("latitude", info.Details.Latitude); + jsonWriter.WriteNumber("longitude", info.Details.Longitude); + jsonWriter.WriteNumber("height", info.Details.Height); + jsonWriter.WriteString("location", info.Details.Location); + + jsonWriter.WriteEndObject(); + } + + // channel data + { + jsonWriter.WritePropertyName("channel"); + jsonWriter.WriteStartObject(); + + jsonWriter.WriteNumber("txPower", info.Details.TxPower); + jsonWriter.WriteNumber("txOffsetMhz", (double)info.Details.TxOffsetMhz); + jsonWriter.WriteNumber("chBandwidthKhz", (double)info.Details.ChBandwidthKhz); + jsonWriter.WriteNumber("channelId", info.Details.ChannelID); + jsonWriter.WriteNumber("channelNo", info.Details.ChannelNo); + + jsonWriter.WriteEndObject(); + } + + // RCON + { + jsonWriter.WritePropertyName("rcon"); + jsonWriter.WriteStartObject(); + + jsonWriter.WriteString("password", info.Details.Password); + jsonWriter.WriteNumber("port", info.Details.Port); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteString("software", info.Details.Software); + + jsonWriter.WriteEndObject(); + } + + json = Encoding.UTF8.GetString(stream.ToArray()); + } + + // send message to master + byte[] res = new byte[json.Length + 8]; + FneUtils.StringToBytes(Constants.TAG_REPEATER_CONFIG, res, 0, 4); + FneUtils.WriteBytes(peerId, ref res, 4); + FneUtils.StringToBytes(json, res, 8, json.Length); + SendMaster(CreateOpcode(Constants.NET_FUNC_RPTC), res); + + info.State = ConnectionState.WAITING_CONFIG; + } + else + { + info.State = ConnectionState.WAITING_LOGIN; + info.Connection = false; + Log(LogLevel.ERROR, $"({systemName}) PEER {this.peerId} master ACK contained wrong ID - connection reset"); + } + } + else if (info.State == ConnectionState.WAITING_CONFIG) // Repeater Configuration + { + if (this.peerId == peerId) + { + info.Connection = true; + info.State = ConnectionState.RUNNING; + Log(LogLevel.INFO, $"({systemName}) PEER {this.peerId} connection to MASTER completed"); + + // userland actions + FirePeerConnected(new PeerConnectedEvent(peerId, info)); + } + else + { + info.State = ConnectionState.WAITING_LOGIN; + info.Connection = false; + Log(LogLevel.ERROR, $"({systemName}) PEER {this.peerId} master ACK contained wrong ID - connection reset"); + } + } + } + break; + case Constants.NET_FUNC_MST_CLOSING: // Master Closing (Disconnect) + { + if (this.peerId == peerId) + { + info.Connection = false; + Log(LogLevel.DEBUG, $"({systemName}) PEER {this.peerId} MSTCL received"); + + // userland actions + if (PeerDisconnected != null) + PeerDisconnected(peerId); + } + } + break; + case Constants.NET_FUNC_PONG: // Master Ping Response + { + if (this.peerId == peerId) + { + PingsAcked++; + Log(LogLevel.DEBUG, $"({systemName}) PEER {this.peerId} MSTPONG received, pongs since connected {PingsAcked}"); + } + } + break; + + default: + Log(LogLevel.ERROR, $"({systemName}) Unknown opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); + break; + } + } + } + catch (InvalidOperationException) + { + Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting..."); + client.Connect(masterEndpoint); + } + catch (SocketException se) + { + // what kind of socket error do we have? + switch (se.SocketErrorCode) + { + case SocketError.NotConnected: + case SocketError.ConnectionReset: + case SocketError.ConnectionAborted: + case SocketError.ConnectionRefused: + Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting..."); + client.Connect(masterEndpoint); + break; + default: + Log(LogLevel.FATAL, $"({systemName}) SOCKET ERROR: {se.SocketErrorCode}; {se.Message}"); + break; + } + } + + if (ct.IsCancellationRequested) + abortListening = true; + } + } + + /// + /// Internal maintainence routine. + /// + private async void Maintainence() + { + CancellationToken ct = maintainenceCancelToken.Token; + while (!abortListening) + { + try + { + // if we're not connected, zero out the connection stats and send a login request to the master + if (!info.Connection || info.State == ConnectionState.WAITING_LOGIN) + { + PingsSent = 0; + PingsAcked = 0; + info.State = ConnectionState.WAITING_LOGIN; + + // send message to master + byte[] res = new byte[8]; + FneUtils.StringToBytes(Constants.TAG_REPEATER_LOGIN, res, 0, 4); + FneUtils.WriteBytes(peerId, ref res, 4); + SendMaster(CreateOpcode(Constants.NET_FUNC_RPTL), res); + + Log(LogLevel.INFO, $"({systemName}) Sending login request to MASTER {masterEndpoint}"); + } + + // if we are connected, sent a ping to the master and increment the counter + if (info.Connection && info.State == ConnectionState.RUNNING) + { + if (PingsSent > (PingsAcked + MAX_MISSED_PEER_PINGS)) + { + Log(LogLevel.WARNING, $"({systemName} Peer connection lost to {masterEndpoint}; reconnecting..."); + + PingsSent = 0; + PingsAcked = 0; + info.State = ConnectionState.WAITING_LOGIN; + + client.Connect(masterEndpoint); + } + else + { + // send message to master + byte[] res = new byte[1]; + SendMaster(CreateOpcode(Constants.NET_FUNC_PING), res); + + PingsSent++; + Log(LogLevel.DEBUG, $"({systemName}) RPTPING sent to MASTER {masterEndpoint}; pings since connected {PingsSent}"); + } + } + } + catch (InvalidOperationException) + { + Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting..."); + client.Connect(masterEndpoint); + } + catch (SocketException se) + { + // what kind of socket error do we have? + switch (se.SocketErrorCode) + { + case SocketError.NotConnected: + case SocketError.ConnectionReset: + case SocketError.ConnectionAborted: + case SocketError.ConnectionRefused: + Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting..."); + client.Connect(masterEndpoint); + break; + default: + Log(LogLevel.FATAL, $"({systemName}) SOCKET ERROR: {se.SocketErrorCode}; {se.Message}"); + abortListening = true; + break; + } + } + + try + { + await Task.Delay(PingTime * 1000, ct); + } + catch (TaskCanceledException) { /* stub */ } + } + } + } // public class FnePeer +} // namespace fnecore diff --git a/FneUtils.cs b/FneUtils.cs new file mode 100644 index 0000000..c4e465d --- /dev/null +++ b/FneUtils.cs @@ -0,0 +1,775 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; +using System.Security.Cryptography; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using System.Text; + +namespace fnecore +{ + /// + /// + /// + public class FneUtils + { + private static readonly byte[] BIT_MASK_TABLE = new byte[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + private static Action memsetDelegate; + + /* + ** Methods + */ + + /// + /// Static initializer for the class. + /// + static FneUtils() + { + DynamicMethod dynamicMethod = new DynamicMethod("Memset", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, + null, new[] { typeof(IntPtr), typeof(byte), typeof(int) }, typeof(FneUtils), true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Initblk); + generator.Emit(OpCodes.Ret); + + memsetDelegate = (Action)dynamicMethod.CreateDelegate(typeof(Action)); + } + + /// + /// + /// + /// + /// + /// + public static void Memset(byte[] array, byte what, int length) + { + GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); + memsetDelegate(gcHandle.AddrOfPinnedObject(), what, length); + gcHandle.Free(); + } + + /// + /// + /// + /// + /// + /// + public static void ByteToBitsBE(byte b, ref bool[] bits, int offset) + { + bits[0 + offset] = (b & 0x80U) == 0x80U; + bits[1 + offset] = (b & 0x40U) == 0x40U; + bits[2 + offset] = (b & 0x20U) == 0x20U; + bits[3 + offset] = (b & 0x10U) == 0x10U; + bits[4 + offset] = (b & 0x08U) == 0x08U; + bits[5 + offset] = (b & 0x04U) == 0x04U; + bits[6 + offset] = (b & 0x02U) == 0x02U; + bits[7 + offset] = (b & 0x01U) == 0x01U; + } + + /// + /// + /// + /// + /// + /// + public static void ByteToBitsLE(byte b, ref bool[] bits, int offset) + { + bits[0 + offset] = (b & 0x01U) == 0x01U; + bits[1 + offset] = (b & 0x02U) == 0x02U; + bits[2 + offset] = (b & 0x04U) == 0x04U; + bits[3 + offset] = (b & 0x08U) == 0x08U; + bits[4 + offset] = (b & 0x10U) == 0x10U; + bits[5 + offset] = (b & 0x20U) == 0x20U; + bits[6 + offset] = (b & 0x40U) == 0x40U; + bits[7 + offset] = (b & 0x80U) == 0x80U; + } + + /// + /// + /// + /// + /// + /// + public static void BitsToByteBE(bool[] bits, int offset, ref byte b) + { + b = (byte)(bits[0 + offset] ? 0x80U : 0x00U); + b |= (byte)(bits[1 + offset] ? 0x40U : 0x00U); + b |= (byte)(bits[2 + offset] ? 0x20U : 0x00U); + b |= (byte)(bits[3 + offset] ? 0x10U : 0x00U); + b |= (byte)(bits[4 + offset] ? 0x08U : 0x00U); + b |= (byte)(bits[5 + offset] ? 0x04U : 0x00U); + b |= (byte)(bits[6 + offset] ? 0x02U : 0x00U); + b |= (byte)(bits[7 + offset] ? 0x01U : 0x00U); + } + + /// + /// + /// + /// + /// + /// + public static void BitsToByteLE(bool[] bits, int offset, ref byte b) + { + b = (byte)(bits[0 + offset] ? 0x01U : 0x00U); + b |= (byte)(bits[1 + offset] ? 0x02U : 0x00U); + b |= (byte)(bits[2 + offset] ? 0x04U : 0x00U); + b |= (byte)(bits[3 + offset] ? 0x08U : 0x00U); + b |= (byte)(bits[4 + offset] ? 0x10U : 0x00U); + b |= (byte)(bits[5 + offset] ? 0x20U : 0x00U); + b |= (byte)(bits[6 + offset] ? 0x40U : 0x00U); + b |= (byte)(bits[7 + offset] ? 0x80U : 0x00U); + } + + /// + /// + /// + /// + /// + /// + public static void WriteBit(ref byte[] p, uint i, bool b) + { + p[(i) >> 3] = (byte)((b) ? (p[(i) >> 3] | BIT_MASK_TABLE[(i) & 7]) : (p[(i) >> 3] & ~BIT_MASK_TABLE[(i) & 7])); + } + + /// + /// + /// + /// + /// + /// + public static void WriteBit(ref Span p, uint i, bool b) + { + p[(int)((i) >> 3)] = (byte)((b) ? (p[(int)((i) >> 3)] | BIT_MASK_TABLE[(i) & 7]) : (p[(int)((i) >> 3)] & ~BIT_MASK_TABLE[(i) & 7])); + } + + /// + /// + /// + /// + /// + /// + public static bool ReadBit(byte[] p, uint i) + { + return (p[(i) >> 3] & BIT_MASK_TABLE[(i) & 7]) != 0; + } + + /// + /// + /// + /// + /// + /// + public static bool ReadBit(Span p, uint i) + { + return (p[(int)((i) >> 3)] & BIT_MASK_TABLE[(i) & 7]) != 0; + } + + /// + /// Write the given bytes in the unsigned short into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(ushort val, ref byte[] buffer, int offset) + { + buffer[offset] = (byte)(val >> 8); + buffer[offset + 1] = (byte)(val & 0xFF); + } + + /// + /// Write the given bytes in the unsigned short into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(ushort val, ref Span buffer, int offset) + { + buffer[offset] = (byte)(val >> 8); + buffer[offset + 1] = (byte)(val & 0xFF); + } + + /// + /// Write the given bytes in the unsigned integer into the given buffer (by most significant byte) + /// + /// + public static void Write3Bytes(uint val, ref byte[] buffer, int offset) + { + buffer[offset + 0] = (byte)((val >> 16) & 0xFF); + buffer[offset + 1] = (byte)((val >> 8) & 0xFF); + buffer[offset + 2] = (byte)(val & 0xFF); + } + + /// + /// Write the given bytes in the unsigned integer into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(uint val, ref byte[] buffer, int offset) + { + buffer[offset] = (byte)((val >> 24) & 0xFF); + buffer[offset + 1] = (byte)((val >> 16) & 0xFF); + buffer[offset + 2] = (byte)((val >> 8) & 0xFF); + buffer[offset + 3] = (byte)(val & 0xFF); + } + + /// + /// Write the given bytes in the unsigned integer into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(uint val, ref Span buffer, int offset) + { + buffer[offset] = (byte)((val >> 24) & 0xFF); + buffer[offset + 1] = (byte)((val >> 16) & 0xFF); + buffer[offset + 2] = (byte)((val >> 8) & 0xFF); + buffer[offset + 3] = (byte)(val & 0xFF); + } + + /// + /// Write the given bytes in the unsigned long into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(ulong val, ref byte[] buffer, int offset) + { + buffer[offset] = (byte)((val >> 56) & 0xFF); + buffer[offset + 1] = (byte)((val >> 48) & 0xFF); + buffer[offset + 2] = (byte)((val >> 40) & 0xFF); + buffer[offset + 3] = (byte)((val >> 32) & 0xFF); + buffer[offset + 4] = (byte)((val >> 24) & 0xFF); + buffer[offset + 5] = (byte)((val >> 16) & 0xFF); + buffer[offset + 6] = (byte)((val >> 8) & 0xFF); + buffer[offset + 7] = (byte)(val & 0xFF); + } + + /// + /// Write the given bytes in the unsigned long into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(ulong val, ref Span buffer, int offset) + { + buffer[offset] = (byte)((val >> 56) & 0xFF); + buffer[offset + 1] = (byte)((val >> 48) & 0xFF); + buffer[offset + 2] = (byte)((val >> 40) & 0xFF); + buffer[offset + 3] = (byte)((val >> 32) & 0xFF); + buffer[offset + 4] = (byte)((val >> 24) & 0xFF); + buffer[offset + 5] = (byte)((val >> 16) & 0xFF); + buffer[offset + 6] = (byte)((val >> 8) & 0xFF); + buffer[offset + 7] = (byte)(val & 0xFF); + } + + /// + /// Write the given bytes in the short into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(short val, ref byte[] buffer, int offset) + { + WriteBytes((ushort)val, ref buffer, offset); + } + + /// + /// Write the given bytes in the short into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(short val, ref Span buffer, int offset) + { + WriteBytes((ushort)val, ref buffer, offset); + } + + /// + /// Write the given bytes in the integer into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(int val, ref byte[] buffer, int offset) + { + WriteBytes((uint)val, ref buffer, offset); + } + + /// + /// Write the given bytes in the integer into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(int val, ref Span buffer, int offset) + { + WriteBytes((uint)val, ref buffer, offset); + } + + /// + /// Write the given bytes in the long into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(long val, ref byte[] buffer, int offset) + { + WriteBytes((ulong)val, ref buffer, offset); + } + + /// + /// Write the given bytes in the long into the given buffer (by most significant byte) + /// + /// + /// + /// + public static void WriteBytes(long val, ref Span buffer, int offset) + { + WriteBytes((ulong)val, ref buffer, offset); + } + + /// + /// Get an unsigned short value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static ushort ToUInt16(byte[] buffer, int offset) + { + return (ushort)(((buffer[offset] << 8) & 0xFF00) | ((buffer[offset + 1] << 0) & 0x00FF)); + } + + /// + /// Get an unsigned short value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static ushort ToUInt16(Span buffer, int offset) + { + return (ushort)(((buffer[offset] << 8) & 0xFF00) | ((buffer[offset + 1] << 0) & 0x00FF)); + } + + /// + /// Get an unsigned integer value from the given bytes. (by most significant byte) + /// + /// + public static uint Bytes3ToUInt32(byte[] buffer, int offset) + { + uint val = (uint)(((buffer[offset] << 16) & 0x00FF0000U) | ((buffer[offset + 1] << 8) & 0x0000FF00U) | + ((buffer[offset + 2] << 0) & 0x000000FFU)); + return val; + } + + /// + /// Get an unsigned integer value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static uint ToUInt32(byte[] buffer, int offset) + { + uint val = (uint)(((buffer[offset + 0] << 24) & 0xFF000000U) | ((buffer[offset + 1] << 16) & 0x00FF0000U) + | ((buffer[offset + 2] << 8) & 0x0000FF00U) | ((buffer[offset + 3] << 0) & 0x000000FFU)); + return val; + } + + /// + /// Get an unsigned integer value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static uint ToUInt32(Span buffer, int offset) + { + uint val = (uint)(((buffer[offset + 0] << 24) & 0xFF000000U) | ((buffer[offset + 1] << 16) & 0x00FF0000U) + | ((buffer[offset + 2] << 8) & 0x0000FF00U) | ((buffer[offset + 3] << 0) & 0x000000FFU)); + return val; + } + + /// + /// Get an unsigned long value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static ulong ToUInt64(byte[] buffer, int offset) + { + return (((ulong)ToUInt32(buffer, offset + 0)) << 32) | ToUInt32(buffer, offset + 4); + } + + /// + /// Get an unsigned long value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static ulong ToUInt64(Span buffer, int offset) + { + return (((ulong)ToUInt32(buffer, offset + 0)) << 32) | ToUInt32(buffer, offset + 4); + } + + /// + /// Get an signed short value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static short ToInt16(byte[] buffer, int offset) + { + return (short)ToUInt16(buffer, offset); + } + + /// + /// Get an signed short value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static short ToInt16(Span buffer, int offset) + { + return (short)ToUInt16(buffer, offset); + } + + /// + /// Get a signed integer value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static int ToInt32(byte[] buffer, int offset) + { + return (int)ToUInt32(buffer, offset); + } + + /// + /// Get a signed integer value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static int ToInt32(Span buffer, int offset) + { + return (int)ToUInt32(buffer, offset); + } + + /// + /// Get a signed long value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static long ToInt64(byte[] buffer, int offset) + { + return (long)ToUInt64(buffer, offset); + } + + /// + /// Get a signed long value from the given bytes. (by most significant byte) + /// + /// + /// + /// + public static long ToInt64(Span buffer, int offset) + { + return (long)ToUInt64(buffer, offset); + } + + /// + /// Primitive conversion from Unicode to ASCII that preserves special characters. + /// + /// The string to convert. + /// The buffer to fill. + /// The start of the string in the buffer. + /// The number of characters to convert. + /// The built-in ASCIIEncoding converts characters of codepoint > 127 to ?, + /// this preserves those code points by removing the top 16 bits of each character. + public static void StringToBytes(string value, byte[] dest, int offset, int count) + { + char[] chars = value.ToCharArray(); + + int i = 0; + while (i < chars.Length) + { + dest[i + offset] = (byte)chars[i]; + ++i; + } + + while (i < count) + { + dest[i + offset] = 0; + ++i; + } + } + + /// + /// Primitive conversion from ASCII to Unicode that preserves special characters. + /// + /// The data to convert. + /// The first byte to convert. + /// The number of bytes to convert. + /// The string. + /// The built-in ASCIIEncoding converts characters of codepoint > 127 to ?, + /// this preserves those code points. + public static string BytesToString(byte[] data, int offset, int count) + { + char[] result = new char[count]; + + // iterate through the individual bytes, and convert them to a character + for (int i = 0; i < count; ++i) + result[i] = (char)data[i + offset]; + + return new string(result); + } + + /// + /// Helper to display the ASCII representation of a hex dump. + /// + /// + /// + /// + private static string DisplayHexChars(Span buffer, int offset) + { + int bCount = 0; + + string _out = string.Empty; + for (int i = offset; i < buffer.Length; i++) + { + // stop every 16 bytes... + if (bCount == 16) + break; + + byte b = buffer[i]; + char c = Convert.ToChar(b); + + // make control and illegal characters spaces + if (c >= 0x00 && c <= 0x1F) + c = ' '; + if (c >= 0x7F) + c = ' '; + + _out += c; + + bCount++; + } + + return _out; + } + + /// + /// Helper to display the ASCII representation of a hex dump. + /// + /// + /// + /// + private static string DisplayHexChars(byte[] buffer, int offset) + { + int bCount = 0; + + string _out = string.Empty; + for (int i = offset; i < buffer.Length; i++) + { + // stop every 16 bytes... + if (bCount == 16) + break; + + byte b = buffer[i]; + char c = Convert.ToChar(b); + + // make control and illegal characters spaces + if (c >= 0x00 && c <= 0x1F) + c = ' '; + if (c >= 0x7F) + c = ' '; + + _out += c; + + bCount++; + } + + return _out; + } + + /// + /// Perform a hex dump of a buffer. + /// + /// + /// + public static string HexDump(byte[] buffer, int offset = 0) + { + int bCount = 0, j = 0; + + // iterate through buffer printing all the stored bytes + string res = "\n\tDUMP " + j.ToString("X4") + ": "; + for (int i = offset; i < buffer.Length; i++) + { + byte b = buffer[i]; + + // split the message every 16 bytes... + if (bCount == 16) + { + res += " *" + DisplayHexChars(buffer, j) + "*\n"; + bCount = 0; + j += 16; + res += "\tDUMP " + j.ToString("X4") + ": "; + } + else + res += (bCount > 0) ? " " : ""; + + res += b.ToString("X2"); + bCount++; + } + + // if the byte count at this point is non-zero print the message + if (bCount != 0) + { + if (bCount < 16) + { + for (int i = bCount; i < 16; i++) + res += " "; + } + + res += " *" + DisplayHexChars(buffer, j) + "*"; + } + + return res; + } + + /// + /// Perform a hex dump of a buffer. + /// + /// + /// + public static string HexDump(short[] buffer, int offset = 0) + { + int bCount = 0, j = 0; + + // iterate through buffer printing all the stored bytes + string res = "\n\tDUMP " + j.ToString("X4") + ": "; + for (int i = offset; i < buffer.Length; i++) + { + short b = buffer[i]; + + // split the message every 16 bytes... + if (bCount == 16) + { + //res += " *" + DisplayHexChars(buffer, j) + "*\n"; + res += "\n"; + bCount = 0; + j += 16; + res += "\tDUMP " + j.ToString("X4") + ": "; + } + else + res += (bCount > 0) ? " " : ""; + + res += b.ToString("X4"); + bCount++; + } + + // if the byte count at this point is non-zero print the message + if (bCount != 0) + { + if (bCount < 16) + { + for (int i = bCount; i < 16; i++) + res += " "; + } + + //res += " *" + DisplayHexChars(buffer, j) + "*"; + } + + return res; + } + + /// + /// Perform a hex dump of a buffer. + /// + /// + /// + public static string HexDump(Memory buffer, int offset = 0) + { + int bCount = 0, j = 0; + + // iterate through buffer printing all the stored bytes + string res = "\n\tDUMP " + j.ToString("X4") + ": "; + for (int i = offset; i < buffer.Length; i++) + { + byte b = buffer.Span[i]; + + // split the message every 16 bytes... + if (bCount == 16) + { + res += " *" + DisplayHexChars(buffer.Span, j) + "*\n"; + bCount = 0; + j += 16; + res += "\tDUMP " + j.ToString("X4") + ": "; + } + else + res += (bCount > 0) ? " " : ""; + + res += b.ToString("X2"); + bCount++; + } + + // if the byte count at this point is non-zero print the message + if (bCount != 0) + { + if (bCount < 16) + { + for (int i = bCount; i < 16; i++) + res += " "; + } + + res += " *" + DisplayHexChars(buffer.Span, j) + "*"; + } + + return res; + } + + /// + /// + /// + /// + /// + public static byte[] sha256_hash(string value) + { + return sha256_hash(Encoding.ASCII.GetBytes(value)); + } + + /// + /// + /// + /// + /// + public static byte[] sha256_hash(byte[] value) + { + using (SHA256 hash = SHA256Managed.Create()) + return hash.ComputeHash(value); + } + } // public class FneUtils +} // namespace fnecore diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + 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 +them 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/NXDN/NXDNDefines.cs b/NXDN/NXDNDefines.cs new file mode 100644 index 0000000..a74c8e5 --- /dev/null +++ b/NXDN/NXDNDefines.cs @@ -0,0 +1,61 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.NXDN +{ + /// + /// NXDN Message Type + /// + public enum NXDNMessageType : byte + { + /// + /// Voice Call + /// + MESSAGE_TYPE_VCALL = 0x01, + /// + /// Voice Call - Individual + /// + MESSAGE_TYPE_VCALL_IV = 0x03, + /// + /// Data Call Header + /// + MESSAGE_TYPE_DCALL_HDR = 0x09, + /// + /// Data Call Header + /// + MESSAGE_TYPE_DCALL_DATA = 0x0B, + /// + /// Data Call Header + /// + MESSAGE_TYPE_DCALL_ACK = 0x0C, + /// + /// Transmit Release + /// + MESSAGE_TYPE_TX_REL = 0x08, + /// + /// Idle + /// + MESSAGE_TYPE_IDLE = 0x10, + } // public enum NXDNMessageType : byte +} // namespace fnecore.NXDN diff --git a/P25/P25Defines.cs b/P25/P25Defines.cs new file mode 100644 index 0000000..18e7d03 --- /dev/null +++ b/P25/P25Defines.cs @@ -0,0 +1,211 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.P25 +{ + /// + /// P25 DFSI Frame Types + /// + public class P25DFSI + { + public const byte P25_RTP_PAYLOAD_TYPE = 100; + + public const uint P25_DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES = 22; + public const uint P25_DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES = 14; + public const uint P25_DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU1_VOICE4_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU1_VOICE5_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU1_VOICE6_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU1_VOICE7_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU1_VOICE8_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES = 16; + + public const uint P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES = 22; + public const uint P25_DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES = 14; + public const uint P25_DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU2_VOICE13_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU2_VOICE14_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU2_VOICE15_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU2_VOICE16_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES = 17; + public const uint P25_DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES = 16; + + public const byte P25_DFSI_STATUS_NO_ERROR = 0x00; // + public const byte P25_DFSI_STATUS_ERASE = 0x02; // + + public const byte P25_DFSI_RT_ENABLED = 0x02; // + public const byte P25_DFSI_RT_DISABLED = 0x04; // + + public const byte P25_DFSI_START_FLAG = 0x0C; // + public const byte P25_DFSI_STOP_FLAG = 0x25; // + + public const byte P25_DFSI_TYPE_DATA_PAYLOAD = 0x06; // + public const byte P25_DFSI_TYPE_VOICE = 0x0B; // + + public const byte P25_DFSI_DEF_ICW_SOURCE = 0x00; // Infrastructure Source - Default Source + public const byte P25_DFSI_DEF_SOURCE = 0x00; // + + public const byte P25_DFSI_MOT_START_STOP = 0x00; // Motorola Start/Stop Frame + public const byte P25_DFSI_MOT_VHDR_1 = 0x60; // Motorola Voice Header 1 + public const byte P25_DFSI_MOT_VHDR_2 = 0x61; // Motorola Voice Header 2 + + public const byte P25_DFSI_LDU1_VOICE1 = 0x62; // IMBE LDU1 - Voice 1 + public const byte P25_DFSI_LDU1_VOICE2 = 0x63; // IMBE LDU1 - Voice 2 + public const byte P25_DFSI_LDU1_VOICE3 = 0x64; // IMBE LDU1 - Voice 3 + Link Control + public const byte P25_DFSI_LDU1_VOICE4 = 0x65; // IMBE LDU1 - Voice 4 + Link Control + public const byte P25_DFSI_LDU1_VOICE5 = 0x66; // IMBE LDU1 - Voice 5 + Link Control + public const byte P25_DFSI_LDU1_VOICE6 = 0x67; // IMBE LDU1 - Voice 6 + Link Control + public const byte P25_DFSI_LDU1_VOICE7 = 0x68; // IMBE LDU1 - Voice 7 + Link Control + public const byte P25_DFSI_LDU1_VOICE8 = 0x69; // IMBE LDU1 - Voice 8 + Link Control + public const byte P25_DFSI_LDU1_VOICE9 = 0x6A; // IMBE LDU1 - Voice 9 + Low Speed Data + + public const byte P25_DFSI_LDU2_VOICE10 = 0x6B; // IMBE LDU2 - Voice 10 + public const byte P25_DFSI_LDU2_VOICE11 = 0x6C; // IMBE LDU2 - Voice 11 + public const byte P25_DFSI_LDU2_VOICE12 = 0x6D; // IMBE LDU2 - Voice 12 + Encryption Sync + public const byte P25_DFSI_LDU2_VOICE13 = 0x6E; // IMBE LDU2 - Voice 13 + Encryption Sync + public const byte P25_DFSI_LDU2_VOICE14 = 0x6F; // IMBE LDU2 - Voice 14 + Encryption Sync + public const byte P25_DFSI_LDU2_VOICE15 = 0x70; // IMBE LDU2 - Voice 15 + Encryption Sync + public const byte P25_DFSI_LDU2_VOICE16 = 0x71; // IMBE LDU2 - Voice 16 + Encryption Sync + public const byte P25_DFSI_LDU2_VOICE17 = 0x72; // IMBE LDU2 - Voice 17 + Encryption Sync + public const byte P25_DFSI_LDU2_VOICE18 = 0x73; // IMBE LDU2 - Voice 18 + Low Speed Data + } // public class P25DSFI_FT + + /// + /// P25 Data Unit ID + /// + public enum P25DUID : byte + { + /// + /// Header Data Unit + /// + HDU = 0x00, + /// + /// Terminator Data Unit + /// + TDU = 0x03, + /// + /// Logical Data Unit 1 + /// + LDU1 = 0x05, + /// + /// Trunking Signalling Data Unit + /// + TSDU = 0x07, + /// + /// Logical Data Unit 2 + /// + LDU2 = 0x0A, + /// + /// Packet Data Unit + /// + PDU = 0x0C, + /// + /// Terminator Data Unit with Link Control + /// + TDULC = 0x0F + } // public enum P25DUID : byte + + /// + /// P25 Constants + /// + public class P25Defines + { + public const byte P25_MFG_STANDARD = 0x00; + + public const byte P25_ALGO_UNENCRYPT = 0x80; + + public const byte P25_MI_LENGTH = 9; + + // LDUx/TDULC Link Control Opcode(s) + public const byte LC_GROUP = 0x00; // GRP VCH USER - Group Voice Channel User + public const byte LC_GROUP_UPDT = 0x02; // GRP VCH UPDT - Group Voice Channel Update + public const byte LC_PRIVATE = 0x03; // UU VCH USER - Unit-to-Unit Voice Channel User + public const byte LC_UU_ANS_REQ = 0x05; // UU ANS REQ - Unit to Unit Answer Request + public const byte LC_TEL_INT_VCH_USER = 0x06; // TEL INT VCH USER - Telephone Interconnect Voice Channel User + public const byte LC_TEL_INT_ANS_RQST = 0x07; // TEL INT ANS RQST - Telephone Interconnect Answer Request + public const byte LC_CALL_TERM = 0x0F; // CALL TERM - Call Termination or Cancellation + public const byte LC_IDEN_UP = 0x18; // IDEN UP - Channel Identifier Update + public const byte LC_SYS_SRV_BCAST = 0x20; // SYS SRV BCAST - System Service Broadcast + public const byte LC_ADJ_STS_BCAST = 0x22; // ADJ STS BCAST - Adjacent Site Status Broadcast + public const byte LC_RFSS_STS_BCAST = 0x23; // RFSS STS BCAST - RFSS Status Broadcast + public const byte LC_NET_STS_BCAST = 0x24; // NET STS BCAST - Network Status Broadcast + public const byte LC_CONV_FALLBACK = 0x2A; // CONV FALLBACK - Conventional Fallback + + // TSBK ISP/OSP Shared Opcode(s) + public const byte TSBK_IOSP_GRP_VCH = 0x00; // GRP VCH REQ - Group Voice Channel Request (ISP), GRP VCH GRANT - Group Voice Channel Grant (OSP) + public const byte TSBK_IOSP_UU_VCH = 0x04; // UU VCH REQ - Unit-to-Unit Voice Channel Request (ISP), UU VCH GRANT - Unit-to-Unit Voice Channel Grant (OSP) + public const byte TSBK_IOSP_UU_ANS = 0x05; // UU ANS RSP - Unit-to-Unit Answer Response (ISP), UU ANS REQ - Unit-to-Unit Answer Request (OSP) + public const byte TSBK_IOSP_TELE_INT_DIAL = 0x08; // TELE INT DIAL REQ - Telephone Interconnect Request - Explicit (ISP), TELE INT DIAL GRANT - Telephone Interconnect Grant (OSP) + public const byte TSBK_IOSP_TELE_INT_ANS = 0x0A; // TELE INT ANS RSP - Telephone Interconnect Answer Response (ISP), TELE INT ANS REQ - Telephone Interconnect Answer Request (OSP) + public const byte TSBK_IOSP_STS_UPDT = 0x18; // STS UPDT REQ - Status Update Request (ISP), STS UPDT - Status Update (OSP) + public const byte TSBK_IOSP_STS_Q = 0x1A; // STS Q REQ - Status Query Request (ISP), STS Q - Status Query (OSP) + public const byte TSBK_IOSP_MSG_UPDT = 0x1C; // MSG UPDT REQ - Message Update Request (ISP), MSG UPDT - Message Update (OSP) + public const byte TSBK_IOSP_CALL_ALRT = 0x1F; // CALL ALRT REQ - Call Alert Request (ISP), CALL ALRT - Call Alert (OSP) + public const byte TSBK_IOSP_ACK_RSP = 0x20; // ACK RSP U - Acknowledge Response - Unit (ISP), ACK RSP FNE - Acknowledge Response - FNE (OSP) + public const byte TSBK_IOSP_EXT_FNCT = 0x24; // EXT FNCT RSP - Extended Function Response (ISP), EXT FNCT CMD - Extended Function Command (OSP) + public const byte TSBK_IOSP_GRP_AFF = 0x28; // GRP AFF REQ - Group Affiliation Request (ISP), GRP AFF RSP - Group Affiliation Response (OSP) + public const byte TSBK_IOSP_U_REG = 0x2C; // U REG REQ - Unit Registration Request (ISP), U REG RSP - Unit Registration Response (OSP) + + // TSBK Inbound Signalling Packet (ISP) Opcode(s) + public const byte TSBK_ISP_TELE_INT_PSTN_REQ = 0x09; // TELE INT PSTN REQ - Telephone Interconnect Request - Implicit + public const byte TSBK_ISP_SNDCP_CH_REQ = 0x12; // SNDCP CH REQ - SNDCP Data Channel Request + public const byte TSBK_ISP_STS_Q_RSP = 0x19; // STS Q RSP - Status Query Response + public const byte TSBK_ISP_CAN_SRV_REQ = 0x23; // CAN SRV REQ - Cancel Service Request + public const byte TSBK_ISP_EMERG_ALRM_REQ = 0x27; // EMERG ALRM REQ - Emergency Alarm Request + public const byte TSBK_ISP_GRP_AFF_Q_RSP = 0x29; // GRP AFF Q RSP - Group Affiliation Query Response + public const byte TSBK_ISP_U_DEREG_REQ = 0x2B; // U DE REG REQ - Unit De-Registration Request + public const byte TSBK_ISP_LOC_REG_REQ = 0x2D; // LOC REG REQ - Location Registration Request + + // TSBK Outbound Signalling Packet (OSP) Opcode(s) + public const byte TSBK_OSP_GRP_VCH_GRANT_UPD = 0x02; // GRP VCH GRANT UPD - Group Voice Channel Grant Update + public const byte TSBK_OSP_UU_VCH_GRANT_UPD = 0x06; // UU VCH GRANT UPD - Unit-to-Unit Voice Channel Grant Update + public const byte TSBK_OSP_SNDCP_CH_GNT = 0x14; // SNDCP CH GNT - SNDCP Data Channel Grant + public const byte TSBK_OSP_SNDCP_CH_ANN = 0x16; // SNDCP CH ANN - SNDCP Data Channel Announcement + public const byte TSBK_OSP_DENY_RSP = 0x27; // DENY RSP - Deny Response + public const byte TSBK_OSP_SCCB_EXP = 0x29; // SCCB - Secondary Control Channel Broadcast - Explicit + public const byte TSBK_OSP_GRP_AFF_Q = 0x2A; // GRP AFF Q - Group Affiliation Query + public const byte TSBK_OSP_LOC_REG_RSP = 0x2B; // LOC REG RSP - Location Registration Response + public const byte TSBK_OSP_U_REG_CMD = 0x2D; // U REG CMD - Unit Registration Command + public const byte TSBK_OSP_U_DEREG_ACK = 0x2F; // U DE REG ACK - Unit De-Registration Acknowledge + public const byte TSBK_OSP_QUE_RSP = 0x33; // QUE RSP - Queued Response + public const byte TSBK_OSP_IDEN_UP_VU = 0x34; // IDEN UP VU - Channel Identifier Update for VHF/UHF Bands + public const byte TSBK_OSP_SYS_SRV_BCAST = 0x38; // SYS SRV BCAST - System Service Broadcast + public const byte TSBK_OSP_SCCB = 0x39; // SCCB - Secondary Control Channel Broadcast + public const byte TSBK_OSP_RFSS_STS_BCAST = 0x3A; // RFSS STS BCAST - RFSS Status Broadcast + public const byte TSBK_OSP_NET_STS_BCAST = 0x3B; // NET STS BCAST - Network Status Broadcast + public const byte TSBK_OSP_ADJ_STS_BCAST = 0x3C; // ADJ STS BCAST - Adjacent Site Status Broadcast + public const byte TSBK_OSP_IDEN_UP = 0x3D; // IDEN UP - Channel Identifier Update + + // TSBK Motorola Outbound Signalling Packet (OSP) Opcode(s) + public const byte TSBK_OSP_MOT_GRG_ADD = 0x00; // MOT GRG ADD - Motorola / Group Regroup Add (Patch Supergroup) + public const byte TSBK_OSP_MOT_GRG_DEL = 0x01; // MOT GRG DEL - Motorola / Group Regroup Delete (Unpatch Supergroup) + public const byte TSBK_OSP_MOT_GRG_VCH_GRANT = 0x02; // MOT GRG GROUP VCH GRANT / Group Regroup Voice Channel Grant + public const byte TSBK_OSP_MOT_GRG_VCH_UPD = 0x03; // MOT GRG GROUP VCH GRANT UPD / Group Regroup Voice Channel Grant Update + public const byte TSBK_OSP_MOT_CC_BSI = 0x0B; // MOT CC BSI - Motorola / Control Channel Base Station Identifier + public const byte TSBK_OSP_MOT_PSH_CCH = 0x0E; // MOT PSH CCH - Motorola / Planned Control Channel Shutdown + + // TSBK Motorola Outbound Signalling Packet (OSP) Opcode(s) + public const byte TSBK_OSP_DVM_GIT_HASH = 0xFB; // + } // public class P25Defines +} // namespace fnecore.P25 diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..85b1c92 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,49 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + + +using System.Reflection; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("fnecore")] +[assembly: AssemblyDescription("Digital Voice Modem FNE Core Library")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("DVMProject Authors")] +[assembly: AssemblyProduct("Digital Voice Modem FNE Core Library")] +[assembly: AssemblyCopyright("Copyright (c) 2023 Bryan Biedenkapp N2PLL and DVMProject Authors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.*")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d769891 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Digital Voice Modem Fixed Network Equipment + +This is a library project that implements the basic communications layer for implementing DVM FNE clients (peers) and servers (masters). + +## License + +This project is licensed under the AGPLv3 License - see the [LICENSE](LICENSE) file for details. Use of this project is intended, for amateur and/or educational use ONLY. Any other use is at the risk of user and all commercial purposes is strictly discouraged. + diff --git a/RtpExtensionHeader.cs b/RtpExtensionHeader.cs new file mode 100644 index 0000000..36c8c7b --- /dev/null +++ b/RtpExtensionHeader.cs @@ -0,0 +1,89 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore +{ + /// + /// + /// + public class RtpExtensionHeader + { + protected int offset = 0; + protected ushort payloadLength; + + /// + /// Format of the extension header payload contained within the packet. + /// + public ushort PayloadType { get; set; } + + /// + /// Length of the extension header payload (in 32-bit units). + /// + public ushort PayloadLength { get => payloadLength; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + /// + public RtpExtensionHeader(int offset = 12) // 12 bytes is the length of the RTP Header + { + this.offset = offset; + PayloadType = 0; + payloadLength = 0; + } + + /// + /// Decode a RTP header. + /// + /// + public virtual bool Decode(byte[] data) + { + if (data == null) + return false; + + PayloadType = (ushort)((data[0 + offset] << 8) | (data[1 + offset] << 0)); // Payload Type + payloadLength = (ushort)((data[2 + offset] << 8) | (data[3 + offset] << 0)); // Payload Length + + return true; + } + + /// + /// Encode a RTP header. + /// + /// + public virtual void Encode(ref byte[] data) + { + if (data == null) + return; + + data[0 + offset] = (byte)((PayloadType >> 8) & 0xFFU); // Payload Type MSB + data[1 + offset] = (byte)((PayloadType >> 0) & 0xFFU); // Payload Type LSB + data[2 + offset] = (byte)((payloadLength >> 8) & 0xFFU); // Payload Length MSB + data[3 + offset] = (byte)((payloadLength >> 0) & 0xFFU); // Payload Length LSB + } + } // public class RtpExtensionHeader +} // namespace fnecore diff --git a/RtpFNEHeader.cs b/RtpFNEHeader.cs new file mode 100644 index 0000000..cac670d --- /dev/null +++ b/RtpFNEHeader.cs @@ -0,0 +1,128 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore +{ + /// + /// Represents an RTP header. + /// + public class RtpFNEHeader : RtpExtensionHeader + { + /// + /// Traffic payload packet CRC-16. + /// + public ushort CRC { get; set; } + + /// + /// Function. + /// + public byte Function { get; set; } + + /// + /// Sub-function. + /// + public byte SubFunction { get; set; } + + /// + /// Traffic Stream ID. + /// + public uint StreamID { get; set; } + + /// + /// Traffic Peer ID. + /// + public uint PeerID { get; set; } + + /// + /// Traffic Message Length. + /// + public uint MessageLength { get; set; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + /// + public RtpFNEHeader(int offset = 12) : base(offset) // 12 bytes is the length of the RTP Header + { + CRC = 0; + StreamID = 0; + PeerID = 0; + MessageLength = 0; + } + + /// + /// Decode a RTP header. + /// + /// + public override bool Decode(byte[] data) + { + if (data == null) + return false; + + if (!base.Decode(data)) + return false; + + if (payloadLength != Constants.RtpFNEHeaderExtLength) + return false; + if (PayloadType != Constants.DVMFrameStart) + return false; + + CRC = (ushort)((data[4 + offset] << 8) | (data[5 + offset] << 0)); // CRC-16 + Function = data[6 + offset]; // Function + SubFunction = data[7 + offset]; // Sub-Function + + StreamID = FneUtils.ToUInt32(data, 8 + offset); // Stream ID + PeerID = FneUtils.ToUInt32(data, 12 + offset); // Peer ID + MessageLength = FneUtils.ToUInt32(data, 16 + offset); // Message Length + + return true; + } + + /// + /// Encode a RTP header. + /// + /// + public override void Encode(ref byte[] data) + { + if (data == null) + return; + + PayloadType = Constants.DVMFrameStart; + payloadLength = Constants.RtpFNEHeaderExtLength; + base.Encode(ref data); + + data[4 + offset] = (byte)((CRC >> 8) & 0xFFU); // CRC-16 MSB + data[5 + offset] = (byte)((CRC >> 0) & 0xFFU); // CRC-16 LSB + data[6 + offset] = Function; // Function + data[7 + offset] = SubFunction; // Sub-Functon + + FneUtils.WriteBytes(StreamID, ref data, 8 + offset); // Stream ID + FneUtils.WriteBytes(PeerID, ref data, 12 + offset); // Peer ID + FneUtils.WriteBytes(MessageLength, ref data, 16 + offset); // Message Length + } + } // public class RtpFNEHeader : RtpExtensionHeader +} // namespace fnecore diff --git a/RtpHeader.cs b/RtpHeader.cs new file mode 100644 index 0000000..ec67708 --- /dev/null +++ b/RtpHeader.cs @@ -0,0 +1,165 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; + +namespace fnecore +{ + /// + /// + /// + public class RtpHeader + { + private Random rand; + private static DateTime start = DateTime.Now; + + private byte version; + private bool padding; + private byte cc; + + /// + /// RTP Protocol Version. + /// + public byte Version { get => version; } + + /// + /// Flag indicating if the packet has trailing padding. + /// + public bool Padding { get => padding; } + + /// + /// Flag indicating the presense of an extension header. + /// + public bool Extension { get; set; } + + /// + /// Count of contributing source IDs that follow the SSRC. + /// + public byte CSRCCount { get => cc; } + + /// + /// Flag indicating application-specific behavior. + /// + public bool Marker { get; set; } + + /// + /// Format of the payload contained within the packet. + /// + public byte PayloadType { get; set; } + + /// + /// Sequence number for the RTP packet. + /// + public ushort Sequence { get; set; } + + /// + /// RTP packet timestamp. + /// + public uint Timestamp { get; set; } + + /// + /// Synchronization Source ID. + /// + public uint SSRC { get; set; } + + /* + ** Methods + */ + /// + /// Initializes a new instance of the class. + /// + /// + public RtpHeader() + { + // bryanb: this isn't perfect -- but we don't need cryptographically + // secure numbers + rand = new Random(Guid.NewGuid().GetHashCode()); + + version = 2; + padding = false; + Extension = false; + cc = 0; + Marker = false; + PayloadType = 0; + Sequence = 0; + Timestamp = Constants.InvalidTS; + SSRC = 0; + } + + /// + /// Decode a RTP header. + /// + /// + /// + public bool Decode(byte[] data) + { + if (data == null) + return false; + + // check for invalid version + if (((data[0U] >> 6) & 0x03) != 0x02U) { + return false; + } + + version = (byte)((data[0] >> 6) & 0x03U); // RTP Version + padding = ((data[0] & 0x20) == 0x20U); // Padding Flag + Extension = ((data[0] & 0x10) == 0x10U); // Extension Header Flag + cc = (byte)(data[0] & 0x0F); // CSRC Count + Marker = ((data[1] & 0x80) == 0x80U); // Marker Flag + PayloadType = (byte)(data[1] & 0x7F); // Payload Type + Sequence = (ushort)((data[2] << 8) | (data[3] << 0)); // Sequence + + Timestamp = FneUtils.ToUInt32(data, 4); // Timestamp + SSRC = FneUtils.ToUInt32(data, 6); // Synchronization Source ID + + return true; + } + + /// + /// Encode a RTP header. + /// + /// + public void Encode(ref byte[] data) + { + if (data == null) + return; + + data[0] = (byte)((version << 6) + // RTP Version + (padding ? 0x20U : 0x00U) + // Padding Flag + (Extension ? 0x10U : 0x00U) + // Extension Header Flag + (cc & 0x0FU)); // CSRC Count + data[1] = (byte)((Marker ? 0x80U : 0x00U) + // Marker Flag + (PayloadType & 0x7FU)); // Payload Type + data[2] = (byte)((Sequence >> 8) & 0xFFU); // Sequence MSB + data[3] = (byte)((Sequence >> 0) & 0xFFU); // Sequence LSB + + TimeSpan timeSinceStart = DateTime.Now - start; + + uint ts = (uint)rand.Next(int.MinValue, int.MaxValue); + ulong microSeconds = (ulong)(timeSinceStart.Ticks * Constants.RtpGenericClockRate); + Timestamp = ts + (uint)(microSeconds / 1000000); + + FneUtils.WriteBytes(Timestamp, ref data, 4); // Timestamp + FneUtils.WriteBytes(SSRC, ref data, 8); // Synchronization Source ID + } + } // public class RtpHeader +} // namespace fnecore diff --git a/Udp.cs b/Udp.cs new file mode 100644 index 0000000..e1e853e --- /dev/null +++ b/Udp.cs @@ -0,0 +1,220 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace fnecore +{ + /// + /// Structure representing a raw UDP packet frame. + /// + /// "Frame" is used loosely here... + public struct UdpFrame + { + /// + /// + /// + public IPEndPoint Endpoint; + /// + /// + /// + public byte[] Message; + } // public struct UDPFrame + + /// + /// Base class from which all UDP classes are derived. + /// + public abstract class UdpBase + { + protected UdpClient client; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + protected UdpBase() + { + client = new UdpClient(); + } + + /// + /// + /// + /// + public async Task Receive() + { + UdpReceiveResult res = await client.ReceiveAsync(); + return new UdpFrame() + { + Message = res.Buffer, + Endpoint = res.RemoteEndPoint + }; + } + } // public abstract class UDPBase + + /// + /// Class implementing a UDP listener (server). + /// + public class UdpListener : UdpBase + { + private IPEndPoint listen; + + /* + ** Properties + */ + + /// + /// Gets the for this . + /// + public IPEndPoint EndPoint => listen; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public UdpListener(string address, int port) : this(new IPEndPoint(IPAddress.Parse(address), port)) + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// + public UdpListener(IPEndPoint endpoint) + { + listen = endpoint; + client = new UdpClient(listen); + } + + /// + /// + /// + /// + public void Send(UdpFrame frame) + { + client.Send(frame.Message, frame.Message.Length, frame.Endpoint); + } + + /// + /// + /// + /// + public async Task SendAsync(UdpFrame frame) + { + return await client.SendAsync(frame.Message, frame.Message.Length, frame.Endpoint); + } + } // public class UdpListener : UdpBase + + /// + /// + /// + public class UdpReceiver : UdpBase + { + private IPEndPoint endpoint; + + /* + ** Properties + */ + + /// + /// Gets the for this . + /// + public IPEndPoint EndPoint => endpoint; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public UdpReceiver() + { + /* stub */ + } + + /// + /// + /// + /// + /// + /// + public void Connect(string hostName, int port) + { + try + { + try + { + endpoint = new IPEndPoint(IPAddress.Parse(hostName), port); + } + catch + { + IPHostEntry entry = Dns.GetHostEntry(hostName); + if (entry.AddressList.Length > 0) + { + IPAddress address = entry.AddressList[0]; + endpoint = new IPEndPoint(address, port); + } + } + } + catch + { + return; + } + + client.Connect(endpoint.Address.ToString(), endpoint.Port); + } + + /// + /// + /// + /// + /// + public void Connect(IPEndPoint endpoint) + { + UdpReceiver recv = new UdpReceiver(); + this.endpoint = endpoint; + client.Connect(endpoint.Address.ToString(), endpoint.Port); + } + + /// + /// + /// + /// + public void Send(UdpFrame frame) + { + client.Send(frame.Message, frame.Message.Length); + } + } // public class UdpReceiver : UdpBase +} // namespace fnecore diff --git a/Utility/CommandOptions.cs b/Utility/CommandOptions.cs new file mode 100644 index 0000000..f404b3c --- /dev/null +++ b/Utility/CommandOptions.cs @@ -0,0 +1,2027 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ +// +// Implements a class to help manage command line options and flags. +// Created Aug 30, 2012 +// +// +// Options.cs +// +// Authors: +// Jonathan Pryor +// Federico Di Gregorio +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// Copyright (C) 2009 Federico Di Gregorio. +// +// 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. +// + +// Compile With: +// gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll +// gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll +// +// The LINQ version just changes the implementation of +// OptionSet.Parse(IEnumerable), and confers no semantic changes. + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// NDesk.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is a delegate that is +// invoked when the format string is matched. +// +// Option format strings: +// Regex-like BNF Grammar: +// name: .+ +// type: [=:] +// sep: ( [^{}]+ | '{' .+ '}' )? +// aliases: ( name type sep ) ( '|' name type sep )* +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. `=' or `:' need only be defined on one +// alias, but if they are provided on more than one they must be consistent. +// +// Each alias portion may also end with a "key/value separator", which is used +// to split option values if the option accepts > 1 value. If not specified, +// it defaults to '=' and ':'. If specified, it can be any character except +// '{' and '}' OR the *string* between '{' and '}'. If no separator should be +// used (i.e. the separate values should be distinct arguments), then "{}" +// should be used as the separator. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The current option requires a value (i.e. not a Option type of ':') +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options are a single character +// - at most one of the bundled options accepts a value, and the value +// provided starts from the next character to the end of the string. +// +// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' +// as '-Dname=value'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported and encouraged: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +namespace fnecore.Utility +{ + /// + /// + public static class StringCoda + { + /* + ** Methods + */ + + /// + /// + public static IEnumerable WrappedLines(string self, params int[] widths) + { + IEnumerable w = widths; + return WrappedLines(self, w); + } + + /// + /// + /// + /// + /// + public static IEnumerable WrappedLines(string self, IEnumerable widths) + { + if (widths == null) + throw new ArgumentNullException("widths"); + return CreateWrappedLinesIterator(self, widths); + } + + /// + /// + /// + /// + /// + private static IEnumerable CreateWrappedLinesIterator(string self, IEnumerable widths) + { + if (string.IsNullOrEmpty(self)) + { + yield return string.Empty; + yield break; + } + + using (IEnumerator ewidths = widths.GetEnumerator()) + { + bool? hw = null; + int width = GetNextWidth(ewidths, int.MaxValue, ref hw); + int start = 0, end; + do + { + end = GetLineEnd(start, width, self); + char c = self[end - 1]; + if (char.IsWhiteSpace(c)) + --end; + bool needContinuation = end != self.Length && !IsEolChar(c); + string continuation = ""; + if (needContinuation) + { + --end; + continuation = "-"; + } + + string line = self.Substring(start, end - start) + continuation; + yield return line; + start = end; + if (char.IsWhiteSpace(c)) + ++start; + width = GetNextWidth(ewidths, width, ref hw); + } while (start < self.Length); + } + } + + /// + /// + /// + /// + /// + /// + private static int GetNextWidth(IEnumerator ewidths, int curWidth, ref bool? eValid) + { + if (!eValid.HasValue || eValid.HasValue && eValid.Value) + { + curWidth = (eValid = ewidths.MoveNext()).Value ? ewidths.Current : curWidth; + + // '.' is any character, - is for a continuation + const string minWidth = ".-"; + if (curWidth < minWidth.Length) + { + throw new ArgumentOutOfRangeException("widths", + string.Format("Element must be >= {0}, was {1}.", minWidth.Length, curWidth)); + } + + return curWidth; + } + + // no more elements, use the last element. + return curWidth; + } + + /// + /// + /// + /// + private static bool IsEolChar(char c) + { + return !char.IsLetterOrDigit(c); + } + + /// + /// + /// + /// + /// + /// + private static int GetLineEnd(int start, int length, string description) + { + int end = Math.Min(start + length, description.Length); + int sep = -1; + for (int i = start; i < end; ++i) + { + if (description[i] == '\n') + return i + 1; + if (IsEolChar(description[i])) + sep = i + 1; + } + + if (sep == -1 || end == description.Length) + return end; + return sep; + } + } // internal static class StringCoda + + /// + /// + public class OptionValueCollection : IList, IList + { + private readonly OptionContext c; + private readonly List values = new List(); + + /* + ** Properties + */ + + /// + /// + public int Count + { + get { return values.Count; } + } + + /// + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// + /// + /// + object IList.this[int index] + { + get { return this[index]; } + set { (values as IList)[index] = value; } + } + + /// + /// + /// + /// + public string this[int index] + { + get + { + AssertValid(index); + return index >= values.Count ? null : values[index]; + } + set { values[index] = value; } + } + + /// + /// + bool ICollection.IsSynchronized + { + get { return (values as ICollection).IsSynchronized; } + } + + /// + /// + object ICollection.SyncRoot + { + get { return (values as ICollection).SyncRoot; } + } + + /// + /// + bool IList.IsFixedSize + { + get { return false; } + } + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + internal OptionValueCollection(OptionContext c) + { + this.c = c; + } + + /// + /// + /// + /// + void ICollection.CopyTo(Array array, int index) + { + (values as ICollection).CopyTo(array, index); + } + + /// + /// + public void Clear() + { + values.Clear(); + } + + /// + /// + /// + IEnumerator IEnumerable.GetEnumerator() + { + return values.GetEnumerator(); + } + + /// + /// + /// + /// + int IList.Add(object value) + { + return (values as IList).Add(value); + } + + /// + /// + /// + /// + bool IList.Contains(object value) + { + return (values as IList).Contains(value); + } + + /// + /// + /// + /// + int IList.IndexOf(object value) + { + return (values as IList).IndexOf(value); + } + + /// + /// + /// + /// + void IList.Insert(int index, object value) + { + (values as IList).Insert(index, value); + } + + /// + /// + /// + void IList.Remove(object value) + { + (values as IList).Remove(value); + } + + /// + /// + /// + void IList.RemoveAt(int index) + { + (values as IList).RemoveAt(index); + } + + /// + /// + /// + public void Add(string item) + { + values.Add(item); + } + + /// + /// + /// + /// + public bool Contains(string item) + { + return values.Contains(item); + } + + /// + /// + /// + /// + public void CopyTo(string[] array, int arrayIndex) + { + values.CopyTo(array, arrayIndex); + } + + /// + /// + /// + /// + public bool Remove(string item) + { + return values.Remove(item); + } + + /// + /// + /// + public IEnumerator GetEnumerator() + { + return values.GetEnumerator(); + } + + /// + /// + /// + /// + public int IndexOf(string item) + { + return values.IndexOf(item); + } + + /// + /// + /// + /// + public void Insert(int index, string item) + { + values.Insert(index, item); + } + + /// + /// + /// + public void RemoveAt(int index) + { + values.RemoveAt(index); + } + + /// + /// + /// + private void AssertValid(int index) + { + if (c.Option == null) + throw new InvalidOperationException("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException("index"); + if (c.Option.OptionValueType == OptionValueType.Required && index >= values.Count) + throw new OptionException(string.Format(c.OptionSet.MessageLocalizer("Missing required value for option '{0}'."), c.OptionName), c.OptionName); + } + + /// + /// + /// + public List ToList() + { + return new List(values); + } + + /// + /// + /// + public string[] ToArray() + { + return values.ToArray(); + } + + /// + public override string ToString() + { + return string.Join(", ", values.ToArray()); + } + } // internal class OptionValueCollection : IList, IList + + /// + /// + public class OptionContext + { + /* + ** Properties + */ + + /// + /// + public Option Option { get; set; } + + /// + /// + public string OptionName { get; set; } + + /// + /// + public int OptionIndex { get; set; } + + /// + /// + public OptionSet OptionSet { get; } + + /// + /// + public OptionValueCollection OptionValues { get; } + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + public OptionContext(OptionSet set) + { + OptionSet = set; + OptionValues = new OptionValueCollection(this); + } + } // internal class OptionContext + + /// + /// Enumeration of the various command line option types. + /// + public enum OptionValueType + { + None, + Optional, + Required + } + + /// + /// + public abstract class Option + { + private static readonly char[] NameTerminator = { '=', ':' }; + + /* + ** Properties + */ + + /// + /// + public string Prototype { get; } + + /// + /// + public string Description { get; } + + /// + /// + public OptionValueType OptionValueType { get; } + + /// + /// + public int MaxValueCount { get; } + + /// + /// + internal string[] Names { get; } + + /// + /// + internal string[] ValueSeparators { get; private set; } + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + /// + protected Option(string prototype, string description) : this(prototype, description, 1) + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + protected Option(string prototype, string description, int maxValueCount) + { + if (prototype == null) + throw new ArgumentNullException("prototype"); + if (prototype.Length == 0) + throw new ArgumentException("Cannot be the empty string.", "prototype"); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException("maxValueCount"); + + Prototype = prototype; + Description = description; + MaxValueCount = maxValueCount; + Names = this is OptionSet.Category + + // append GetHashCode() so that "duplicate" categories have distinct + // names, e.g. adding multiple "" categories should be valid. + ? + new[] { prototype + GetHashCode() } : + prototype.Split('|'); + + if (this is OptionSet.Category) + return; + + OptionValueType = ParsePrototype(); + + if (MaxValueCount == 0 && OptionValueType != OptionValueType.None) + throw new ArgumentException("Cannot provide maxValueCount of 0 for OptionValueType.Required or " + "OptionValueType.Optional.", + "maxValueCount"); + + if (OptionValueType == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException(string.Format("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + "maxValueCount"); + + if (Array.IndexOf(Names, "<>") >= 0 && (Names.Length == 1 && OptionValueType != OptionValueType.None || Names.Length > 1 && MaxValueCount > 1)) + throw new ArgumentException("The default option handler '<>' cannot require values.", "prototype"); + } + + /// + /// + /// + public string[] GetNames() + { + return (string[])Names.Clone(); + } + + /// + /// + /// + public string[] GetValueSeparators() + { + if (ValueSeparators == null) + return new string[0]; + return (string[])ValueSeparators.Clone(); + } + + /// + /// + /// + /// + /// + /// + protected static T Parse(string value, OptionContext c) + { + Type tt = typeof(T); + bool nullable = tt.IsValueType && tt.IsGenericType && !tt.IsGenericTypeDefinition && tt.GetGenericTypeDefinition() == typeof(Nullable<>); + Type targetType = nullable ? tt.GetGenericArguments()[0] : typeof(T); + TypeConverter conv = TypeDescriptor.GetConverter(targetType); + T t = default(T); + try + { + if (value != null) + t = (T)conv.ConvertFromString(value); + } + catch (Exception e) + { + throw new OptionException(string.Format(c.OptionSet.MessageLocalizer("Could not convert string `{0}' to type {1} for option `{2}'."), + value, targetType.Name, c.OptionName), c.OptionName, e); + } + + return t; + } + + /// + /// + /// + private OptionValueType ParsePrototype() + { + char type = '\0'; + List seps = new List(); + for (int i = 0; i < Names.Length; ++i) + { + string name = Names[i]; + if (name.Length == 0) + throw new ArgumentException("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny(NameTerminator); + if (end == -1) + continue; + Names[i] = name.Substring(0, end); + if (type == '\0' || type == name[end]) + type = name[end]; + else + throw new ArgumentException(string.Format("Conflicting option types: '{0}' vs. '{1}'.", type, name[end]), "prototype"); + + AddSeparators(name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (MaxValueCount <= 1 && seps.Count != 0) + throw new ArgumentException(string.Format("Cannot provide key/value separators for Options taking {0} value(s).", + MaxValueCount), "prototype"); + + if (MaxValueCount > 1) + { + if (seps.Count == 0) + ValueSeparators = new[] { ":", "=" }; + else if (seps.Count == 1 && seps[0].Length == 0) + ValueSeparators = null; + else + ValueSeparators = seps.ToArray(); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + /// + /// + /// + /// + /// + private static void AddSeparators(string name, int end, ICollection seps) + { + int start = -1; + for (int i = end + 1; i < name.Length; ++i) + { + switch (name[i]) + { + case '{': + if (start != -1) + throw new ArgumentException(string.Format("Ill-formed name/value separator found in \"{0}\".", name), "prototype"); + + start = i + 1; + break; + + case '}': + if (start == -1) + throw new ArgumentException(string.Format("Ill-formed name/value separator found in \"{0}\".", name), "prototype"); + + seps.Add(name.Substring(start, i - start)); + start = -1; + break; + + default: + if (start == -1) + seps.Add(name[i].ToString()); + break; + } + } + + if (start != -1) + throw new ArgumentException(string.Format("Ill-formed name/value separator found in \"{0}\".", name), "prototype"); + } + + /// + /// + /// + public void Invoke(OptionContext c) + { + OnParseComplete(c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear(); + } + + /// + /// + /// + protected abstract void OnParseComplete(OptionContext c); + + /// + public override string ToString() + { + return Prototype; + } + } // internal abstract class Option + + /// + /// + public abstract class ArgumentSource + { + /* + ** Properties + */ + + /// + /// + public abstract string Description { get; } + + /* + ** Methods + */ + + /// + /// + /// + public abstract string[] GetNames(); + + /// + /// + /// + /// + /// + public abstract bool GetArguments(string value, out IEnumerable replacement); + + /// + /// + /// + /// + public static IEnumerable GetArgumentsFromFile(string file) + { + return GetArguments(File.OpenText(file), true); + } + + /// + /// + /// + /// + public static IEnumerable GetArguments(TextReader reader) + { + return GetArguments(reader, false); + } + + /// + /// + /// + /// + /// + private static IEnumerable GetArguments(TextReader reader, bool close) + { + try + { + StringBuilder arg = new StringBuilder(); + + string line; + while ((line = reader.ReadLine()) != null) + { + int t = line.Length; + + for (int i = 0; i < t; i++) + { + char c = line[i]; + + if (c == '"' || c == '\'') + { + char end = c; + + for (i++; i < t; i++) + { + c = line[i]; + + if (c == end) + break; + arg.Append(c); + } + } + else if (c == ' ') + { + if (arg.Length > 0) + { + yield return arg.ToString(); + arg.Length = 0; + } + } + else + arg.Append(c); + } + + if (arg.Length > 0) + { + yield return arg.ToString(); + arg.Length = 0; + } + } + } + finally + { + if (close) + reader.Close(); + } + } + } // internal abstract class ArgumentSource + + /// + /// + internal class ResponseFileSource : ArgumentSource + { + /* + ** Properties + */ + + /// + public override string Description + { + get { return "Read response file for more options."; } + } + + /* + ** Methods + */ + + /// + public override string[] GetNames() + { + return new[] { "@file" }; + } + + /// + public override bool GetArguments(string value, out IEnumerable replacement) + { + if (string.IsNullOrEmpty(value) || !value.StartsWith("@")) + { + replacement = null; + return false; + } + + replacement = GetArgumentsFromFile(value.Substring(1)); + return true; + } + } // internal class ResponseFileSource : ArgumentSource + + /// + /// + [Serializable] + public class OptionException : Exception + { + /* + ** Properties + */ + + /// + /// + public string OptionName { get; } + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public OptionException() + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public OptionException(string message, string optionName) : base(message) + { + OptionName = optionName; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public OptionException(string message, string optionName, Exception innerException) : base(message, + innerException) + { + OptionName = optionName; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + protected OptionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + OptionName = info.GetString("OptionName"); + } + + /// + [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("OptionName", OptionName); + } + } // internal class OptionException : Exception + + public delegate void OptionAction(TKey key, TValue value); + + /// + /// + public class OptionSet : KeyedCollection + { + private const int OptionWidth = 29; + private const int Description_FirstWidth = 80 - OptionWidth; + private const int Description_RemWidth = 80 - OptionWidth - 2; + + private readonly List sources = new List(); + + private readonly Regex ValueOption = new Regex(@"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); + + /* + ** Classes + */ + + /// + /// + internal sealed class Category : Option + { + /** + * Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + /// Prototype starts with '=' because this is an invalid prototype + /// (see Option.ParsePrototype(), and thus it'll prevent Category + /// instances from being accidentally used as normal options. + /// + /// + public Category(string description) : base("=:Category:= " + description, description) + { + /* stub */ + } + + /// + protected override void OnParseComplete(OptionContext c) + { + throw new NotSupportedException("Category.OnParseComplete should not be invoked."); + } + } // internal sealed class Category : Option + + /// + /// + private sealed class ActionOption : Option + { + private readonly Action action; + + /** + * Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public ActionOption(string prototype, string description, int count, Action action) : + base(prototype, description, count) + { + if (action == null) + throw new ArgumentNullException("action"); + this.action = action; + } + + /// + protected override void OnParseComplete(OptionContext c) + { + action(c.OptionValues); + } + } // private sealed class ActionOption : Option + + /// + /// + /// + private sealed class ActionOption : Option + { + private readonly Action action; + + /** + * Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public ActionOption(string prototype, string description, Action action) : base(prototype, description, 1) + { + if (action == null) + throw new ArgumentNullException("action"); + this.action = action; + } + + /// + protected override void OnParseComplete(OptionContext c) + { + action(Parse(c.OptionValues[0], c)); + } + } // private sealed class ActionOption : Option + + /// + /// + /// + /// + private sealed class ActionOption : Option + { + private readonly OptionAction action; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public ActionOption(string prototype, string description, OptionAction action) : base(prototype, description, 2) + { + if (action == null) + throw new ArgumentNullException("action"); + this.action = action; + } + + /// + protected override void OnParseComplete(OptionContext c) + { + action(Parse(c.OptionValues[0], c), Parse(c.OptionValues[1], c)); + } + } // private sealed class ActionOption : Option + + /// + /// + private class ArgumentEnumerator : IEnumerable + { + private readonly List> sources = new List>(); + + /** + * Methods + */ + + /// + /// Initializes a new instance of the class. + /// + /// + public ArgumentEnumerator(IEnumerable arguments) + { + sources.Add(arguments.GetEnumerator()); + } + + /// + /// + /// + public IEnumerator GetEnumerator() + { + do + { + IEnumerator c = sources[sources.Count - 1]; + if (c.MoveNext()) + yield return c.Current; + else + { + c.Dispose(); + sources.RemoveAt(sources.Count - 1); + } + } while (sources.Count > 0); + } + + /// + /// + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// + /// + public void Add(IEnumerable arguments) + { + sources.Add(arguments.GetEnumerator()); + } + } // private class ArgumentEnumerator : IEnumerable + + /* + ** Properties + */ + + /// + /// + public Converter MessageLocalizer { get; } + + /// + /// + public ReadOnlyCollection ArgumentSources { get; } + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public OptionSet() : this(delegate (string f) { return f; }) + { + /* stub */ + } + + /// + /// Initializes a new instance of the class. + /// + /// + public OptionSet(Converter localizer) + { + MessageLocalizer = localizer; + ArgumentSources = new ReadOnlyCollection(sources); + } + + /// + protected override string GetKeyForItem(Option item) + { + if (item == null) + throw new ArgumentNullException("option"); + if (item.Names != null && item.Names.Length > 0) + return item.Names[0]; + + // This should never happen, as it's invalid for Option to be + // constructed w/o any names. + throw new InvalidOperationException("Option has no names!"); + } + + /// + /// + /// + /// + [Obsolete("Use KeyedCollection.this[string]")] + protected Option GetOptionForName(string option) + { + if (option == null) + throw new ArgumentNullException("option"); + try + { + return base[option]; + } + catch (KeyNotFoundException) + { + return null; + } + } + + /// + protected override void InsertItem(int index, Option item) + { + base.InsertItem(index, item); + AddImpl(item); + } + + /// + protected override void RemoveItem(int index) + { + Option p = Items[index]; + base.RemoveItem(index); + + // KeyedCollection.RemoveItem() handles the 0th item + for (int i = 1; i < p.Names.Length; ++i) + Dictionary.Remove(p.Names[i]); + } + + /// + protected override void SetItem(int index, Option item) + { + base.SetItem(index, item); + AddImpl(item); + } + + /// + /// + /// + private void AddImpl(Option option) + { + if (option == null) + throw new ArgumentNullException("option"); + List added = new List(option.Names.Length); + try + { + // KeyedCollection.InsertItem/SetItem handle the 0th name. + for (int i = 1; i < option.Names.Length; ++i) + { + Dictionary.Add(option.Names[i], option); + added.Add(option.Names[i]); + } + } + catch (Exception) + { + foreach (string name in added) + Dictionary.Remove(name); + throw; + } + } + + /// + /// + /// + /// + public OptionSet Add(string header) + { + if (header == null) + throw new ArgumentNullException("header"); + Add(new Category(header)); + return this; + } + + /// + /// + /// + /// + public new OptionSet Add(Option option) + { + base.Add(option); + return this; + } + + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, Action action) + { + return Add(prototype, null, action); + } + + /// + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, string description, Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + Option p = new ActionOption(prototype, description, 1, delegate (OptionValueCollection v) { action(v[0]); }); + base.Add(p); + return this; + } + + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, OptionAction action) + { + return Add(prototype, null, action); + } + + /// + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, string description, OptionAction action) + { + if (action == null) + throw new ArgumentNullException("action"); + Option p = new ActionOption(prototype, description, 2, + delegate (OptionValueCollection v) { action(v[0], v[1]); }); + base.Add(p); + return this; + } + + /// + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, Action action) + { + return Add(prototype, null, action); + } + + /// + /// + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, string description, Action action) + { + return Add(new ActionOption(prototype, description, action)); + } + + /// + /// + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, OptionAction action) + { + return Add(prototype, null, action); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public OptionSet Add(string prototype, string description, OptionAction action) + { + return Add(new ActionOption(prototype, description, action)); + } + + /// + /// + /// + /// + public OptionSet Add(ArgumentSource source) + { + if (source == null) + throw new ArgumentNullException("source"); + sources.Add(source); + return this; + } + + /// + /// + /// + protected virtual OptionContext CreateOptionContext() + { + return new OptionContext(this); + } + + /// + /// + /// + /// + public List Parse(IEnumerable arguments) + { + if (arguments == null) + throw new ArgumentNullException("arguments"); + OptionContext c = CreateOptionContext(); + c.OptionIndex = -1; + bool process = true; + List unprocessed = new List(); + Option def = Contains("<>") ? this["<>"] : null; + ArgumentEnumerator ae = new ArgumentEnumerator(arguments); + foreach (string argument in ae) + { + ++c.OptionIndex; + if (argument == "--") + { + process = false; + continue; + } + + if (!process) + { + Unprocessed(unprocessed, def, c, argument); + continue; + } + + if (AddSource(ae, argument)) + continue; + if (!Parse(argument, c)) + Unprocessed(unprocessed, def, c, argument); + } + + if (c.Option != null) + c.Option.Invoke(c); + return unprocessed; + } + + /// + /// + /// + /// + /// + private bool AddSource(ArgumentEnumerator ae, string argument) + { + foreach (ArgumentSource source in sources) + { + IEnumerable replacement; + if (!source.GetArguments(argument, out replacement)) + continue; + ae.Add(replacement); + return true; + } + + return false; + } + + /// + /// + /// + /// + /// + /// + /// + private static bool Unprocessed(ICollection extra, Option def, OptionContext c, string argument) + { + if (def == null) + { + extra.Add(argument); + return false; + } + + c.OptionValues.Add(argument); + c.Option = def; + c.Option.Invoke(c); + return false; + } + + /// + /// + /// + /// + /// + /// + /// + /// + protected bool GetOptionParts(string argument, out string flag, out string name, out string sep, + out string value) + { + if (argument == null) + throw new ArgumentNullException("argument"); + + flag = name = sep = value = null; + Match m = ValueOption.Match(argument); + if (!m.Success) return false; + flag = m.Groups["flag"].Value; + name = m.Groups["name"].Value; + if (m.Groups["sep"].Success && m.Groups["value"].Success) + { + sep = m.Groups["sep"].Value; + value = m.Groups["value"].Value; + } + + return true; + } + + /// + /// + /// + /// + /// + protected virtual bool Parse(string argument, OptionContext c) + { + if (c.Option != null) + { + ParseValue(argument, c); + return true; + } + + string f, n, s, v; + if (!GetOptionParts(argument, out f, out n, out s, out v)) + return false; + + Option p; + if (Contains(n)) + { + p = this[n]; + c.OptionName = f + n; + c.Option = p; + switch (p.OptionValueType) + { + case OptionValueType.None: + c.OptionValues.Add(n); + c.Option.Invoke(c); + break; + + case OptionValueType.Optional: + case OptionValueType.Required: + ParseValue(v, c); + break; + } + + return true; + } + + // no match; is it a bool option? + if (ParseBool(argument, n, c)) + return true; + + // is it a bundled option? + if (ParseBundledValue(f, string.Concat(n + s + v), c)) + return true; + + return false; + } + + /// + /// + /// + /// + private void ParseValue(string option, OptionContext c) + { + if (option != null) + { + foreach (string o in c.Option.ValueSeparators != null ? + option.Split(c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None) : new[] { option }) + c.OptionValues.Add(o); + } + + if (c.OptionValues.Count == c.Option.MaxValueCount || c.Option.OptionValueType == OptionValueType.Optional) + c.Option.Invoke(c); + else if (c.OptionValues.Count > c.Option.MaxValueCount) + throw new OptionException(MessageLocalizer(string.Format("Error: Found {0} option values when expecting {1}.", + c.OptionValues.Count, c.Option.MaxValueCount)), c.OptionName); + } + + /// + /// + /// + /// + /// + /// + private bool ParseBool(string option, string n, OptionContext c) + { + Option p; + string rn; + if (n.Length >= 1 && (n[n.Length - 1] == '+' || n[n.Length - 1] == '-') && + Contains(rn = n.Substring(0, n.Length - 1))) + { + p = this[rn]; + string v = n[n.Length - 1] == '+' ? option : null; + c.OptionName = option; + c.Option = p; + c.OptionValues.Add(v); + p.Invoke(c); + return true; + } + + return false; + } + + /// + /// + /// + /// + /// + /// + private bool ParseBundledValue(string f, string n, OptionContext c) + { + if (f != "-") + return false; + for (int i = 0; i < n.Length; ++i) + { + Option p; + string opt = f + n[i]; + string rn = n[i].ToString(); + if (!Contains(rn)) + { + if (i == 0) + return false; + throw new OptionException( + string.Format(MessageLocalizer("Cannot bundle unregistered option '{0}'."), opt), opt); + } + + p = this[rn]; + switch (p.OptionValueType) + { + case OptionValueType.None: + Invoke(c, opt, n, p); + break; + + case OptionValueType.Optional: + case OptionValueType.Required: + { + string v = n.Substring(i + 1); + c.Option = p; + c.OptionName = opt; + ParseValue(v.Length != 0 ? v : null, c); + return true; + } + default: + throw new InvalidOperationException("Unknown OptionValueType: " + p.OptionValueType); + } + } + + return true; + } + + /// + /// + /// + /// + /// + /// + private static void Invoke(OptionContext c, string name, string value, Option option) + { + c.OptionName = name; + c.Option = option; + c.OptionValues.Add(value); + option.Invoke(c); + } + + /// + /// + /// + public void WriteOptionDescriptions(TextWriter o) + { + foreach (Option p in this) + { + int written = 0; + + Category c = p as Category; + if (c != null) + { + WriteDescription(o, p.Description, "", 80, 80); + continue; + } + + if (!WriteOptionPrototype(o, p, ref written)) + continue; + + if (written < OptionWidth) + o.Write(new string(' ', OptionWidth - written)); + else + { + o.WriteLine(); + o.Write(new string(' ', OptionWidth)); + } + + WriteDescription(o, p.Description, new string(' ', OptionWidth + 2), Description_FirstWidth, Description_RemWidth); + } + + foreach (ArgumentSource s in sources) + { + string[] names = s.GetNames(); + if (names == null || names.Length == 0) + continue; + + int written = 0; + + Write(o, ref written, " "); + Write(o, ref written, names[0]); + for (int i = 1; i < names.Length; ++i) + { + Write(o, ref written, ", "); + Write(o, ref written, names[i]); + } + + if (written < OptionWidth) + o.Write(new string(' ', OptionWidth - written)); + else + { + o.WriteLine(); + o.Write(new string(' ', OptionWidth)); + } + + WriteDescription(o, s.Description, new string(' ', OptionWidth + 2), Description_FirstWidth, Description_RemWidth); + } + } + + /// + /// + /// + /// + /// + /// + /// + private void WriteDescription(TextWriter o, string value, string prefix, int firstWidth, int remWidth) + { + bool indent = false; + foreach (string line in GetLines(MessageLocalizer(GetDescription(value)), firstWidth, remWidth)) + { + if (indent) + o.Write(prefix); + o.WriteLine(line); + indent = true; + } + } + + /// + /// + /// + /// + /// + /// + private bool WriteOptionPrototype(TextWriter o, Option p, ref int written) + { + string[] names = p.Names; + + int i = GetNextOptionIndex(names, 0); + if (i == names.Length) + return false; + + if (names[i].Length == 1) + { + Write(o, ref written, " -"); + Write(o, ref written, names[0]); + } + else + { + Write(o, ref written, " --"); + Write(o, ref written, names[0]); + } + + for (i = GetNextOptionIndex(names, i + 1); i < names.Length; i = GetNextOptionIndex(names, i + 1)) + { + Write(o, ref written, ", "); + Write(o, ref written, names[i].Length == 1 ? "-" : "--"); + Write(o, ref written, names[i]); + } + + if (p.OptionValueType == OptionValueType.Optional || p.OptionValueType == OptionValueType.Required) + { + if (p.OptionValueType == OptionValueType.Optional) Write(o, ref written, MessageLocalizer("[")); + Write(o, ref written, MessageLocalizer("=" + GetArgumentName(0, p.MaxValueCount, p.Description))); + string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 ? p.ValueSeparators[0] : " "; + for (int c = 1; c < p.MaxValueCount; ++c) + Write(o, ref written, MessageLocalizer(sep + GetArgumentName(c, p.MaxValueCount, p.Description))); + if (p.OptionValueType == OptionValueType.Optional) Write(o, ref written, MessageLocalizer("]")); + } + + return true; + } + + /// + /// + /// + /// + /// + private static int GetNextOptionIndex(string[] names, int i) + { + while (i < names.Length && names[i] == "<>") ++i; + return i; + } + + /// + /// + /// + /// + /// + private static void Write(TextWriter o, ref int n, string s) + { + n += s.Length; + o.Write(s); + } + + /// + /// + /// + /// + /// + /// + private static string GetArgumentName(int index, int maxIndex, string description) + { + if (description == null) + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + string[] nameStart; + if (maxIndex == 1) + nameStart = new[] { "{0:", "{" }; + else + nameStart = new[] { "{" + index + ":" }; + for (int i = 0; i < nameStart.Length; ++i) + { + int start, j = 0; + do + { + start = description.IndexOf(nameStart[i], j); + } while (start >= 0 && j != 0 ? description[j++ - 1] == '{' : false); + + if (start == -1) + continue; + int end = description.IndexOf("}", start); + if (end == -1) + continue; + return description.Substring(start + nameStart[i].Length, end - start - nameStart[i].Length); + } + + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + } + + /// + /// + /// + /// + private static string GetDescription(string description) + { + if (description == null) + return string.Empty; + StringBuilder sb = new StringBuilder(description.Length); + int start = -1; + for (int i = 0; i < description.Length; ++i) + { + switch (description[i]) + { + case '{': + if (i == start) + { + sb.Append('{'); + start = -1; + } + else if (start < 0) start = i + 1; + + break; + + case '}': + if (start < 0) + { + if (i + 1 == description.Length || description[i + 1] != '}') + throw new InvalidOperationException("Invalid option description: " + description); + ++i; + sb.Append("}"); + } + else + { + sb.Append(description.Substring(start, i - start)); + start = -1; + } + + break; + + case ':': + if (start < 0) + goto default; + start = i + 1; + break; + + default: + if (start < 0) + sb.Append(description[i]); + break; + } + } + + return sb.ToString(); + } + + /// + /// + /// + /// + /// + /// + private static IEnumerable GetLines(string description, int firstWidth, int remWidth) + { + return StringCoda.WrappedLines(description, firstWidth, remWidth); + } + } // internal class OptionSet : KeyedCollection +} // namespace fnecore.Utility diff --git a/Utility/SemVersion.cs b/Utility/SemVersion.cs new file mode 100644 index 0000000..6d6f49f --- /dev/null +++ b/Utility/SemVersion.cs @@ -0,0 +1,688 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the A Semantic Version Library for .Net project. (https://github.com/maxhauser/semver) +// Copyright (c) 2013 Max Hauser +// Licensed under the MIT License (http://www.opensource.org/licenses/MIT) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 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 Affero General Public License for more details. +*/ + +using System; +using System.Globalization; +using System.Text; +using System.Reflection; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text.RegularExpressions; + +namespace fnecore.Utility +{ + /// + /// + /// + internal static class IntExtensions + { + /// + /// The number of digits in a non-negative number. Returns 1 for all + /// negative numbers. That is ok because we are using it to calculate + /// string length for a for numbers that + /// aren't supposed to be negative, but when they are it is just a little + /// slower. + /// + /// + /// This approach is based on https://stackoverflow.com/a/51099524/268898 + /// where the poster offers performance benchmarks showing this is the + /// fastest way to get a number of digits. + /// + public static int Digits(this int n) + { + if (n < 10) + return 1; + if (n < 100) + return 2; + if (n < 1_000) + return 3; + if (n < 10_000) + return 4; + if (n < 100_000) + return 5; + if (n < 1_000_000) + return 6; + if (n < 10_000_000) + return 7; + if (n < 100_000_000) + return 8; + if (n < 1_000_000_000) + return 9; + + return 10; + } + } // internal static class IntExtensions + + /// + /// A semantic version implementation. + /// Conforms with v2.0.0 of http://semver.org + /// + [Serializable] + public sealed class SemVersion : IComparable, IComparable, ISerializable + { + private static readonly Regex ParseEx = new Regex(@"^(?\d+)" + @"(?>\.(?\d+))?" + + @"(?>\.(?\d+))?" + @"(?>\-(?
[0-9A-Za-z\-\.]+))?" + @"(?>\+(?[0-9A-Za-z\-\.]+))?$",
+                RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.ExplicitCapture,
+                TimeSpan.FromSeconds(0.5));
+
+        /*
+        ** Properties
+        */
+
+        /// 
+        /// Gets the major version.
+        /// 
+        /// 
+        /// The major version.
+        /// 
+        public int Major { get; }
+
+        /// 
+        /// Gets the minor version.
+        /// 
+        /// 
+        /// The minor version.
+        /// 
+        public int Minor { get; }
+
+        /// 
+        /// Gets the patch version.
+        /// 
+        /// 
+        /// The patch version.
+        /// 
+        public int Patch { get; }
+
+        /// 
+        /// Gets the prerelease version.
+        /// 
+        /// 
+        /// The prerelease version. Empty string if this is a release version.
+        /// 
+        public string Prerelease { get; }
+
+        /// 
+        /// Gets the build metadata.
+        /// 
+        /// 
+        /// The build metadata. Empty string if there is no build metadata.
+        /// 
+        public string Build { get; }
+
+        /*
+        ** Operators
+        */
+
+#pragma warning disable CA2225 // Operator overloads have named alternates
+        /// 
+        /// Implicit conversion from  to .
+        /// 
+        /// The semantic version.
+        /// The  object.
+        /// The  is .
+        /// The version number has an invalid format.
+        /// The Major, Minor, or Patch versions are larger than int.MaxValue.
+        public static implicit operator SemVersion(string version)
+#pragma warning restore CA2225 // Operator overloads have named alternates
+        {
+            return Parse(version);
+        }
+
+        /// 
+        /// Compares two semantic versions for equality.
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is equal to right , otherwise .
+        public static bool operator ==(SemVersion left, SemVersion right)
+        {
+            return Equals(left, right);
+        }
+
+        /// 
+        /// Compares two semantic versions for inequality.
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is not equal to right , otherwise .
+        public static bool operator !=(SemVersion left, SemVersion right)
+        {
+            return !Equals(left, right);
+        }
+
+        /// 
+        /// Compares two semantic versions.
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is greater than right , otherwise .
+        public static bool operator >(SemVersion left, SemVersion right)
+        {
+            return Compare(left, right) > 0;
+        }
+
+        /// 
+        /// Compares two semantic versions.
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is greater than or equal to right , otherwise .
+        public static bool operator >=(SemVersion left, SemVersion right)
+        {
+            return Equals(left, right) || Compare(left, right) > 0;
+        }
+
+        /// 
+        /// Compares two semantic versions.
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is less than right , otherwise .
+        public static bool operator <(SemVersion left, SemVersion right)
+        {
+            return Compare(left, right) < 0;
+        }
+
+        /// 
+        /// Compares two semantic versions.
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is less than or equal to right , otherwise .
+        public static bool operator <=(SemVersion left, SemVersion right)
+        {
+            return Equals(left, right) || Compare(left, right) < 0;
+        }
+
+        /*
+        ** Methods
+        */
+
+#pragma warning disable CA1801 // Parameter unused
+        /// 
+        /// Deserialize a .
+        /// 
+        /// The  parameter is null.
+        private SemVersion(SerializationInfo info, StreamingContext context)
+#pragma warning restore CA1801 // Parameter unused
+        {
+            if (info == null) throw new ArgumentNullException(nameof(info));
+            SemVersion semVersion = Parse(info.GetString("SemVersion"));
+            Major = semVersion.Major;
+            Minor = semVersion.Minor;
+            Patch = semVersion.Patch;
+            Prerelease = semVersion.Prerelease;
+            Build = semVersion.Build;
+        }
+
+        /// 
+        /// Initializes a new instance of the  class.
+        /// 
+        /// The major version.
+        /// The minor version.
+        /// The patch version.
+        /// The prerelease version (e.g. "alpha").
+        /// The build metadata (e.g. "nightly.232").
+        public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
+        {
+            Major = major;
+            Minor = minor;
+            Patch = patch;
+
+            Prerelease = prerelease ?? "";
+            Build = build ?? "";
+        }
+
+        /// 
+        /// Initializes a new instance of the  class from
+        /// a .
+        /// 
+        /// The  that is used to initialize
+        /// the Major, Minor, Patch and Build.
+        /// The prerelease version (e.g. "alpha").
+        /// A  with the same Major and Minor version.
+        /// The Patch version will be the fourth part of the version number. The
+        /// build meta data will contain the third part of the version number if
+        /// it is greater than zero.
+        public SemVersion(Assembly assembly, string prerelease = "")
+        {
+            if (assembly == null)
+                throw new ArgumentNullException(nameof(assembly));
+
+            Version version = assembly.GetName().Version;
+
+            Major = version.Major;
+            Minor = version.Minor;
+
+            if (version.Revision >= 0)
+                Patch = version.Revision;
+
+            Prerelease = prerelease;
+
+            Build = version.Build > 0 ? version.Build.ToString(CultureInfo.InvariantCulture) : "";
+        }
+
+        /// 
+        /// Initializes a new instance of the  class from
+        /// a .
+        /// 
+        /// The  that is used to initialize
+        /// the Major, Minor, Patch and Build.
+        /// The prerelease version (e.g. "alpha").
+        /// A  with the same Major and Minor version.
+        /// The Patch version will be the fourth part of the version number. The
+        /// build meta data will contain the third part of the version number if
+        /// it is greater than zero.
+        public SemVersion(Version version, string prerelease = "")
+        {
+            if (version == null)
+                throw new ArgumentNullException(nameof(version));
+
+            Major = version.Major;
+            Minor = version.Minor;
+
+            if (version.Revision >= 0)
+                Patch = version.Revision;
+
+            Prerelease = prerelease;
+
+            Build = version.Build > 0 ? version.Build.ToString(CultureInfo.InvariantCulture) : "";
+        }
+
+        /// 
+        /// Converts the string representation of a semantic version to its  equivalent.
+        /// 
+        /// The version string.
+        /// If set to  minor and patch version are required,
+        /// otherwise they are optional.
+        /// The  object.
+        /// The  is .
+        /// The  has an invalid format.
+        /// The  is missing Minor or Patch versions and  is .
+        /// The Major, Minor, or Patch versions are larger than int.MaxValue.
+        public static SemVersion Parse(string version, bool strict = false)
+        {
+            Match match = ParseEx.Match(version);
+            if (!match.Success)
+                throw new ArgumentException($"Invalid version '{version}'.", nameof(version));
+
+            int major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
+
+            Group minorMatch = match.Groups["minor"];
+            int minor = 0;
+            if (minorMatch.Success)
+                minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
+            else if (strict)
+                throw new InvalidOperationException("Invalid version (no minor version given in strict mode)");
+
+            Group patchMatch = match.Groups["patch"];
+            int patch = 0;
+            if (patchMatch.Success)
+                patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
+            else if (strict)
+                throw new InvalidOperationException("Invalid version (no patch version given in strict mode)");
+
+            string prerelease = match.Groups["pre"].Value;
+            string build = match.Groups["build"].Value;
+
+            return new SemVersion(major, minor, patch, prerelease, build);
+        }
+
+        /// 
+        /// Converts the string representation of a semantic version to its 
+        /// equivalent and returns a value that indicates whether the conversion succeeded.
+        /// 
+        /// The version string.
+        /// When the method returns, contains a  instance equivalent
+        /// to the version string passed in, if the version string was valid, or  if the
+        /// version string was not valid.
+        /// If set to  minor and patch version are required,
+        /// otherwise they are optional.
+        ///  when a invalid version string is passed, otherwise .
+        public static bool TryParse(string version, out SemVersion semver, bool strict = false)
+        {
+            semver = null;
+            if (version is null) 
+                return false;
+
+            Match match = ParseEx.Match(version);
+            if (!match.Success) 
+                return false;
+
+            if (!int.TryParse(match.Groups["major"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var major))
+                return false;
+
+            Group minorMatch = match.Groups["minor"];
+            int minor = 0;
+            if (minorMatch.Success)
+            {
+                if (!int.TryParse(minorMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out minor))
+                    return false;
+            }
+            else if (strict)
+                return false;
+
+            Group patchMatch = match.Groups["patch"];
+            int patch = 0;
+            if (patchMatch.Success)
+            {
+                if (!int.TryParse(patchMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out patch))
+                    return false;
+            }
+            else if (strict) 
+                return false;
+
+            string prerelease = match.Groups["pre"].Value;
+            string build = match.Groups["build"].Value;
+
+            semver = new SemVersion(major, minor, patch, prerelease, build);
+            return true;
+        }
+
+        /// 
+        /// Checks whether two semantic versions are equal.
+        /// 
+        /// The first version to compare.
+        /// The second version to compare.
+        ///  if the two values are equal, otherwise .
+        public static bool Equals(SemVersion versionA, SemVersion versionB)
+        {
+            if (ReferenceEquals(versionA, versionB)) 
+                return true;
+            if (versionA is null || versionB is null) 
+                return false;
+
+            return versionA.Equals(versionB);
+        }
+
+        /// 
+        /// Compares the specified versions.
+        /// 
+        /// The first version to compare.
+        /// The second version to compare.
+        /// A signed number indicating the relative values of  and .
+        public static int Compare(SemVersion versionA, SemVersion versionB)
+        {
+            if (ReferenceEquals(versionA, versionB))
+                return 0;
+            if (versionA is null)
+                return -1;
+            if (versionB is null)
+                return 1;
+
+            return versionA.CompareTo(versionB);
+        }
+
+        /// 
+        /// Make a copy of the current instance with changed properties.
+        /// 
+        /// The value to replace the major version or  to leave it unchanged.
+        /// The value to replace the minor version or  to leave it unchanged.
+        /// The value to replace the patch version or  to leave it unchanged.
+        /// The value to replace the prerelease version or  to leave it unchanged.
+        /// The value to replace the build metadata or  to leave it unchanged.
+        /// The new version object.
+        /// 
+        /// The change method is intended to be called using named argument syntax, passing only
+        /// those fields to be changed.
+        /// 
+        /// 
+        /// To change only the patch version:
+        /// version.Change(patch: 4)
+        /// 
+        public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
+            string prerelease = null, string build = null)
+        {
+            return new SemVersion(major ?? Major, minor ?? Minor, patch ?? Patch,
+                prerelease ?? Prerelease, build ?? Build);
+        }
+
+        /// 
+        /// Returns the  equivalent of this version.
+        /// 
+        /// 
+        /// The  equivalent of this version.
+        /// 
+        public override string ToString()
+        {
+            // Assume all separators ("..-+"), at most 2 extra chars
+            int estimatedLength = 4 + Major.Digits() + Minor.Digits() + Patch.Digits() + Prerelease.Length + Build.Length;
+            StringBuilder version = new StringBuilder(estimatedLength);
+            version.Append(Major);
+            version.Append('.');
+            version.Append(Minor);
+            version.Append('.');
+            version.Append(Patch);
+            
+            if (Prerelease.Length > 0)
+            {
+                version.Append('-');
+                version.Append(Prerelease);
+            }
+
+            if (Build.Length > 0)
+            {
+                version.Append('+');
+                version.Append(Build);
+            }
+
+            return version.ToString();
+        }
+
+        /// 
+        /// Compares the current instance with another object of the same type and returns an integer that indicates
+        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
+        /// other object.
+        /// 
+        /// An object to compare with this instance.
+        /// 
+        /// A value that indicates the relative order of the objects being compared.
+        /// The return value has these meanings:
+        ///  Less than zero: This instance precedes  in the sort order.
+        ///  Zero: This instance occurs in the same position in the sort order as .
+        ///  Greater than zero: This instance follows  in the sort order.
+        /// 
+        /// The  is not a .
+        public int CompareTo(object obj)
+        {
+            return CompareTo((SemVersion)obj);
+        }
+
+        /// 
+        /// Compares the current instance with another object of the same type and returns an integer that indicates
+        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
+        /// other object.
+        /// 
+        /// An object to compare with this instance.
+        /// 
+        /// A value that indicates the relative order of the objects being compared.
+        /// The return value has these meanings:
+        ///  Less than zero: This instance precedes  in the sort order.
+        ///  Zero: This instance occurs in the same position in the sort order as .
+        ///  Greater than zero: This instance follows  in the sort order.
+        /// 
+        public int CompareTo(SemVersion other)
+        {
+            int r = CompareByPrecedence(other);
+            if (r != 0) 
+                return r;
+
+#pragma warning disable CA1062 // Validate arguments of public methods
+            // If other is null, CompareByPrecedence() returns 1
+            return CompareComponent(Build, other.Build);
+#pragma warning restore CA1062 // Validate arguments of public methods
+        }
+
+        /// 
+        /// Returns whether two semantic versions have the same precedence. Versions
+        /// that differ only by build metadata have the same precedence.
+        /// 
+        /// The semantic version to compare to.
+        ///  if the version precedences are equal.
+        public bool PrecedenceMatches(SemVersion other)
+        {
+            return CompareByPrecedence(other) == 0;
+        }
+
+        /// 
+        /// Compares two semantic versions by precedence as defined in the SemVer spec. Versions
+        /// that differ only by build metadata have the same precedence.
+        /// 
+        /// The semantic version.
+        /// 
+        /// A value that indicates the relative order of the objects being compared.
+        /// The return value has these meanings:
+        ///  Less than zero: This instance precedes  in the sort order.
+        ///  Zero: This instance occurs in the same position in the sort order as .
+        ///  Greater than zero: This instance follows  in the sort order.
+        /// 
+        public int CompareByPrecedence(SemVersion other)
+        {
+            if (other is null)
+                return 1;
+
+            int r = Major.CompareTo(other.Major);
+            if (r != 0)
+                return r;
+
+            r = Minor.CompareTo(other.Minor);
+            if (r != 0) 
+                return r;
+
+            r = Patch.CompareTo(other.Patch);
+            if (r != 0)
+                return r;
+
+            return CompareComponent(Prerelease, other.Prerelease, true);
+        }
+
+        /// 
+        /// 
+        /// 
+        /// 
+        /// 
+        /// 
+        /// 
+        private static int CompareComponent(string a, string b, bool nonemptyIsLower = false)
+        {
+            bool aEmpty = string.IsNullOrEmpty(a);
+            bool bEmpty = string.IsNullOrEmpty(b);
+            if (aEmpty && bEmpty)
+                return 0;
+
+            if (aEmpty)
+                return nonemptyIsLower ? 1 : -1;
+            if (bEmpty)
+                return nonemptyIsLower ? -1 : 1;
+
+            string[] aComps = a.Split('.');
+            string[] bComps = b.Split('.');
+
+            int minLen = Math.Min(aComps.Length, bComps.Length);
+            for (int i = 0; i < minLen; i++)
+            {
+                string ac = aComps[i];
+                string bc = bComps[i];
+                bool aIsNum = int.TryParse(ac, out var aNum);
+                bool bIsNum = int.TryParse(bc, out var bNum);
+                int r;
+                if (aIsNum && bIsNum)
+                {
+                    r = aNum.CompareTo(bNum);
+                    if (r != 0) return r;
+                }
+                else
+                {
+                    if (aIsNum)
+                        return -1;
+                    if (bIsNum)
+                        return 1;
+                    r = string.CompareOrdinal(ac, bc);
+                    if (r != 0)
+                        return r;
+                }
+            }
+
+            return aComps.Length.CompareTo(bComps.Length);
+        }
+
+        /// 
+        /// Determines whether the specified  is equal to this instance.
+        /// 
+        /// The  to compare with this instance.
+        /// 
+        ///    if the specified  is equal to this instance, otherwise .
+        /// 
+        /// The  is not a .
+        public override bool Equals(object obj)
+        {
+            if (obj is null)
+                return false;
+
+            if (ReferenceEquals(this, obj))
+                return true;
+
+            SemVersion other = (SemVersion)obj;
+
+            return Major == other.Major && Minor == other.Minor && Patch == other.Patch
+                && string.Equals(Prerelease, other.Prerelease, StringComparison.Ordinal) && string.Equals(Build, other.Build, StringComparison.Ordinal);
+        }
+
+        /// 
+        /// Returns a hash code for this instance.
+        /// 
+        /// 
+        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+        /// 
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                // TODO verify this. Some versions start result = 17. Some use 37 instead of 31
+                int result = Major.GetHashCode();
+                result = result * 31 + Minor.GetHashCode();
+                result = result * 31 + Patch.GetHashCode();
+                result = result * 31 + Prerelease.GetHashCode();
+                result = result * 31 + Build.GetHashCode();
+                return result;
+            }
+        }
+
+        /// 
+        /// Populates a  with the data needed to serialize the target object.
+        /// 
+        /// The  to populate with data.
+        /// The destination (see ) for this serialization.
+        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
+        public void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            if (info == null)
+                throw new ArgumentNullException(nameof(info));
+            info.AddValue("SemVersion", ToString());
+        }
+    } // public sealed class SemVersion : IComparable, IComparable, ISerializable
+} // namespace fnecore.Utility
diff --git a/fnecore.csproj b/fnecore.csproj
new file mode 100644
index 0000000..85d787e
--- /dev/null
+++ b/fnecore.csproj
@@ -0,0 +1,10 @@
+
+
+  
+    netcoreapp3.1
+    false
+    true
+    False
+  
+
+