initial commit;

pull/1/head
Bryan Biedenkapp 2 years ago
commit 1744e123b4

1
.gitattributes vendored

@ -0,0 +1 @@
* text=auto

66
.gitignore vendored

@ -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

@ -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)

@ -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
{
/// <summary>
/// Used internally to identify the logging level.
/// </summary>
public enum LogLevel : byte
{
/// <summary>
/// Informational
/// </summary>
INFO = 0x00,
/// <summary>
/// Warning
/// </summary>
WARNING = 0x01,
/// <summary>
/// Error
/// </summary>
ERROR = 0x02,
/// <summary>
/// Debug
/// </summary>
DEBUG = 0x04,
/// <summary>
/// Fatal
/// </summary>
FATAL = 0x08
} // public enum LogLevel : byte
/// <summary>
/// Peer Connection State
/// </summary>
public enum ConnectionState
{
/// <summary>
/// Waiting on Login - Received the repeater login request
/// </summary>
WAITING_LOGIN,
/// <summary>
/// Waiting on Authorization - Sent the connection challenge to peer
/// </summary>
WAITING_AUTHORISATION,
/// <summary>
/// Waiting on Configuration
/// </summary>
WAITING_CONFIG,
/// <summary>
/// Running
/// </summary>
RUNNING,
} // public enum ConnectionState
/// <summary>
///
/// </summary>
public enum CallType : byte
{
/// <summary>
/// Group Call
/// </summary>
GROUP = 0x00,
/// <summary>
/// Private Call
/// </summary>
PRIVATE = 0x01,
} // public enum CallType : byte
/// <summary>
///
/// </summary>
public enum FrameType : byte
{
/// <summary>
///
/// </summary>
VOICE = 0x00,
/// <summary>
///
/// </summary>
VOICE_SYNC = 0x01,
/// <summary>
///
/// </summary>
DATA_SYNC = 0x02,
/// <summary>
///
/// </summary>
TERMINATOR = 0xFF
} // public enum FrameType : byte
/// <summary>
///
/// </summary>
public enum DVMState : byte
{
/// <summary>
/// Idle
/// </summary>
IDLE = 0,
/// <summary>
/// DMR
/// </summary>
DMR = 1,
/// <summary>
/// P25
/// </summary>
P25 = 2,
/// <summary>
/// NXDN
/// </summary>
NXDN = 3
} // public enum DVMState : byte
/// <summary>
/// This class defines commonly used protocol and internal constants.
/// </summary>
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

@ -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
{
/// <summary>
/// DMR Data Types
/// </summary>
public enum DMRDataType : byte
{
/// <summary>
/// Voice Privacy Indicator Header
/// </summary>
VOICE_PI_HEADER = 0x00,
/// <summary>
/// Voice Link Control Header
/// </summary>
VOICE_LC_HEADER = 0x01,
/// <summary>
/// Terminator with Link Control
/// </summary>
TERMINATOR_WITH_LC = 0x02,
/// <summary>
/// Control Signalling Block
/// </summary>
CSBK = 0x03,
/// <summary>
/// Data Header
/// </summary>
DATA_HEADER = 0x06,
/// <summary>
/// 1/2 Rate Data
/// </summary>
RATE_12_DATA = 0x07,
/// <summary>
/// 3/4 Rate Data
/// </summary>
RATE_34_DATA = 0x08,
/// <summary>
/// Idle Burst
/// </summary>
IDLE = 0x09,
/// <summary>
/// 1 Rate Data
/// </summary>
RATE_1_DATA = 0x0A,
} // public enum DMRDataType : byte
/// <summary>
/// DMR Full-Link Opcodes
/// </summary>
public enum DMRFLCO : byte
{
/// <summary>
/// GRP VCH USER - Group Voice Channel User
/// </summary>
FLCO_GROUP = 0x00,
/// <summary>
/// UU VCH USER - Unit-to-Unit Voice Channel User
/// </summary>
FLCO_PRIVATE = 0x01,
} // public enum DMRFLCO : byte
} // namespace fnecore.DMR

@ -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
{
/// <summary>
/// Represents DMR embedded signalling.
/// </summary>
public class EMB
{
/// <summary>
/// DMR access color code.
/// </summary>
public byte ColorCode;
/// <summary>
/// Flag indicating whether the privacy indicator is set or not.
/// </summary>
public bool PI;
/// <summary>
/// Link control start/stop.
/// </summary>
public byte LCSS;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="EMB"/> class.
/// </summary>
public EMB()
{
ColorCode = 0;
PI = false;
LCSS = 0;
}
/// <summary>
/// Decodes DMR embedded signalling data.
/// </summary>
/// <param name="data"></param>
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);
}
/// <summary>
/// Encodes DMR embedded signalling data.
/// </summary>
/// <param name="data"></param>
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

@ -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
{
/// <summary>
///
/// </summary>
public enum EmbeddedLCState
{
LCS_NONE,
LCS_FIRST,
LCS_SECOND,
LCS_THIRD
};
/// <summary>
/// Represents DMR embedded data.
/// </summary>
public class EmbeddedData
{
private EmbeddedLCState state;
bool[] data;
bool[] raw;
/// <summary>
/// Flag indicating whether or not the embedded data is valid.
/// </summary>
public bool IsValid
{
get;
private set;
}
/// <summary>
/// Full-link control opcode
/// </summary>
public byte FLCO;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="EmbeddedData"/> class.
/// </summary>
public EmbeddedData()
{
IsValid = false;
FLCO = (byte)DMRFLCO.FLCO_GROUP;
state = EmbeddedLCState.LCS_NONE;
data = new bool[72];
raw = new bool[128];
}
/// <summary>
/// Unpack and error check an embedded LC.
/// </summary>
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);
}
/// <summary>
/// Pack and FEC for an embedded LC.
/// </summary>
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;
}
}
/// <summary>
/// Add LC data (which may consist of 4 blocks) to the data store.
/// </summary>
/// <param name="data"></param>
/// <param name="lcss"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
/// <param name="n"></param>
/// <returns></returns>
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;
}
}
/// <summary>Sets link control data.</summary>
/// <param name="lc"></param>
public void SetLC(LC lc)
{
lc.GetData(ref data);
FLCO = lc.FLCO;
IsValid = true;
EncodeEmbeddedData();
}
/// <summary>Gets link control data.</summary>
/// <returns></returns>
public LC GetLC()
{
if (!IsValid)
return null;
if (FLCO != (byte)DMRFLCO.FLCO_GROUP && FLCO != (byte)DMRFLCO.FLCO_PRIVATE)
return null;
return new LC(data);
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper to reset data values to defaults.
/// </summary>
public void Reset()
{
state = EmbeddedLCState.LCS_NONE;
IsValid = false;
}
} // public class EmbeddedData
} // namespace fnecore.DMR

@ -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
{
/// <summary>
/// Represents full DMR link control.
/// </summary>
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
*/
/// <summary>
/// Decode DMR full-link control data.
/// </summary>
/// <param name="data"></param>
/// <param name="type"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Encode DMR full-link control data.
/// </summary>
/// <param name="lc"></param>
/// <param name="data"></param>
/// <param name="type"></param>
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);
}
/// <summary>
/// Decode DMR privacy control data.
/// </summary>
/// <param name="data"></param>
/// <param name="type"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Encode DMR privacy control data.
/// </summary>
/// <param name="lc"></param>
/// <param name="data"></param>
/// <param name="type"></param>
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

@ -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
{
/// <summary>
/// Represents DMR link control data.
/// </summary>
public class LC
{
private bool R;
/// <summary>
/// Flag indicating whether link protection is enabled.
/// </summary>
public bool PF;
/// <summary>
/// Full-link control opcode.
/// </summary>
public byte FLCO;
/// <summary>
/// Feature ID.
/// </summary>
public byte FID;
/// <summary>
/// Source ID.
/// </summary>
public uint SrcId;
/// <summary>
/// Destination ID.
/// </summary>
public uint DstId;
/** Service Options */
/// <summary>
/// Flag indicating the emergency bits are set.
/// </summary>
public bool Emergency;
/// <summary>
/// Flag indicating that encryption is enabled.
/// </summary>
public bool Encrypted;
/// <summary>
/// Flag indicating broadcast operation.
/// </summary>
public bool Broadcast;
/// <summary>
/// Flag indicating OVCM operation.
/// </summary>
public bool OVCM;
/// <summary>
/// Priority level for the traffic.
/// </summary>
public byte Priority;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="LC"/> class.
/// </summary>
public LC()
{
PF = false;
FLCO = 0;
FID = 0;
SrcId = 0;
DstId = 0;
Emergency = false;
Encrypted = false;
Broadcast = false;
OVCM = false;
Priority = 2;
R = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="LC"/> class.
/// </summary>
/// <param name="bytes"></param>
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
}
/// <summary>
/// Initializes a new instance of the <see cref="LC"/> class.
/// </summary>
/// <param name="bits"></param>
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
}
/// <summary>
/// Gets LC data as bytes.
/// </summary>
/// <returns></returns>
public byte[] GetBytes()
{
byte[] lcData = new byte[12];
GetData(ref lcData);
return lcData;
}
/// <summary>
/// Gets LC data as bytes.
/// </summary>
/// <param name="bytes"></param>
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); // ..
}
/// <summary>
///
/// </summary>
/// <param name="bits"></param>
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

@ -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
{
/// <summary>
/// Represents DMR privacy indicator link control data.
/// </summary>
public class PrivacyLC
{
private byte[] mi;
/// <summary>
/// Feature ID.
/// </summary>
public byte FID;
/// <summary>
/// Destination ID.
/// </summary>
public uint DstId;
/** Service Options */
/// <summary>
/// Flag indicating a group/talkgroup operation.
/// </summary>
public bool Group;
/** Encryption Data */
/// <summary>
/// Encryption algorithm ID.
/// </summary>
public byte AlgId;
/// <summary>
/// Encryption key ID.
/// </summary>
public uint KId;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="PrivacyLC"/> class.
/// </summary>
public PrivacyLC()
{
mi = new byte[4];
FID = 0;
DstId = 0;
Group = false;
AlgId = 0;
KId = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="PrivacyLC"/> class.
/// </summary>
/// <param name="bytes"></param>
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
}
/// <summary>
/// Gets LC data as bytes.
/// </summary>
/// <returns></returns>
public byte[] GetBytes()
{
byte[] lcData = new byte[12];
GetData(ref lcData);
return lcData;
}
/// <summary>
/// Gets LC data as bytes.
/// </summary>
/// <param name="bytes"></param>
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

@ -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
{
/// <summary>
/// Represents DMR slot type.
/// </summary>
public class SlotType
{
/// <summary>
/// DMR access color code.
/// </summary>
public byte ColorCode;
/// <summary>
/// Slot data type.
/// </summary>
public byte DataType;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="SlotType"/> class.
/// </summary>
public SlotType()
{
ColorCode = 0;
DataType = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="SlotType"/> class.
/// </summary>
/// <param name="bytes"></param>
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);
}
/// <summary>
/// Gets <see cref="SlotType"/> data as bytes.
/// </summary>
/// <param name="bytes"></param>
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

@ -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
{
/// <summary>
/// Implements Block Product Turbo Code (196,96) FEC.
/// </summary>
public sealed class BPTC19696
{
private bool[] rawData;
private bool[] deInterData;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="BPTC19696"/> class.
/// </summary>
public BPTC19696()
{
rawData = new bool[196];
deInterData = new bool[196];
}
/// <summary>
/// Decode BPTC (196,96) FEC.
/// </summary>
/// <param name="_in"></param>
/// <param name="_out"></param>
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();
}
/// <summary>
/// Encode BPTC (196,96) FEC.
/// </summary>
/// <param name="_in"></param>
/// <param name="_out"></param>
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();
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
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);
}
/// <summary>
///
/// </summary>
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);
}
/// <summary>
///
/// </summary>
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];
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
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];
}
/// <summary>
///
/// </summary>
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];
}
}
/// <summary>
///
/// </summary>
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;
}
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
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

@ -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
{
/// <summary>
/// Implements various Cyclic Redundancy Check routines.
/// </summary>
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
/// <summary>
///
/// </summary>
private struct DoubleByte
{
/// <summary>
///
/// </summary>
public byte B0;
/// <summary>
///
/// </summary>
public byte B1;
} // private struct DoubleByte
/// <summary>
///
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 2)]
private struct UShortUnion
{
[FieldOffset(0)]
public ushort crc16;
[FieldOffset(0)]
public DoubleByte crc8;
} // private struct UShortUnion
/// <summary>
///
/// </summary>
private struct QuadByte
{
/// <summary>
///
/// </summary>
public byte B0;
/// <summary>
///
/// </summary>
public byte B1;
/// <summary>
///
/// </summary>
public byte B2;
/// <summary>
///
/// </summary>
public byte B3;
} // private struct QuadByte
/// <summary>
///
/// </summary>
[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
*/
/// <summary>
/// Check 5-bit CRC.
/// </summary>
/// <param name="in">Boolean bit array.</param>
/// <param name="tcrc">Computed CRC to check.</param>
/// <returns>True, if CRC is valid, otherwise false.</returns>
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;
}
/// <summary>
/// Encode 5-bit CRC.
/// </summary>
/// <param name="in">Boolean bit array.</param>
/// <param name="tcrc">Computed CRC.</param>
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;
}
/// <summary>
/// Check 16-bit CRC-CCITT.
/// </summary>
/// <remarks>This uses polynomial 0x1021.</remarks>
/// <param name="in">Input byte array.</param>
/// <param name="length">Length of byte array.</param>
/// <returns>True, if CRC is valid, otherwise false.</returns>
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];
}
/// <summary>
/// Encode 16-bit CRC-CCITT.
/// </summary>
/// <remarks>This uses polynomial 0x1021.</remarks>
/// <param name="in">Input byte array.</param>
/// <param name="length">Length of byte array.</param>
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;
}
/// <summary>
/// Check 16-bit CRC-CCITT.
/// </summary>
/// <remarks>This uses polynomial 0x1189.</remarks>
/// <param name="in">Input byte array.</param>
/// <param name="length">Length of byte array.</param>
/// <returns>True, if CRC is valid, otherwise false.</returns>
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];
}
/// <summary>
/// Encode 16-bit CRC-CCITT.
/// </summary>
/// <remarks>This uses polynomial 0x1189.</remarks>
/// <param name="in">Input byte array.</param>
/// <param name="length">Length of byte array.</param>
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;
}
/// <summary>
/// Check 32-bit CRC.
/// </summary>
/// <param name="in">Input byte array.</param>
/// <param name="length">Length of byte array.</param>
/// <returns>True, if CRC is valid, otherwise false.</returns>
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];
}
/// <summary>
/// Encode 32-bit CRC.
/// </summary>
/// <param name="in">Input byte array.</param>
/// <param name="length">Length of byte array.</param>
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;
}
/// <summary>
/// Generate 8-bit CRC.
/// </summary>
/// <param name="in">Input byte array.</param>
/// <param name="length">Length of byte array.</param>
/// <returns>Calculated 8-bit CRC value.</returns>
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;
}
/// <summary>
/// Generate 9-bit CRC.
/// </summary>
/// <param name="in">Input byte array.</param>
/// <param name="bitLength">Length of byte array in bits.</param>
/// <returns>Calculated 9-bit CRC value.</returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="in">Input byte array.</param>
/// <param name="bitLength">Length of byte array in bits.</param>
/// <returns></returns>
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

@ -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
{
/// <summary>
/// Implements Golay (20,8,7) forward error correction.
/// </summary>
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
*/
/// <summary>
/// Decode Golay (20,8,7) FEC.
/// </summary>
/// <param name="data">Golay FEC encoded data byte array</param>
/// <returns></returns>
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);
}
/// <summary>
/// Encode Golay (20,8,7) FEC.
/// </summary>
/// <param name="data">Data to encode with Golay FEC.</param>
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);
}
/// <summary>
///
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="pattern"></param>
/// <returns></returns>
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

@ -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
{
/// <summary>
/// Implements Hamming (15,11,3), (13,9,3), (10,6,3), (16,11,4) and
// (17,12,3) forward error correction.
/// </summary>
public sealed class Hamming
{
/*
** Methods
*/
/// <summary>
/// Decode Hamming (15,11,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
/// <returns>True, if bit errors are detected, otherwise false.</returns>
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;
}
}
/// <summary>
/// Encode Hamming (15,11,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
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];
}
/// <summary>
/// Decode Hamming (15,11,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
/// <returns>True, if bit errors are detected, otherwise false.</returns>
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;
}
}
/// <summary>
/// Encode Hamming (15,11,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
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];
}
/// <summary>
/// Decode Hamming (13,9,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <returns>True, if bit errors are detected, otherwise false.</returns>
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;
}
}
/// <summary>
/// Encode Hamming (13,9,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
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];
}
/// <summary>
/// Decode Hamming (10,6,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
/// <returns>True, if bit errors are detected, otherwise false.</returns>
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;
}
}
/// <summary>
/// Encode Hamming (10,6,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
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];
}
/// <summary>
/// Decode Hamming (16,11,4).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
/// <returns>True, if bit errors are detected or no bit errors, otherwise false if unrecoverable errors are detected.</returns>
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;
}
}
/// <summary>
/// Encode Hamming (10,6,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
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];
}
/// <summary>
/// Decode Hamming (17,12,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
/// <returns>True, if bit errors are detected or no bit errors, otherwise false if unrecoverable errors are detected.</returns>
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;
}
}
/// <summary>
/// Encode Hamming (17,12,3).
/// </summary>
/// <param name="d">Boolean bit array.</param>
/// <param name="offset"></param>
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

@ -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
{
/// <summary>
/// Implements Quadratic residue (16,7,6) forward error correction.
/// </summary>
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
*/
/// <summary>
/// Decode QR (16,7,6) FEC.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Encode QR (16,7,6) FEC.
/// </summary>
/// <remarks>Compute the EMB against a precomputed list of correct words.</remarks>
/// <param name="data"></param>
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);
}
/// <summary>
///
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="pattern"></param>
/// <returns></returns>
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

@ -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
{
/// <summary>
/// Implements Reed-Solomon (12,9) forward error correction.
/// </summary>
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
*/
/// <summary>
/// Check RS (12,9) FEC.
/// </summary>
/// <param name="in"></param>
/// <returns></returns>
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];
}
/// <summary>
/// Encode RS (12,9) FEC.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="msg"></param>
/// <param name="nbytes"></param>
/// <param name="parity"></param>
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<nbytes; i++) {
byte dbyte = (byte)(msg[i] ^ parity[NPAR - 1U]);
for (int j = (int)(NPAR - 1); j > 0; j--)
parity[j] = (byte)(parity[j - 1] ^ gmult(POLY[j], dbyte));
parity[0] = gmult(POLY[0], dbyte);
}
}
/// <summary>
///
/// </summary>
/// <remarks>Multiplication using logarithms.</remarks>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
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

@ -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
{
/// <summary>
/// Structure containing detailed information about a connected peer.
/// </summary>
public class PeerDetails
{
/// <summary>
/// Identity
/// </summary>
public string Identity;
/// <summary>
/// Receive Frequency
/// </summary>
public uint RxFrequency;
/// <summary>
/// Transmit Frequency
/// </summary>
public uint TxFrequency;
/// <summary>
/// Software Identifier
/// </summary>
public string Software;
/*
** System Information
*/
/// <summary>
/// Latitude
/// </summary>
public double Latitude;
/// <summary>
/// Longitude
/// </summary>
public double Longitude;
/// <summary>
/// Height
/// </summary>
public int Height;
/// <summary>
/// Location
/// </summary>
public string Location;
/*
** Channel Data
*/
/// <summary>
/// Transmit Offset (Mhz)
/// </summary>
public float TxOffsetMhz;
/// <summary>
/// Channel Bandwidth (Khz)
/// </summary>
public float ChBandwidthKhz;
/// <summary>
/// Channel ID
/// </summary>
public byte ChannelID;
/// <summary>
/// Channel Number
/// </summary>
public uint ChannelNo;
/// <summary>
/// Transmit Power
/// </summary>
public uint TxPower;
/*
** REST API
*/
/// <summary>
/// REST API Password
/// </summary>
public string Password;
/// <summary>
/// REST API Port
/// </summary>
public int Port;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="PeerDetails"/> class.
/// </summary>
public PeerDetails()
{
/* stub */
}
} // public class PeerDetails
/// <summary>
/// Structure containing information about a connected peer.
/// </summary>
public class PeerInformation
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerID;
/// <summary>
/// Stream ID
/// </summary>
public uint StreamID;
/// <summary>
/// RTP Packet Sequence
/// </summary>
public ushort PacketSequence;
/// <summary>
/// Next expected RTP Packet Sequence
/// </summary>
public ushort NextPacketSequence;
/// <summary>
/// Peer IP EndPoint
/// </summary>
public IPEndPoint EndPoint;
/// <summary>
/// Salt value used for authentication.
/// </summary>
public uint Salt;
/// <summary>
/// Connection State
/// </summary>
public ConnectionState State;
/// <summary>
/// Flag indicating peer is "connected".
/// </summary>
public bool Connection;
/// <summary>
/// Number of pings received.
/// </summary>
public int PingsReceived;
/// <summary>
/// Date/Time of last ping.
/// </summary>
public DateTime LastPing;
/// <summary>
/// Peer Details Structure
/// </summary>
public PeerDetails Details = null;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="PeerInformation"/> class.
/// </summary>
public PeerInformation()
{
Details = new PeerDetails();
}
} // public class PeerInformation
/// <summary>
/// Callback used to validate incoming DMR data.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="slot">Slot Number</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="frameType">Frame Type</param>
/// <param name="dataType">DMR Data Type</param>
/// <param name="streamId">Stream ID</param>
/// <param name="message">Raw message data</param>
/// <returns>True, if data stream is valid, otherwise false.</returns>
public delegate bool DMRDataValidate(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId, byte[] message);
/// <summary>
/// Event used to process incoming DMR data.
/// </summary>
public class DMRDataReceivedEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Source Address
/// </summary>
public uint SrcId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// Slot Number
/// </summary>
public byte Slot { get; }
/// <summary>
/// Call Type (Group or Private)
/// </summary>
public CallType CallType { get; }
/// <summary>
/// Frame Type
/// </summary>
public FrameType FrameType { get; }
/// <summary>
/// DMR Data Type
/// </summary>
public DMRDataType DataType { get; }
/// <summary>
///
/// </summary>
public byte n { get; }
/// <summary>
/// RTP Packet Sequence
/// </summary>
public ushort PacketSequence { get; }
/// <summary>
/// Stream ID
/// </summary>
public uint StreamId { get; }
/// <summary>
/// Raw message data
/// </summary>
public byte[] Data { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="DMRDataReceivedEvent"/> class.
/// </summary>
private DMRDataReceivedEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="DMRDataReceivedEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="slot">Slot Number</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="frameType">Frame Type</param>
/// <param name="dataType">DMR Data Type</param>
/// <param name="n"></param>
/// <param name="pktSeq">RTP Packet Sequence</param>
/// <param name="streamId">Stream ID</param>
/// <param name="data">Raw message data</param>
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
/// <summary>
/// Callback used to validate incoming P25 data.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="duid">P25 DUID</param>
/// <param name="frameType">Frame Type</param>
/// <param name="streamId">Stream ID</param>
/// <param name="message">Raw message data</param>
/// <returns>True, if data stream is valid, otherwise false.</returns>
public delegate bool P25DataValidate(uint peerId, uint srcId, uint dstId, CallType callType, P25DUID duid, FrameType frameType, uint streamId, byte[] message);
/// <summary>
/// Event used to process incoming P25 data.
/// </summary>
public class P25DataReceivedEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Source Address
/// </summary>
public uint SrcId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// Call Type (Group or Private)
/// </summary>
public CallType CallType { get; }
/// <summary>
/// P25 DUID
/// </summary>
public P25DUID DUID { get; }
/// <summary>
/// Frame Type
/// </summary>
public FrameType FrameType { get; }
/// <summary>
/// RTP Packet Sequence
/// </summary>
public ushort PacketSequence { get; }
/// <summary>
/// Stream ID
/// </summary>
public uint StreamId { get; }
/// <summary>
/// Raw message data
/// </summary>
public byte[] Data { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="P25DataReceivedEvent"/> class.
/// </summary>
private P25DataReceivedEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="P25DataPreprocessEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="duid">P25 DUID</param>
/// <param name="frameType">Frame Type</param>
/// <param name="pktSeq">RTP Packet Sequence</param>
/// <param name="streamId">Stream ID</param>
/// <param name="data">Raw message data</param>
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
/// <summary>
/// Callback used to validate incoming NXDN data.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="messageType">NXDN Message Type</param>
/// <param name="frameType">Frame Type</param>
/// <param name="streamId">Stream ID</param>
/// <param name="message">Raw message data</param>
/// <returns>True, if data stream is valid, otherwise false.</returns>
public delegate bool NXDNDataValidate(uint peerId, uint srcId, uint dstId, CallType callType, NXDNMessageType messageType, FrameType frameType, uint streamId, byte[] message);
/// <summary>
/// Event used to process incoming NXDN data.
/// </summary>
public class NXDNDataReceivedEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Source Address
/// </summary>
public uint SrcId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// Call Type (Group or Private)
/// </summary>
public CallType CallType { get; }
/// <summary>
/// NXDN Message Type
/// </summary>
public NXDNMessageType MessageType { get; }
/// <summary>
/// Frame Type
/// </summary>
public FrameType FrameType { get; }
/// <summary>
/// RTP Packet Sequence
/// </summary>
public ushort PacketSequence { get; }
/// <summary>
/// Stream ID
/// </summary>
public uint StreamId { get; }
/// <summary>
/// Raw message data
/// </summary>
public byte[] Data { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="NXDNDataReceivedEvent"/> class.
/// </summary>
private NXDNDataReceivedEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="NXDNDataReceivedEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="messageType">NXDN Message Type</param>
/// <param name="frameType">Frame Type</param>
/// <param name="pktSeq">RTP Packet Sequence</param>
/// <param name="streamId">Stream ID</param>
/// <param name="data">Raw message data</param>
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
/// <summary>
/// Callback used to process whether or not a peer is being ignored for traffic.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="slot">Slot Number</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="frameType">Frame Type</param>
/// <param name="dataType">DMR Data Type</param>
/// <param name="streamId">Stream ID</param>
/// <returns>True, if peer is ignored, otherwise false.</returns>
public delegate bool PeerIgnored(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId);
/// <summary>
/// Event when a peer connects.
/// </summary>
public class PeerConnectedEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Peer Information
/// </summary>
public PeerInformation Information { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="PeerConnectedEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="peer">Peer Information</param>
public PeerConnectedEvent(uint peerId, PeerInformation peer) : base()
{
this.PeerId = peerId;
this.Information = peer;
}
} // public class PeerConnectedEvent : EventArgs
/// <summary>
/// Type of FNE instance.
/// </summary>
public enum FneType : byte
{
/// <summary>
/// Master
/// </summary>
MASTER,
/// <summary>
/// Peer
/// </summary>
PEER,
/// <summary>
/// Unknown (should never happen)
/// </summary>
UNKNOWN = 0xFF
} // public enum FneType : byte
/// <summary>
/// This class implements some base functionality for all other FNE network classes.
/// </summary>
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
*/
/// <summary>
/// Gets the system name for this <see cref="FneBase"/>.
/// </summary>
public string SystemName => systemName;
/// <summary>
/// Gets the peer ID for this <see cref="FneBase"/>.
/// </summary>
public uint PeerId => peerId;
/// <summary>
/// Flag indicating whether this <see cref="FneBase"/> is running.
/// </summary>
public bool IsStarted => isStarted;
/// <summary>
/// Gets the <see cref="FneType"/> this <see cref="FneBase"/> is.
/// </summary>
public FneType FneType => fneType;
/// <summary>
/// Gets/sets the interval that peers will need to ping the master.
/// </summary>
public int PingTime
{
get;
set;
}
/// <summary>
/// Get/sets the current logging level of the <see cref="FneBase"/> instance.
/// </summary>
public LogLevel LogLevel
{
get;
set;
}
/// <summary>
/// Get/sets a flag that enables dumping the raw recieved packets to the log.
/// </summary>
/// <remarks>This will also require the <see cref="FneBase.LogLevel"/> be set to DEBUG.</remarks>
public bool RawPacketTrace
{
get;
set;
}
/*
** Events/Callbacks
*/
/// <summary>
/// Callback action that handles validating a DMR call stream.
/// </summary>
public DMRDataValidate DMRDataValidate = null;
/// <summary>
/// Event action that handles processing a DMR call stream.
/// </summary>
public event EventHandler<DMRDataReceivedEvent> DMRDataReceived;
/// <summary>
/// Callback action that handles validating a P25 call stream.
/// </summary>
public P25DataValidate P25DataValidate = null;
/// <summary>
/// Event action that handles preprocessing a P25 call stream.
/// </summary>
public event EventHandler<P25DataReceivedEvent> P25DataPreprocess;
/// <summary>
/// Event action that handles processing a P25 call stream.
/// </summary>
public event EventHandler<P25DataReceivedEvent> P25DataReceived;
/// <summary>
/// Callback action that handles validating a NXDN call stream.
/// </summary>
public NXDNDataValidate NXDNDataValidate = null;
/// <summary>
/// Event action that handles processing a NXDN call stream.
/// </summary>
public event EventHandler<NXDNDataReceivedEvent> NXDNDataReceived;
/// <summary>
/// Callback action that handles verifying if a peer is ignored for a call stream.
/// </summary>
public PeerIgnored PeerIgnored = null;
/// <summary>
/// Event action that handles when a peer connects.
/// </summary>
public event EventHandler<PeerConnectedEvent> PeerConnected;
/// <summary>
/// Callback action that handles when a peer disconnects.
/// </summary>
public Action<uint> PeerDisconnected = null;
/// <summary>
/// Callback action that handles internal logging.
/// </summary>
public Action<LogLevel, string> Logger;
/*
** Methods
*/
/// <summary>
/// Static initializer for the <see cref="FneMaster"/> class.
/// </summary>
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();
}
/// <summary>
/// Initializes a new instance of the <see cref="FneBase"/> class.
/// </summary>
/// <param name="systemName"></param>
/// <param name="peerId"></param>
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) => { };
}
/// <summary>
/// Starts the main execution loop for this <see cref="FneBase"/>.
/// </summary>
public abstract void Start();
/// <summary>
/// Stops the main execution loop for this <see cref="FneBase"/>.
/// </summary>
public abstract void Stop();
/// <summary>
/// Helper to generate a new stream ID.
/// </summary>
/// <returns></returns>
public static uint CreateStreamID()
{
return (uint)rand.Next(int.MinValue, int.MaxValue);
}
/// <summary>
/// Helper to just quickly generate opcode tuples (mainly for brevity).
/// </summary>
/// <param name="func">Function</param>
/// <param name="subFunc">Sub-Function</param>
/// <returns></returns>
public static Tuple<byte, byte> CreateOpcode(byte func, byte subFunc = Constants.NET_SUBFUNC_NOP)
{
return new Tuple<byte, byte>(func, subFunc);
}
/// <summary>
/// Helper to fire the logging action.
/// </summary>
/// <param name="logLevel"></param>
/// <param name="message"></param>
protected void Log(LogLevel logLevel, string message)
{
byte level = (byte)logLevel;
if (level <= (byte)LogLevel)
Logger(logLevel, message);
}
/// <summary>
/// Helper to read and process a FNE RTP frame.
/// </summary>
/// <param name="frame">Raw UDP socket frame.</param>
/// <param name="messageLength">Length of payload message.</param>
/// <param name="rtpHeader">RTP Header.</param>
/// <param name="fneHeader">RTP FNE Header.</param>
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;
}
/// <summary>
/// Helper to generate and write a FNE RTP frame.
/// </summary>
/// <param name="message">Payload message.</param>
/// <param name="peerId">Peer ID.</param>
/// <param name="ssrc">Synchronization Source ID.</param>
/// <param name="opcode">FNE Network Opcode.</param>
/// <param name="pktSeq">RTP Packet Sequence.</param>
/// <param name="streamId">Stream ID.</param>
/// <returns></returns>
protected byte[] WriteFrame(byte[] message, uint peerId, uint ssrc, Tuple<byte, byte> 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;
}
/// <summary>
/// Helper to fire the DMR data received event.
/// </summary>
/// <param name="e"><see cref="DMRDataReceivedEvent"/> instance</param>
protected void FireDMRDataReceived(DMRDataReceivedEvent e)
{
if (DMRDataReceived != null)
DMRDataReceived.Invoke(this, e);
}
/// <summary>
/// Helper to fire the P25 data pre-process event.
/// </summary>
/// <param name="e"><see cref="P25DataReceivedEvent"/> instance</param>
protected void FireP25DataPreprocess(P25DataReceivedEvent e)
{
if (P25DataPreprocess != null)
P25DataPreprocess.Invoke(this, e);
}
/// <summary>
/// Helper to fire the P25 data received event.
/// </summary>
/// <param name="e"><see cref="P25DataReceivedEvent"/> instance</param>
protected void FireP25DataReceived(P25DataReceivedEvent e)
{
if (P25DataReceived != null)
P25DataReceived.Invoke(this, e);
}
/// <summary>
/// Helper to fire the NXDN data received event.
/// </summary>
/// <param name="e"><see cref="NXDNDataReceivedEvent"/> instance</param>
protected void FireNXDNDataReceived(NXDNDataReceivedEvent e)
{
if (NXDNDataReceived != null)
NXDNDataReceived.Invoke(this, e);
}
/// <summary>
/// Helper to fire the peer connected event.
/// </summary>
/// <param name="e"><see cref="PeerConnectedEvent"/> instance</param>
protected void FirePeerConnected(PeerConnectedEvent e)
{
if (PeerConnected != null)
PeerConnected.Invoke(this, e);
}
} // public abstract class FneBase
} // namespace fnecore

File diff suppressed because it is too large Load Diff

@ -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
{
/// <summary>
/// Callback used to process a raw network frame.
/// </summary>
/// <param name="frame"><see cref="UdpFrame"/></param>
/// <param name="peerId">Peer ID</param>
/// <param name="streamId">Stream ID</param>
/// <returns>True, if the frame was handled, otherwise false.</returns>
public delegate bool RawNetworkFrame(UdpFrame frame, uint peerId, uint streamId);
/// <summary>
/// Implements an FNE "peer".
/// </summary>
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
*/
/// <summary>
/// Gets/sets the peer information.
/// </summary>
public PeerInformation Information
{
get { return info; }
set { info = value; }
}
/// <summary>
/// Gets/sets the password used for connecting to a master.
/// </summary>
public string Passphrase
{
get;
set;
}
/// <summary>
/// Gets the number of pings sent.
/// </summary>
public int PingsSent
{
get;
private set;
}
/// <summary>
/// Gets the number of pings acked.
/// </summary>
public int PingsAcked
{
get;
private set;
}
/*
** Events/Callbacks
*/
/// <summary>
/// Event action that handles a raw network frame directly.
/// </summary>
public RawNetworkFrame NetworkFrameHandler = null;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="FnePeer"/> class.
/// </summary>
/// <param name="systemName"></param>
/// <param name="peerId"></param>
/// <param name="address"></param>
/// <param name="port"></param>
public FnePeer(string systemName, uint peerId, string address, int port) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port))
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="FnePeer"/> class.
/// </summary>
/// <param name="systemName"></param>
/// <param name="peerId"></param>
/// <param name="endpoint"></param>
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;
}
/// <summary>
/// Starts the main execution loop for this <see cref="FneMaster"/>.
/// </summary>
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;
}
/// <summary>
/// Stops the main execution loop for this <see cref="FneMaster"/>.
/// </summary>
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;
}
/// <summary>
/// Helper to send a raw UDP frame.
/// </summary>
/// <param name="frame">UDP frame to send</param>
public void Send(UdpFrame frame)
{
if (RawPacketTrace)
Log(LogLevel.DEBUG, $"({systemName}) Network Sent (to {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}");
client.Send(frame);
}
/// <summary>
/// Helper to send a data message to the master.
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="message">Byte array containing message to send</param>
/// <param name="pktSeq">RTP Packet Sequence</param>
/// <param name="streamId"></param>
public void SendMaster(Tuple<byte, byte> 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)
});
}
/// <summary>
/// Helper to send a data message to the master.
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="message">Byte array containing message to send</param>
public void SendMaster(Tuple<byte, byte> opcode, byte[] message)
{
SendMaster(opcode, message, pktSeq());
}
/// <summary>
/// Helper to update the RTP packet sequence.
/// </summary>
/// <param name="reset"></param>
/// <returns>RTP packet sequence.</returns>
public ushort pktSeq(bool reset = false)
{
if (reset)
{
currPktSeq = 0;
return currPktSeq;
}
ushort curr = currPktSeq;
++currPktSeq;
if (currPktSeq > ushort.MaxValue)
currPktSeq = 0;
return curr;
}
/// <summary>
/// Internal UDP listen routine.
/// </summary>
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;
}
}
/// <summary>
/// Internal maintainence routine.
/// </summary>
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

@ -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
{
/// <summary>
///
/// </summary>
public class FneUtils
{
private static readonly byte[] BIT_MASK_TABLE = new byte[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
private static Action<IntPtr, byte, int> memsetDelegate;
/*
** Methods
*/
/// <summary>
/// Static initializer for the <see cref="FneUtils"/> class.
/// </summary>
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<IntPtr, byte, int>)dynamicMethod.CreateDelegate(typeof(Action<IntPtr, byte, int>));
}
/// <summary>
///
/// </summary>
/// <param name="array"></param>
/// <param name="what"></param>
/// <param name="length"></param>
public static void Memset(byte[] array, byte what, int length)
{
GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
memsetDelegate(gcHandle.AddrOfPinnedObject(), what, length);
gcHandle.Free();
}
/// <summary>
///
/// </summary>
/// <param name="b"></param>
/// <param name="bits"></param>
/// <param name="offset"></param>
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;
}
/// <summary>
///
/// </summary>
/// <param name="b"></param>
/// <param name="bits"></param>
/// <param name="offset"></param>
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;
}
/// <summary>
///
/// </summary>
/// <param name="bits"></param>
/// <param name="offset"></param>
/// <param name="b"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="bits"></param>
/// <param name="offset"></param>
/// <param name="b"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="p"></param>
/// <param name="i"></param>
/// <param name="b"></param>
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]));
}
/// <summary>
///
/// </summary>
/// <param name="p"></param>
/// <param name="i"></param>
/// <param name="b"></param>
public static void WriteBit(ref Span<byte> 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]));
}
/// <summary>
///
/// </summary>
/// <param name="p"></param>
/// <param name="i"></param>
/// <returns></returns>
public static bool ReadBit(byte[] p, uint i)
{
return (p[(i) >> 3] & BIT_MASK_TABLE[(i) & 7]) != 0;
}
/// <summary>
///
/// </summary>
/// <param name="p"></param>
/// <param name="i"></param>
/// <returns></returns>
public static bool ReadBit(Span<byte> p, uint i)
{
return (p[(int)((i) >> 3)] & BIT_MASK_TABLE[(i) & 7]) != 0;
}
/// <summary>
/// Write the given bytes in the unsigned short into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(ushort val, ref byte[] buffer, int offset)
{
buffer[offset] = (byte)(val >> 8);
buffer[offset + 1] = (byte)(val & 0xFF);
}
/// <summary>
/// Write the given bytes in the unsigned short into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(ushort val, ref Span<byte> buffer, int offset)
{
buffer[offset] = (byte)(val >> 8);
buffer[offset + 1] = (byte)(val & 0xFF);
}
/// <summary>
/// Write the given bytes in the unsigned integer into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
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);
}
/// <summary>
/// Write the given bytes in the unsigned integer into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
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);
}
/// <summary>
/// Write the given bytes in the unsigned integer into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(uint val, ref Span<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);
}
/// <summary>
/// Write the given bytes in the unsigned long into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
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);
}
/// <summary>
/// Write the given bytes in the unsigned long into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(ulong val, ref Span<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);
}
/// <summary>
/// Write the given bytes in the short into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(short val, ref byte[] buffer, int offset)
{
WriteBytes((ushort)val, ref buffer, offset);
}
/// <summary>
/// Write the given bytes in the short into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(short val, ref Span<byte> buffer, int offset)
{
WriteBytes((ushort)val, ref buffer, offset);
}
/// <summary>
/// Write the given bytes in the integer into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(int val, ref byte[] buffer, int offset)
{
WriteBytes((uint)val, ref buffer, offset);
}
/// <summary>
/// Write the given bytes in the integer into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(int val, ref Span<byte> buffer, int offset)
{
WriteBytes((uint)val, ref buffer, offset);
}
/// <summary>
/// Write the given bytes in the long into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(long val, ref byte[] buffer, int offset)
{
WriteBytes((ulong)val, ref buffer, offset);
}
/// <summary>
/// Write the given bytes in the long into the given buffer (by most significant byte)
/// </summary>
/// <param name="val"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static void WriteBytes(long val, ref Span<byte> buffer, int offset)
{
WriteBytes((ulong)val, ref buffer, offset);
}
/// <summary>
/// Get an unsigned short value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static ushort ToUInt16(byte[] buffer, int offset)
{
return (ushort)(((buffer[offset] << 8) & 0xFF00) | ((buffer[offset + 1] << 0) & 0x00FF));
}
/// <summary>
/// Get an unsigned short value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static ushort ToUInt16(Span<byte> buffer, int offset)
{
return (ushort)(((buffer[offset] << 8) & 0xFF00) | ((buffer[offset + 1] << 0) & 0x00FF));
}
/// <summary>
/// Get an unsigned integer value from the given bytes. (by most significant byte)
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Get an unsigned integer value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Get an unsigned integer value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static uint ToUInt32(Span<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;
}
/// <summary>
/// Get an unsigned long value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static ulong ToUInt64(byte[] buffer, int offset)
{
return (((ulong)ToUInt32(buffer, offset + 0)) << 32) | ToUInt32(buffer, offset + 4);
}
/// <summary>
/// Get an unsigned long value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static ulong ToUInt64(Span<byte> buffer, int offset)
{
return (((ulong)ToUInt32(buffer, offset + 0)) << 32) | ToUInt32(buffer, offset + 4);
}
/// <summary>
/// Get an signed short value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static short ToInt16(byte[] buffer, int offset)
{
return (short)ToUInt16(buffer, offset);
}
/// <summary>
/// Get an signed short value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static short ToInt16(Span<byte> buffer, int offset)
{
return (short)ToUInt16(buffer, offset);
}
/// <summary>
/// Get a signed integer value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static int ToInt32(byte[] buffer, int offset)
{
return (int)ToUInt32(buffer, offset);
}
/// <summary>
/// Get a signed integer value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static int ToInt32(Span<byte> buffer, int offset)
{
return (int)ToUInt32(buffer, offset);
}
/// <summary>
/// Get a signed long value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static long ToInt64(byte[] buffer, int offset)
{
return (long)ToUInt64(buffer, offset);
}
/// <summary>
/// Get a signed long value from the given bytes. (by most significant byte)
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static long ToInt64(Span<byte> buffer, int offset)
{
return (long)ToUInt64(buffer, offset);
}
/// <summary>
/// Primitive conversion from Unicode to ASCII that preserves special characters.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="dest">The buffer to fill.</param>
/// <param name="offset">The start of the string in the buffer.</param>
/// <param name="count">The number of characters to convert.</param>
/// <remarks>The built-in ASCIIEncoding converts characters of codepoint > 127 to ?,
/// this preserves those code points by removing the top 16 bits of each character.</remarks>
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;
}
}
/// <summary>
/// Primitive conversion from ASCII to Unicode that preserves special characters.
/// </summary>
/// <param name="data">The data to convert.</param>
/// <param name="offset">The first byte to convert.</param>
/// <param name="count">The number of bytes to convert.</param>
/// <returns>The string.</returns>
/// <remarks>The built-in ASCIIEncoding converts characters of codepoint > 127 to ?,
/// this preserves those code points.</remarks>
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);
}
/// <summary>
/// Helper to display the ASCII representation of a hex dump.
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
private static string DisplayHexChars(Span<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;
}
/// <summary>
/// Helper to display the ASCII representation of a hex dump.
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Perform a hex dump of a buffer.
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
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;
}
/// <summary>
/// Perform a hex dump of a buffer.
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
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;
}
/// <summary>
/// Perform a hex dump of a buffer.
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
public static string HexDump(Memory<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.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;
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static byte[] sha256_hash(string value)
{
return sha256_hash(Encoding.ASCII.GetBytes(value));
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static byte[] sha256_hash(byte[] value)
{
using (SHA256 hash = SHA256Managed.Create())
return hash.ComputeHash(value);
}
} // public class FneUtils
} // namespace fnecore

@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <https://www.gnu.org/licenses/>.
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
<https://www.gnu.org/licenses/>.

@ -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
{
/// <summary>
/// NXDN Message Type
/// </summary>
public enum NXDNMessageType : byte
{
/// <summary>
/// Voice Call
/// </summary>
MESSAGE_TYPE_VCALL = 0x01,
/// <summary>
/// Voice Call - Individual
/// </summary>
MESSAGE_TYPE_VCALL_IV = 0x03,
/// <summary>
/// Data Call Header
/// </summary>
MESSAGE_TYPE_DCALL_HDR = 0x09,
/// <summary>
/// Data Call Header
/// </summary>
MESSAGE_TYPE_DCALL_DATA = 0x0B,
/// <summary>
/// Data Call Header
/// </summary>
MESSAGE_TYPE_DCALL_ACK = 0x0C,
/// <summary>
/// Transmit Release
/// </summary>
MESSAGE_TYPE_TX_REL = 0x08,
/// <summary>
/// Idle
/// </summary>
MESSAGE_TYPE_IDLE = 0x10,
} // public enum NXDNMessageType : byte
} // namespace fnecore.NXDN

@ -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
{
/// <summary>
/// P25 DFSI Frame Types
/// </summary>
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
/// <summary>
/// P25 Data Unit ID
/// </summary>
public enum P25DUID : byte
{
/// <summary>
/// Header Data Unit
/// </summary>
HDU = 0x00,
/// <summary>
/// Terminator Data Unit
/// </summary>
TDU = 0x03,
/// <summary>
/// Logical Data Unit 1
/// </summary>
LDU1 = 0x05,
/// <summary>
/// Trunking Signalling Data Unit
/// </summary>
TSDU = 0x07,
/// <summary>
/// Logical Data Unit 2
/// </summary>
LDU2 = 0x0A,
/// <summary>
/// Packet Data Unit
/// </summary>
PDU = 0x0C,
/// <summary>
/// Terminator Data Unit with Link Control
/// </summary>
TDULC = 0x0F
} // public enum P25DUID : byte
/// <summary>
/// P25 Constants
/// </summary>
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

@ -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")]

@ -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.

@ -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
{
/// <summary>
///
/// </summary>
public class RtpExtensionHeader
{
protected int offset = 0;
protected ushort payloadLength;
/// <summary>
/// Format of the extension header payload contained within the packet.
/// </summary>
public ushort PayloadType { get; set; }
/// <summary>
/// Length of the extension header payload (in 32-bit units).
/// </summary>
public ushort PayloadLength { get => payloadLength; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="RtpExtensionHeader"/> class.
/// </summary>
/// <param name="offset"></param>
public RtpExtensionHeader(int offset = 12) // 12 bytes is the length of the RTP Header
{
this.offset = offset;
PayloadType = 0;
payloadLength = 0;
}
/// <summary>
/// Decode a RTP header.
/// </summary>
/// <param name="data"></param>
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;
}
/// <summary>
/// Encode a RTP header.
/// </summary>
/// <param name="data"></param>
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

@ -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
{
/// <summary>
/// Represents an RTP header.
/// </summary>
public class RtpFNEHeader : RtpExtensionHeader
{
/// <summary>
/// Traffic payload packet CRC-16.
/// </summary>
public ushort CRC { get; set; }
/// <summary>
/// Function.
/// </summary>
public byte Function { get; set; }
/// <summary>
/// Sub-function.
/// </summary>
public byte SubFunction { get; set; }
/// <summary>
/// Traffic Stream ID.
/// </summary>
public uint StreamID { get; set; }
/// <summary>
/// Traffic Peer ID.
/// </summary>
public uint PeerID { get; set; }
/// <summary>
/// Traffic Message Length.
/// </summary>
public uint MessageLength { get; set; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="RtpFNEHeader"/> class.
/// </summary>
/// <param name="offset"></param>
public RtpFNEHeader(int offset = 12) : base(offset) // 12 bytes is the length of the RTP Header
{
CRC = 0;
StreamID = 0;
PeerID = 0;
MessageLength = 0;
}
/// <summary>
/// Decode a RTP header.
/// </summary>
/// <param name="data"></param>
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;
}
/// <summary>
/// Encode a RTP header.
/// </summary>
/// <param name="data"></param>
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

@ -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
{
/// <summary>
///
/// </summary>
public class RtpHeader
{
private Random rand;
private static DateTime start = DateTime.Now;
private byte version;
private bool padding;
private byte cc;
/// <summary>
/// RTP Protocol Version.
/// </summary>
public byte Version { get => version; }
/// <summary>
/// Flag indicating if the packet has trailing padding.
/// </summary>
public bool Padding { get => padding; }
/// <summary>
/// Flag indicating the presense of an extension header.
/// </summary>
public bool Extension { get; set; }
/// <summary>
/// Count of contributing source IDs that follow the SSRC.
/// </summary>
public byte CSRCCount { get => cc; }
/// <summary>
/// Flag indicating application-specific behavior.
/// </summary>
public bool Marker { get; set; }
/// <summary>
/// Format of the payload contained within the packet.
/// </summary>
public byte PayloadType { get; set; }
/// <summary>
/// Sequence number for the RTP packet.
/// </summary>
public ushort Sequence { get; set; }
/// <summary>
/// RTP packet timestamp.
/// </summary>
public uint Timestamp { get; set; }
/// <summary>
/// Synchronization Source ID.
/// </summary>
public uint SSRC { get; set; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="RtpHeader"/> class.
/// </summary>
/// <param name="noIncrement"></param>
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;
}
/// <summary>
/// Decode a RTP header.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Encode a RTP header.
/// </summary>
/// <param name="data"></param>
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

220
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
{
/// <summary>
/// Structure representing a raw UDP packet frame.
/// </summary>
/// <remarks>"Frame" is used loosely here...</remarks>
public struct UdpFrame
{
/// <summary>
///
/// </summary>
public IPEndPoint Endpoint;
/// <summary>
///
/// </summary>
public byte[] Message;
} // public struct UDPFrame
/// <summary>
/// Base class from which all UDP classes are derived.
/// </summary>
public abstract class UdpBase
{
protected UdpClient client;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="UdpBase"/> class.
/// </summary>
protected UdpBase()
{
client = new UdpClient();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public async Task<UdpFrame> Receive()
{
UdpReceiveResult res = await client.ReceiveAsync();
return new UdpFrame()
{
Message = res.Buffer,
Endpoint = res.RemoteEndPoint
};
}
} // public abstract class UDPBase
/// <summary>
/// Class implementing a UDP listener (server).
/// </summary>
public class UdpListener : UdpBase
{
private IPEndPoint listen;
/*
** Properties
*/
/// <summary>
/// Gets the <see cref="IPEndPoint"/> for this <see cref="UdpListener"/>.
/// </summary>
public IPEndPoint EndPoint => listen;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="UdpListener"/> class.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
public UdpListener(string address, int port) : this(new IPEndPoint(IPAddress.Parse(address), port))
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="UdpListener"/> class.
/// </summary>
/// <param name="endpoint"></param>
public UdpListener(IPEndPoint endpoint)
{
listen = endpoint;
client = new UdpClient(listen);
}
/// <summary>
///
/// </summary>
/// <param name="frame"></param>
public void Send(UdpFrame frame)
{
client.Send(frame.Message, frame.Message.Length, frame.Endpoint);
}
/// <summary>
///
/// </summary>
/// <param name="frame"></param>
public async Task<int> SendAsync(UdpFrame frame)
{
return await client.SendAsync(frame.Message, frame.Message.Length, frame.Endpoint);
}
} // public class UdpListener : UdpBase
/// <summary>
///
/// </summary>
public class UdpReceiver : UdpBase
{
private IPEndPoint endpoint;
/*
** Properties
*/
/// <summary>
/// Gets the <see cref="IPEndPoint"/> for this <see cref="UdpReceiver"/>.
/// </summary>
public IPEndPoint EndPoint => endpoint;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="UdpListener"/> class.
/// </summary>
public UdpReceiver()
{
/* stub */
}
/// <summary>
///
/// </summary>
/// <param name="hostName"></param>
/// <param name="port"></param>
/// <returns></returns>
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);
}
/// <summary>
///
/// </summary>
/// <param name="endpoint"></param>
/// <returns></returns>
public void Connect(IPEndPoint endpoint)
{
UdpReceiver recv = new UdpReceiver();
this.endpoint = endpoint;
client.Connect(endpoint.Address.ToString(), endpoint.Port);
}
/// <summary>
///
/// </summary>
/// <param name="frame"></param>
public void Send(UdpFrame frame)
{
client.Send(frame.Message, frame.Message.Length);
}
} // public class UdpReceiver : UdpBase
} // namespace fnecore

File diff suppressed because it is too large Load Diff

@ -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
{
/// <summary>
///
/// </summary>
internal static class IntExtensions
{
/// <summary>
/// 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 <see cref="StringBuilder"/> for numbers that
/// aren't supposed to be negative, but when they are it is just a little
/// slower.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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
/// <summary>
/// A semantic version implementation.
/// Conforms with v2.0.0 of http://semver.org
/// </summary>
[Serializable]
public sealed class SemVersion : IComparable<SemVersion>, IComparable, ISerializable
{
private static readonly Regex ParseEx = new Regex(@"^(?<major>\d+)" + @"(?>\.(?<minor>\d+))?" +
@"(?>\.(?<patch>\d+))?" + @"(?>\-(?<pre>[0-9A-Za-z\-\.]+))?" + @"(?>\+(?<build>[0-9A-Za-z\-\.]+))?$",
RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.ExplicitCapture,
TimeSpan.FromSeconds(0.5));
/*
** Properties
*/
/// <summary>
/// Gets the major version.
/// </summary>
/// <value>
/// The major version.
/// </value>
public int Major { get; }
/// <summary>
/// Gets the minor version.
/// </summary>
/// <value>
/// The minor version.
/// </value>
public int Minor { get; }
/// <summary>
/// Gets the patch version.
/// </summary>
/// <value>
/// The patch version.
/// </value>
public int Patch { get; }
/// <summary>
/// Gets the prerelease version.
/// </summary>
/// <value>
/// The prerelease version. Empty string if this is a release version.
/// </value>
public string Prerelease { get; }
/// <summary>
/// Gets the build metadata.
/// </summary>
/// <value>
/// The build metadata. Empty string if there is no build metadata.
/// </value>
public string Build { get; }
/*
** Operators
*/
#pragma warning disable CA2225 // Operator overloads have named alternates
/// <summary>
/// Implicit conversion from <see cref="string"/> to <see cref="SemVersion"/>.
/// </summary>
/// <param name="version">The semantic version.</param>
/// <returns>The <see cref="SemVersion"/> object.</returns>
/// <exception cref="ArgumentNullException">The <paramref name="version"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The version number has an invalid format.</exception>
/// <exception cref="OverflowException">The Major, Minor, or Patch versions are larger than <code>int.MaxValue</code>.</exception>
public static implicit operator SemVersion(string version)
#pragma warning restore CA2225 // Operator overloads have named alternates
{
return Parse(version);
}
/// <summary>
/// Compares two semantic versions for equality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>If left is equal to right <see langword="true"/>, otherwise <see langword="false"/>.</returns>
public static bool operator ==(SemVersion left, SemVersion right)
{
return Equals(left, right);
}
/// <summary>
/// Compares two semantic versions for inequality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>If left is not equal to right <see langword="true"/>, otherwise <see langword="false"/>.</returns>
public static bool operator !=(SemVersion left, SemVersion right)
{
return !Equals(left, right);
}
/// <summary>
/// Compares two semantic versions.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>If left is greater than right <see langword="true"/>, otherwise <see langword="false"/>.</returns>
public static bool operator >(SemVersion left, SemVersion right)
{
return Compare(left, right) > 0;
}
/// <summary>
/// Compares two semantic versions.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>If left is greater than or equal to right <see langword="true"/>, otherwise <see langword="false"/>.</returns>
public static bool operator >=(SemVersion left, SemVersion right)
{
return Equals(left, right) || Compare(left, right) > 0;
}
/// <summary>
/// Compares two semantic versions.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>If left is less than right <see langword="true"/>, otherwise <see langword="false"/>.</returns>
public static bool operator <(SemVersion left, SemVersion right)
{
return Compare(left, right) < 0;
}
/// <summary>
/// Compares two semantic versions.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>If left is less than or equal to right <see langword="true"/>, otherwise <see langword="false"/>.</returns>
public static bool operator <=(SemVersion left, SemVersion right)
{
return Equals(left, right) || Compare(left, right) < 0;
}
/*
** Methods
*/
#pragma warning disable CA1801 // Parameter unused
/// <summary>
/// Deserialize a <see cref="SemVersion"/>.
/// </summary>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is null.</exception>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="SemVersion" /> class.
/// </summary>
/// <param name="major">The major version.</param>
/// <param name="minor">The minor version.</param>
/// <param name="patch">The patch version.</param>
/// <param name="prerelease">The prerelease version (e.g. "alpha").</param>
/// <param name="build">The build metadata (e.g. "nightly.232").</param>
public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
{
Major = major;
Minor = minor;
Patch = patch;
Prerelease = prerelease ?? "";
Build = build ?? "";
}
/// <summary>
/// Initializes a new instance of the <see cref="SemVersion"/> class from
/// a <see cref="System.Reflection.Assembly"/>.
/// </summary>
/// <param name="assembly">The <see cref="System.Reflection.Assembly"/> that is used to initialize
/// the Major, Minor, Patch and Build.</param>
/// <param name="prerelease">The prerelease version (e.g. "alpha").</param>
/// <returns>A <see cref="SemVersion"/> 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.</returns>
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) : "";
}
/// <summary>
/// Initializes a new instance of the <see cref="SemVersion"/> class from
/// a <see cref="System.Version"/>.
/// </summary>
/// <param name="version">The <see cref="Version"/> that is used to initialize
/// the Major, Minor, Patch and Build.</param>
/// <param name="prerelease">The prerelease version (e.g. "alpha").</param>
/// <returns>A <see cref="SemVersion"/> 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.</returns>
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) : "";
}
/// <summary>
/// Converts the string representation of a semantic version to its <see cref="SemVersion"/> equivalent.
/// </summary>
/// <param name="version">The version string.</param>
/// <param name="strict">If set to <see langword="true"/> minor and patch version are required,
/// otherwise they are optional.</param>
/// <returns>The <see cref="SemVersion"/> object.</returns>
/// <exception cref="ArgumentNullException">The <paramref name="version"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The <paramref name="version"/> has an invalid format.</exception>
/// <exception cref="InvalidOperationException">The <paramref name="version"/> is missing Minor or Patch versions and <paramref name="strict"/> is <see langword="true"/>.</exception>
/// <exception cref="OverflowException">The Major, Minor, or Patch versions are larger than <code>int.MaxValue</code>.</exception>
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);
}
/// <summary>
/// Converts the string representation of a semantic version to its <see cref="SemVersion"/>
/// equivalent and returns a value that indicates whether the conversion succeeded.
/// </summary>
/// <param name="version">The version string.</param>
/// <param name="semver">When the method returns, contains a <see cref="SemVersion"/> instance equivalent
/// to the version string passed in, if the version string was valid, or <see langword="null"/> if the
/// version string was not valid.</param>
/// <param name="strict">If set to <see langword="true"/> minor and patch version are required,
/// otherwise they are optional.</param>
/// <returns><see langword="false"/> when a invalid version string is passed, otherwise <see langword="true"/>.</returns>
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;
}
/// <summary>
/// Checks whether two semantic versions are equal.
/// </summary>
/// <param name="versionA">The first version to compare.</param>
/// <param name="versionB">The second version to compare.</param>
/// <returns><see langword="true"/> if the two values are equal, otherwise <see langword="false"/>.</returns>
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);
}
/// <summary>
/// Compares the specified versions.
/// </summary>
/// <param name="versionA">The first version to compare.</param>
/// <param name="versionB">The second version to compare.</param>
/// <returns>A signed number indicating the relative values of <paramref name="versionA"/> and <paramref name="versionB"/>.</returns>
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);
}
/// <summary>
/// Make a copy of the current instance with changed properties.
/// </summary>
/// <param name="major">The value to replace the major version or <see langword="null"/> to leave it unchanged.</param>
/// <param name="minor">The value to replace the minor version or <see langword="null"/> to leave it unchanged.</param>
/// <param name="patch">The value to replace the patch version or <see langword="null"/> to leave it unchanged.</param>
/// <param name="prerelease">The value to replace the prerelease version or <see langword="null"/> to leave it unchanged.</param>
/// <param name="build">The value to replace the build metadata or <see langword="null"/> to leave it unchanged.</param>
/// <returns>The new version object.</returns>
/// <remarks>
/// The change method is intended to be called using named argument syntax, passing only
/// those fields to be changed.
/// </remarks>
/// <example>
/// To change only the patch version:
/// <code>version.Change(patch: 4)</code>
/// </example>
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);
}
/// <summary>
/// Returns the <see cref="string" /> equivalent of this version.
/// </summary>
/// <returns>
/// The <see cref="string" /> equivalent of this version.
/// </returns>
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();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="obj">An object to compare with this instance.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared.
/// The return value has these meanings:
/// Less than zero: This instance precedes <paramref name="obj" /> in the sort order.
/// Zero: This instance occurs in the same position in the sort order as <paramref name="obj" />.
/// Greater than zero: This instance follows <paramref name="obj" /> in the sort order.
/// </returns>
/// <exception cref="InvalidCastException">The <paramref name="obj"/> is not a <see cref="SemVersion"/>.</exception>
public int CompareTo(object obj)
{
return CompareTo((SemVersion)obj);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="other">An object to compare with this instance.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared.
/// The return value has these meanings:
/// Less than zero: This instance precedes <paramref name="other" /> in the sort order.
/// Zero: This instance occurs in the same position in the sort order as <paramref name="other" />.
/// Greater than zero: This instance follows <paramref name="other" /> in the sort order.
/// </returns>
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
}
/// <summary>
/// Returns whether two semantic versions have the same precedence. Versions
/// that differ only by build metadata have the same precedence.
/// </summary>
/// <param name="other">The semantic version to compare to.</param>
/// <returns><see langword="true"/> if the version precedences are equal.</returns>
public bool PrecedenceMatches(SemVersion other)
{
return CompareByPrecedence(other) == 0;
}
/// <summary>
/// Compares two semantic versions by precedence as defined in the SemVer spec. Versions
/// that differ only by build metadata have the same precedence.
/// </summary>
/// <param name="other">The semantic version.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared.
/// The return value has these meanings:
/// Less than zero: This instance precedes <paramref name="other" /> in the sort order.
/// Zero: This instance occurs in the same position in the sort order as <paramref name="other" />.
/// Greater than zero: This instance follows <paramref name="other" /> in the sort order.
/// </returns>
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);
}
/// <summary>
///
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="nonemptyIsLower"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Determines whether the specified <see cref="object" /> is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="object" /> to compare with this instance.</param>
/// <returns>
/// <see langword="true"/> if the specified <see cref="object" /> is equal to this instance, otherwise <see langword="false"/>.
/// </returns>
/// <exception cref="InvalidCastException">The <paramref name="obj"/> is not a <see cref="SemVersion"/>.</exception>
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);
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
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;
}
}
/// <summary>
/// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the target object.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> to populate with data.</param>
/// <param name="context">The destination (see <see cref="SerializationInfo"/>) for this serialization.</param>
[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<SemVersion>, IComparable, ISerializable
} // namespace fnecore.Utility

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<InvariantGlobalization>true</InvariantGlobalization>
<Deterministic>False</Deterministic>
</PropertyGroup>
</Project>
Loading…
Cancel
Save

Powered by TurnKey Linux.