-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFneSystemBase.cs
More file actions
550 lines (457 loc) · 19.8 KB
/
FneSystemBase.cs
File metadata and controls
550 lines (457 loc) · 19.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
// 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-2026 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;
using fnecore.P25.LC.TSBK;
namespace fnecore
{
/// <summary>
/// Metadata class containing remote call data.
/// </summary>
public class RemoteCallData
{
/// <summary>
/// Source ID.
/// </summary>
public uint SrcId = 0;
/// <summary>
/// Destination ID.
/// </summary>
public uint DstId = 0;
/// <summary>
/// Link-Control Opcode.
/// </summary>
public byte LCO = 0;
/// <summary>
/// Manufacturer ID.
/// </summary>
public byte MFId = 0;
/// <summary>
/// Service Options.
/// </summary>
public byte ServiceOptions = 0;
/// <summary>
/// Low-speed Data Byte 1
/// </summary>
public byte LSD1 = 0;
/// <summary>
/// Low-speed Data Byte 2
/// </summary>
public byte LSD2 = 0;
/// <summary>
/// Encryption Message Indicator
/// </summary>
public byte[] MessageIndicator = new byte[P25Defines.P25_MI_LENGTH];
/// <summary>
/// Algorithm ID.
/// </summary>
public byte AlgorithmId = P25Defines.P25_ALGO_UNENCRYPT;
/// <summary>
/// Key ID.
/// </summary>
public ushort KeyId = 0;
/// <summary>
///
/// </summary>
public uint TxStreamID = 0;
/// <summary>
///
/// </summary>
public FrameType FrameType = FrameType.TERMINATOR;
/// <summary>
///
/// </summary>
public byte Slot = 0;
/*
** Methods
*/
/// <summary>
/// Reset values.
/// </summary>
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
/// <summary>
/// Data structure representing encryption parameters.
/// </summary>
public class CryptoParams
{
/// <summary>
/// Algorithm ID
/// </summary>
public byte AlgoId = P25Defines.P25_ALGO_UNENCRYPT;
/// <summary>
/// Encryption Key ID
/// </summary>
public ushort KeyId = 0;
/// <summary>
/// Message Indicator
/// </summary>
public byte[] MI = new byte[P25Defines.P25_MI_LENGTH];
} // public class CryptoParams
/// <summary>
/// Implements a FNE system.
/// </summary>
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
*/
/// <summary>
/// Gets the system name for this <see cref="FneSystemBase"/>.
/// </summary>
public string SystemName
{
get
{
if (fne != null)
return fne.SystemName;
return string.Empty;
}
}
/// <summary>
/// Gets the peer ID for this <see cref="FneSystemBase"/>.
/// </summary>
public uint PeerId
{
get
{
if (fne != null)
return fne.PeerId;
return uint.MaxValue;
}
}
/// <summary>
/// Flag indicating whether this <see cref="FneSystemBase"/> is running.
/// </summary>
public bool IsStarted
{
get
{
if (fne != null)
return fne.IsStarted;
return false;
}
}
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="FneSystemBase"/> class.
/// </summary>
/// <param name="fne">Instance of <see cref="FnePeer"/></param>
/// <param name="fneLogLevel"></param>
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;
}
/// <summary>
/// Starts the main execution loop for this <see cref="FneSystemBase"/>.
/// </summary>
public virtual void Start()
{
if (!fne.IsStarted)
fne.Start();
}
/// <summary>
/// Stops the main execution loop for this <see cref="FneSystemBase"/>.
/// </summary>
public virtual void Stop()
{
if (fne.IsStarted)
fne.Stop();
}
/// <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>
protected abstract bool DMRDataValidate(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId, byte[] message);
/// <summary>
/// Event handler used to process incoming DMR data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void DMRDataReceived(object sender, DMRDataReceivedEvent e);
/// <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>
protected abstract bool P25DataValidate(uint peerId, uint srcId, uint dstId, CallType callType, P25DUID duid, FrameType frameType, uint streamId, byte[] message);
/// <summary>
/// Event handler used to pre-process incoming P25 data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void P25DataPreprocess(object sender, P25DataReceivedEvent e);
/// <summary>
/// Event handler used to process incoming P25 data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void P25DataReceived(object sender, P25DataReceivedEvent e);
/// <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>
protected abstract bool NXDNDataValidate(uint peerId, uint srcId, uint dstId, CallType callType, NXDNMessageType messageType, FrameType frameType, uint streamId, byte[] message);
/// <summary>
/// Event handler used to process incoming NXDN data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void NXDNDataReceived(object sender, NXDNDataReceivedEvent e);
/// <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>
protected abstract bool PeerIgnored(uint peerId, uint srcId, uint dstId, byte slot, CallType callType, FrameType frameType, DMRDataType dataType, uint streamId);
/// <summary>
/// Event handler used to handle a peer connected event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void PeerConnected(object sender, PeerConnectedEvent e);
/// <summary>
/// Event handler used to handle a key response
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void KeyResponse(object sender, KeyResponseEvent e);
/// <summary>
/// Creates an DMR frame message.
/// </summary>
/// <param name="data"></param>
/// <param name="callData"></param>
/// <param name="n"></param>
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;
}
/// <summary>
/// Helper to send a DMR terminator with LC message.
/// </summary>
/// <param name="callData"></param>
/// <param name="seqNo"></param>
/// <param name="dmrN"></param>
/// <param name="embeddedData"></param>
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<byte, byte>(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<byte, byte>(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_DMR), dmrpkt, pktSeq, callData.TxStreamID);
seqNo = 0;
dmrN = 0;
}
/// <summary>
/// Creates an P25 frame message header.
/// </summary>
/// <param name="duid"></param>
/// <param name="callData"></param>
/// <param name="data"></param>
/// <param name="cryptoParams"></param>
public void CreateP25MessageHdr(byte duid, RemoteCallData callData, ref byte[] data, CryptoParams cryptoParams = null)
{
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] = P25Defines.P25_FT_DATA_UNIT; // Frame Type
if (cryptoParams != null)
{
data[180U] = P25Defines.P25_FT_HDU_VALID; // Frame Type
data[14U] |= 0x08; // Control bit
data[181U] = cryptoParams.AlgoId; // Algorithm ID
FneUtils.WriteBytes(cryptoParams.KeyId, ref data, 182); // Key ID
if (cryptoParams.MI != null)
Array.Copy(cryptoParams.MI, 0, data, 184, P25Defines.P25_MI_LENGTH); // Message Indicator
}
}
/// <summary>
/// Helper to send a P25 TSDU message.
/// </summary>
/// <param name="tsbk"></param>
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, Constants.RtpCallEndSeq, callData.TxStreamID);
}
/// <summary>
/// Helper to send a P25 TDU message.
/// </summary>
/// <param name="callData"></param>
/// <param name="grantDemand"></param>
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, Constants.RtpCallEndSeq, callData.TxStreamID);
}
} // public abstract class FneSystemBase
} // namespace fnecore