// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* 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 Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using fnecore.EDAC;
using fnecore.DMR;
using fnecore.P25;
using fnecore.NXDN;
namespace fnecore
{
///
/// Metadata class containing remote call data.
///
public class RemoteCallData
{
///
/// Source ID.
///
public uint SrcId = 0;
///
/// Destination ID.
///
public uint DstId = 0;
///
/// Link-Control Opcode.
///
public byte LCO = 0;
///
/// Manufacturer ID.
///
public byte MFId = 0;
///
/// Service Options.
///
public byte ServiceOptions = 0;
///
/// Low-speed Data Byte 1
///
public byte LSD1 = 0;
///
/// Low-speed Data Byte 2
///
public byte LSD2 = 0;
///
/// Encryption Message Indicator
///
public byte[] MessageIndicator = new byte[P25Defines.P25_MI_LENGTH];
///
/// Algorithm ID.
///
public byte AlgorithmId = P25Defines.P25_ALGO_UNENCRYPT;
///
/// Key ID.
///
public ushort KeyId = 0;
///
///
///
public uint TxStreamID = 0;
///
///
///
public FrameType FrameType = FrameType.TERMINATOR;
///
///
///
public byte Slot = 0;
/*
** Methods
*/
///
/// Reset values.
///
public virtual void Reset()
{
SrcId = 0;
DstId = 0;
LCO = 0;
MFId = 0;
ServiceOptions = 0;
LSD1 = 0;
LSD2 = 0;
MessageIndicator = new byte[P25Defines.P25_MI_LENGTH];
AlgorithmId = P25Defines.P25_ALGO_UNENCRYPT;
KeyId = 0;
FrameType = FrameType.TERMINATOR;
Slot = 0;
}
} // public class RemoteCallData
///
/// Implements a FNE system.
///
public abstract class FneSystemBase
{
protected FnePeer fne;
protected const int DMR_FRAME_LENGTH_BYTES = 33;
protected const int DMR_PACKET_SIZE = 55;
protected static readonly byte[] DMR_SILENCE_DATA = { 0x01, 0x00,
0xB9, 0xE8, 0x81, 0x52, 0x61, 0x73, 0x00, 0x2A, 0x6B, 0xB9, 0xE8,
0x81, 0x52, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x73, 0x00,
0x2A, 0x6B, 0xB9, 0xE8, 0x81, 0x52, 0x61, 0x73, 0x00, 0x2A, 0x6B };
protected const int P25_MSG_HDR_SIZE = 24;
/*
** Properties
*/
///
/// Gets the system name for this .
///
public string SystemName
{
get
{
if (fne != null)
return fne.SystemName;
return string.Empty;
}
}
///
/// Gets the peer ID for this .
///
public uint PeerId
{
get
{
if (fne != null)
return fne.PeerId;
return uint.MaxValue;
}
}
///
/// Flag indicating whether this is running.
///
public bool IsStarted
{
get
{
if (fne != null)
return fne.IsStarted;
return false;
}
}
/*
** Methods
*/
///
/// Initializes a new instance of the class.
///
/// Instance of
///
public FneSystemBase(FnePeer fne, LogLevel fneLogLevel = LogLevel.INFO)
{
this.fne = fne;
// hook various FNE network callbacks
this.fne.DMRDataValidate = DMRDataValidate;
this.fne.DMRDataReceived += DMRDataReceived;
this.fne.P25DataValidate = P25DataValidate;
this.fne.P25DataPreprocess += P25DataPreprocess;
this.fne.P25DataReceived += P25DataReceived;
this.fne.NXDNDataValidate = NXDNDataValidate;
this.fne.NXDNDataReceived += NXDNDataReceived;
this.fne.PeerIgnored = PeerIgnored;
this.fne.PeerConnected += PeerConnected;
this.fne.KeyResponse += KeyResponse;
// hook logger callback
this.fne.LogLevel = fneLogLevel;
}
///
/// Starts the main execution loop for this .
///
public virtual void Start()
{
if (!fne.IsStarted)
fne.Start();
}
///
/// Stops the main execution loop for this .
///
public virtual void Stop()
{
if (fne.IsStarted)
fne.Stop();
}
///
/// Callback used to validate incoming DMR data.
///
/// Peer ID
/// Source Address
/// Destination Address
/// Slot Number
/// Call Type (Group or Private)
/// Frame Type
/// DMR Data Type
/// Stream ID
/// Raw message data
/// True, if data stream is valid, otherwise false.
protected abstract bool DMRDataValidate(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId, byte[] message);
///
/// Event handler used to process incoming DMR data.
///
///
///
protected abstract void DMRDataReceived(object sender, DMRDataReceivedEvent e);
///
/// Callback used to validate incoming P25 data.
///
/// Peer ID
/// Source Address
/// Destination Address
/// Call Type (Group or Private)
/// P25 DUID
/// Frame Type
/// Stream ID
/// Raw message data
/// True, if data stream is valid, otherwise false.
protected abstract bool P25DataValidate(uint peerId, uint srcId, uint dstId, CallType callType, P25DUID duid, FrameType frameType, uint streamId, byte[] message);
///
/// Event handler used to pre-process incoming P25 data.
///
///
///
protected abstract void P25DataPreprocess(object sender, P25DataReceivedEvent e);
///
/// Event handler used to process incoming P25 data.
///
///
///
protected abstract void P25DataReceived(object sender, P25DataReceivedEvent e);
///
/// Callback used to validate incoming NXDN data.
///
/// Peer ID
/// Source Address
/// Destination Address
/// Call Type (Group or Private)
/// NXDN Message Type
/// Frame Type
/// Stream ID
/// Raw message data
/// True, if data stream is valid, otherwise false.
protected abstract bool NXDNDataValidate(uint peerId, uint srcId, uint dstId, CallType callType, NXDNMessageType messageType, FrameType frameType, uint streamId, byte[] message);
///
/// Event handler used to process incoming NXDN data.
///
///
///
protected abstract void NXDNDataReceived(object sender, NXDNDataReceivedEvent e);
///
/// Callback used to process whether or not a peer is being ignored for traffic.
///
/// Peer ID
/// Source Address
/// Destination Address
/// Slot Number
/// Call Type (Group or Private)
/// Frame Type
/// DMR Data Type
/// Stream ID
/// True, if peer is ignored, otherwise false.
protected abstract bool PeerIgnored(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId);
///
/// Event handler used to handle a peer connected event.
///
///
///
protected abstract void PeerConnected(object sender, PeerConnectedEvent e);
///
/// Event handler used to handle a key response
///
///
///
protected abstract void KeyResponse(object sender, KeyResponseEvent e);
///
/// Creates an DMR frame message.
///
///
///
///
protected void CreateDMRMessage(ref byte[] data, RemoteCallData callData, byte seqNo, byte n)
{
FneUtils.StringToBytes(Constants.TAG_DMR_DATA, data, 0, Constants.TAG_DMR_DATA.Length);
FneUtils.Write3Bytes(callData.SrcId, ref data, 5); // Source Address
FneUtils.Write3Bytes(callData.DstId, ref data, 8); // Destination Address
data[15U] = (byte)((callData.Slot == 1) ? 0x00 : 0x80); // Slot Number
data[15U] |= 0x00; // Group
if (callData.FrameType == FrameType.VOICE_SYNC)
data[15U] |= 0x10;
else if (callData.FrameType == FrameType.VOICE)
data[15U] |= n;
else
data[15U] |= (byte)(0x20 | (byte)callData.FrameType);
data[4U] = seqNo;
}
///
/// Helper to send a DMR terminator with LC message.
///
///
///
///
///
protected virtual void SendDMRTerminator(RemoteCallData callData, ref int seqNo, ref byte dmrN, EmbeddedData embeddedData)
{
byte n = (byte)((seqNo - 3U) % 6U);
uint fill = 6U - n;
FnePeer peer = (FnePeer)fne;
ushort pktSeq = peer.pktSeq(true);
byte[] data = null, dmrpkt = null;
if (n > 0U)
{
for (uint i = 0U; i < fill; i++)
{
// generate DMR AMBE data
data = new byte[DMR_FRAME_LENGTH_BYTES];
Buffer.BlockCopy(DMR_SILENCE_DATA, 0, data, 0, DMR_FRAME_LENGTH_BYTES);
byte lcss = embeddedData.GetData(ref data, n);
// generated embedded signalling
EMB emb = new EMB();
emb.ColorCode = 0;
emb.LCSS = lcss;
emb.Encode(ref data);
// generate DMR network frame
dmrpkt = new byte[DMR_PACKET_SIZE];
callData.FrameType = FrameType.DATA_SYNC;
CreateDMRMessage(ref dmrpkt, callData, (byte)seqNo, n);
Buffer.BlockCopy(data, 0, dmrpkt, 20, DMR_FRAME_LENGTH_BYTES);
peer.SendMaster(new Tuple(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_DMR), dmrpkt, pktSeq, callData.TxStreamID);
seqNo++;
dmrN++;
}
}
data = new byte[DMR_FRAME_LENGTH_BYTES];
// generate DMR LC
LC dmrLC = new LC();
dmrLC.FLCO = (byte)DMRFLCO.FLCO_GROUP;
dmrLC.SrcId = callData.SrcId;
dmrLC.DstId = callData.DstId;
// generate the Slot TYpe
SlotType slotType = new SlotType();
slotType.DataType = (byte)DMRDataType.TERMINATOR_WITH_LC;
slotType.GetData(ref data);
FullLC.Encode(dmrLC, ref data, DMRDataType.TERMINATOR_WITH_LC);
// generate DMR network frame
dmrpkt = new byte[DMR_PACKET_SIZE];
callData.FrameType = FrameType.DATA_SYNC;
CreateDMRMessage(ref dmrpkt, callData, (byte)seqNo, 0);
Buffer.BlockCopy(data, 0, dmrpkt, 20, DMR_FRAME_LENGTH_BYTES);
peer.SendMaster(new Tuple(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_DMR), dmrpkt, pktSeq, callData.TxStreamID);
seqNo = 0;
dmrN = 0;
}
///
/// Creates an P25 frame message header.
///
///
///
protected void CreateP25MessageHdr(byte duid, RemoteCallData callData, ref byte[] data)
{
FneUtils.StringToBytes(Constants.TAG_P25_DATA, data, 0, Constants.TAG_P25_DATA.Length);
data[4U] = callData.LCO; // LCO
FneUtils.Write3Bytes(callData.SrcId, ref data, 5); // Source Address
FneUtils.Write3Bytes(callData.DstId, ref data, 8); // Destination Address
data[11U] = 0; // System ID
data[12U] = 0;
data[14U] = 0; // Control Byte
data[15U] = callData.MFId; // MFId
data[16U] = 0; // Network ID
data[17U] = 0;
data[18U] = 0;
data[20U] = callData.LSD1; // LSD 1
data[21U] = callData.LSD2; // LSD 2
data[22U] = duid; // DUID
data[180U] = 0; // Frame Type
}
///
/// Helper to send a P25 TSDU message.
///
///
public virtual void SendP25TSBK(RemoteCallData callData, byte[] tsbk)
{
if (tsbk.Length != P25Defines.P25_TSBK_LENGTH_BYTES)
throw new InvalidOperationException($"TSBK length must be {P25Defines.P25_TSBK_LENGTH_BYTES}, passed length is {tsbk.Length}");
Trellis trellis = new Trellis();
byte[] payload = new byte[200];
CreateP25MessageHdr((byte)P25DUID.TSDU, callData, ref payload);
// pack raw P25 TSDU bytes
byte[] tsbkTrellis = new byte[P25Defines.P25_TSBK_FEC_LENGTH_BYTES];
trellis.Encode12(tsbk, ref tsbkTrellis);
byte[] raw = new byte[P25Defines.P25_TSDU_FRAME_LENGTH_BYTES];
P25Interleaver.Encode(tsbkTrellis, ref raw, 114, 318);
Buffer.BlockCopy(raw, 0, payload, 24, raw.Length);
payload[23U] = (byte)(P25_MSG_HDR_SIZE + raw.Length);
FnePeer peer = (FnePeer)fne;
ushort pktSeq = peer.pktSeq(true);
peer.SendMaster(FneBase.CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, callData.TxStreamID);
}
///
/// Helper to send a P25 TDU message.
///
///
///
public virtual void SendP25TDU(RemoteCallData callData, bool grantDemand = false)
{
byte[] payload = new byte[200];
CreateP25MessageHdr((byte)P25DUID.TDU, callData, ref payload);
payload[23U] = P25_MSG_HDR_SIZE;
// if this TDU is demanding a grant, set the grant demand control bit
if (grantDemand)
payload[14U] |= 0x80;
FnePeer peer = (FnePeer)fne;
ushort pktSeq = peer.pktSeq(true);
peer.SendMaster(FneBase.CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, callData.TxStreamID);
}
} // public abstract class FneSystemBase
} // namespace fnecore