Files
ewsdr/DMRProtocol.pas
T

543 lines
15 KiB
ObjectPascal

unit DMRProtocol;
{
Small, allocation-free DMR burst protocol layer.
The CACH interleave and Hamming/Golay generator matrices are derived from
dsd-fme (src/dmr_bs.c and src/fec.c). The original code is distributed
under the ISC licence; see the dsd-fme source tree for its full notice.
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
SysUtils;
const
DMR_PROTOCOL_BURST_DIBITS = 144;
type
TDMRDibitBurst = array[0..DMR_PROTOCOL_BURST_DIBITS - 1] of Byte;
TDMRBit7 = array[0..6] of Byte;
TDMRBit20 = array[0..19] of Byte;
TDMRBit96 = array[0..95] of Byte;
TDMRCACH = record
Valid: Boolean;
CorrectedBits: Integer;
AccessType: Boolean;
Slot: Integer;
LCSS: Integer;
end;
TDMRSlotType = record
Valid: Boolean;
CorrectedBits: Integer;
ColorCode: Integer;
DataType: Integer;
end;
TDMRLinkControl = record
Decoded: Boolean;
CRCValid: Boolean;
RSCorrectedBytes: Integer;
ProtectedFlag: Boolean;
Reserved: Boolean;
Opcode: Integer;
FeatureSetID: Integer;
ServiceOptions: Integer;
TargetID: LongWord;
SourceID: LongWord;
end;
TDMRBurstInfo = record
CACH: TDMRCACH;
SlotType: TDMRSlotType;
BPTCValid: Boolean;
BPTCIrrecoverableErrors: Integer;
Payload96: TDMRBit96;
LinkControl: TDMRLinkControl;
end;
function DMRHamming74Encode(Value: Byte): TDMRBit7;
function DMRGolay208Encode(Value: Byte): TDMRBit20;
procedure DMRBPTC19696Encode(const Payload: TDMRBit96;
out Burst: TDMRDibitBurst);
procedure DMRFullLCSetParity(var Payload: TDMRBit96; DataType: Integer);
function DMRDecodeCACH(const Burst: TDMRDibitBurst; out CACH: TDMRCACH): Boolean;
function DMRDecodeSlotType(const Burst: TDMRDibitBurst;
out SlotType: TDMRSlotType): Boolean;
function DMRDecodeBPTC19696(const Burst: TDMRDibitBurst;
out Payload: TDMRBit96; out IrrecoverableErrors: Integer): Boolean;
procedure DMRParseBurst(const Burst: TDMRDibitBurst; out Info: TDMRBurstInfo);
function DMRDataTypeName(DataType: Integer): string;
implementation
const
CACH_INTERLEAVE: array[0..23] of Byte = (
0, 7, 8, 9, 1, 10,
11, 12, 2, 13, 14, 15,
3, 16, 4, 17, 18, 19,
5, 20, 21, 22, 6, 23
);
HAMMING_7_4_G: array[0..3, 0..6] of Byte = (
(1, 0, 0, 0, 1, 0, 1),
(0, 1, 0, 0, 1, 1, 1),
(0, 0, 1, 0, 1, 1, 0),
(0, 0, 0, 1, 0, 1, 1)
);
GOLAY_20_8_G: array[0..7, 0..19] of Byte = (
(1,0,0,0,0,0,0,0, 0,0,1,1,1,1,0,1,1,0,1,0),
(0,1,0,0,0,0,0,0, 1,1,0,1,1,0,0,1,1,0,0,1),
(0,0,1,0,0,0,0,0, 0,1,1,0,1,1,0,0,1,1,0,1),
(0,0,0,1,0,0,0,0, 0,0,1,1,0,1,1,0,0,1,1,1),
(0,0,0,0,1,0,0,0, 1,1,0,1,1,1,0,0,0,1,1,0),
(0,0,0,0,0,1,0,0, 1,0,1,0,1,0,0,1,0,1,1,1),
(0,0,0,0,0,0,1,0, 1,0,0,1,0,0,1,1,1,1,1,0),
(0,0,0,0,0,0,0,1, 1,0,0,0,1,1,1,0,1,0,1,1)
);
// Syndrome values for bit positions of the systematic Hamming codewords.
HAMMING_13_9_SYNDROME: array[0..12] of Byte =
(15,14,7,10,5,11,12,6,3, 8,4,2,1);
HAMMING_15_11_SYNDROME: array[0..14] of Byte =
(9,13,15,14,7,10,5,11,12,6,3, 8,4,2,1);
function DMRHamming74Encode(Value: Byte): TDMRBit7;
var
i, j: Integer;
begin
FillChar(Result, SizeOf(Result), 0);
for i := 0 to 3 do
if ((Value shr (3 - i)) and 1) <> 0 then
for j := 0 to 6 do Result[j] := Result[j] xor HAMMING_7_4_G[i, j];
end;
function DMRGolay208Encode(Value: Byte): TDMRBit20;
var
i, j: Integer;
begin
FillChar(Result, SizeOf(Result), 0);
for i := 0 to 7 do
if ((Value shr (7 - i)) and 1) <> 0 then
for j := 0 to 19 do Result[j] := Result[j] xor GOLAY_20_8_G[i, j];
end;
procedure EncodeHamming(var Codeword: array of Byte; DataBits: Integer;
const Syndromes: array of Byte);
var
i, Syndrome: Integer;
begin
Syndrome := 0;
for i := 0 to DataBits - 1 do
if (Codeword[i] and 1) <> 0 then Syndrome := Syndrome xor Syndromes[i];
// The final four systematic parity positions have syndromes 8,4,2,1.
for i := 0 to 3 do Codeword[DataBits + i] := (Syndrome shr (3 - i)) and 1;
end;
function CorrectHamming(var Codeword: array of Byte;
const Syndromes: array of Byte): Boolean;
var
i, Syndrome, Position: Integer;
begin
Syndrome := 0;
for i := 0 to High(Codeword) do
if (Codeword[i] and 1) <> 0 then Syndrome := Syndrome xor Syndromes[i];
if Syndrome = 0 then Exit(True);
Position := -1;
for i := 0 to High(Codeword) do
if Syndromes[i] = Syndrome then begin Position := i; Break; end;
Result := Position >= 0;
if Result then Codeword[Position] := Codeword[Position] xor 1;
end;
procedure DMRBPTC19696Encode(const Payload: TDMRBit96;
out Burst: TDMRDibitBurst);
var
Matrix: array[0..12, 0..14] of Byte;
Deinterleaved, Air: array[0..195] of Byte;
Row: array[0..14] of Byte;
Column: array[0..12] of Byte;
i, j, k: Integer;
begin
FillChar(Burst, SizeOf(Burst), 0);
FillChar(Matrix, SizeOf(Matrix), 0);
k := 0;
for j := 3 to 10 do begin Matrix[0, j] := Payload[k] and 1; Inc(k); end;
for i := 1 to 8 do
for j := 0 to 10 do begin Matrix[i, j] := Payload[k] and 1; Inc(k); end;
for i := 0 to 8 do
begin
for j := 0 to 14 do Row[j] := Matrix[i, j];
EncodeHamming(Row, 11, HAMMING_15_11_SYNDROME);
for j := 0 to 14 do Matrix[i, j] := Row[j];
end;
for j := 0 to 14 do
begin
for i := 0 to 12 do Column[i] := Matrix[i, j];
EncodeHamming(Column, 9, HAMMING_13_9_SYNDROME);
for i := 0 to 12 do Matrix[i, j] := Column[i];
end;
Deinterleaved[0] := 0; // R(3)
k := 1;
for i := 0 to 12 do
for j := 0 to 14 do begin Deinterleaved[k] := Matrix[i, j]; Inc(k); end;
for i := 0 to 195 do Air[i] := Deinterleaved[(i * 13) mod 196];
k := 0;
for i := 12 to 60 do
begin
Burst[i] := (Air[k] shl 1) or Air[k + 1]; Inc(k, 2);
end;
for i := 95 to 143 do
begin
Burst[i] := (Air[k] shl 1) or Air[k + 1]; Inc(k, 2);
end;
end;
function DecodeHamming74(const Received: TDMRBit7; out Value: Byte;
out Corrected: Integer): Boolean;
var
Candidate: TDMRBit7;
V, i, Distance, BestDistance: Integer;
begin
Value := 0;
BestDistance := 8;
for V := 0 to 15 do
begin
Candidate := DMRHamming74Encode(V);
Distance := 0;
for i := 0 to 6 do Inc(Distance, Candidate[i] xor (Received[i] and 1));
if Distance < BestDistance then
begin
BestDistance := Distance;
Value := V;
end;
end;
Corrected := BestDistance;
Result := BestDistance <= 1;
end;
function DecodeGolay208(const Received: TDMRBit20; out Value: Byte;
out Corrected: Integer): Boolean;
var
Candidate: TDMRBit20;
V, i, Distance, BestDistance: Integer;
begin
Value := 0;
BestDistance := 21;
for V := 0 to 255 do
begin
Candidate := DMRGolay208Encode(V);
Distance := 0;
for i := 0 to 19 do Inc(Distance, Candidate[i] xor (Received[i] and 1));
if Distance < BestDistance then
begin
BestDistance := Distance;
Value := V;
if Distance = 0 then Break;
end;
end;
Corrected := BestDistance;
Result := BestDistance <= 2;
end;
function DMRDecodeCACH(const Burst: TDMRDibitBurst; out CACH: TDMRCACH): Boolean;
var
Deinterleaved: array[0..23] of Byte;
TACT: TDMRBit7;
Value: Byte;
i: Integer;
begin
FillChar(CACH, SizeOf(CACH), 0);
FillChar(Deinterleaved, SizeOf(Deinterleaved), 0);
for i := 0 to 11 do
begin
Deinterleaved[CACH_INTERLEAVE[2 * i]] := (Burst[i] shr 1) and 1;
Deinterleaved[CACH_INTERLEAVE[2 * i + 1]] := Burst[i] and 1;
end;
for i := 0 to 6 do TACT[i] := Deinterleaved[i];
CACH.Valid := DecodeHamming74(TACT, Value, CACH.CorrectedBits);
if CACH.Valid then
begin
CACH.AccessType := (Value and 8) <> 0;
CACH.Slot := (Value shr 2) and 1;
CACH.LCSS := Value and 3;
end;
Result := CACH.Valid;
end;
function DMRDecodeSlotType(const Burst: TDMRDibitBurst;
out SlotType: TDMRSlotType): Boolean;
var
Bits: TDMRBit20;
Value: Byte;
i, k: Integer;
begin
FillChar(SlotType, SizeOf(SlotType), 0);
k := 0;
for i := 61 to 65 do
begin
Bits[k] := (Burst[i] shr 1) and 1; Inc(k);
Bits[k] := Burst[i] and 1; Inc(k);
end;
for i := 90 to 94 do
begin
Bits[k] := (Burst[i] shr 1) and 1; Inc(k);
Bits[k] := Burst[i] and 1; Inc(k);
end;
SlotType.Valid := DecodeGolay208(Bits, Value, SlotType.CorrectedBits);
if SlotType.Valid then
begin
SlotType.ColorCode := (Value shr 4) and $0F;
SlotType.DataType := Value and $0F;
end;
Result := SlotType.Valid;
end;
function DMRDecodeBPTC19696(const Burst: TDMRDibitBurst;
out Payload: TDMRBit96; out IrrecoverableErrors: Integer): Boolean;
var
Air, Deinterleaved: array[0..195] of Byte;
Matrix: array[0..12, 0..14] of Byte;
Row: array[0..14] of Byte;
Column: array[0..12] of Byte;
i, j, k, Pass: Integer;
begin
FillChar(Payload, SizeOf(Payload), 0);
k := 0;
for i := 12 to 60 do
begin
Air[k] := (Burst[i] shr 1) and 1; Inc(k);
Air[k] := Burst[i] and 1; Inc(k);
end;
for i := 95 to 143 do
begin
Air[k] := (Burst[i] shr 1) and 1; Inc(k);
Air[k] := Burst[i] and 1; Inc(k);
end;
for i := 0 to 195 do Deinterleaved[(i * 13) mod 196] := Air[i];
k := 1; // discard R(3)
for i := 0 to 12 do
for j := 0 to 14 do begin Matrix[i, j] := Deinterleaved[k]; Inc(k); end;
IrrecoverableErrors := 0;
// Alternating row/column passes allow a correction in one dimension to
// make a previously ambiguous syndrome correctable in the other.
for Pass := 0 to 1 do
begin
if Pass = 1 then IrrecoverableErrors := 0;
for i := 0 to 8 do
begin
for j := 0 to 14 do Row[j] := Matrix[i, j];
if not CorrectHamming(Row, HAMMING_15_11_SYNDROME) then
Inc(IrrecoverableErrors);
for j := 0 to 10 do Matrix[i, j] := Row[j];
end;
for j := 0 to 14 do
begin
for i := 0 to 12 do Column[i] := Matrix[i, j];
if not CorrectHamming(Column, HAMMING_13_9_SYNDROME) then
Inc(IrrecoverableErrors);
for i := 0 to 8 do Matrix[i, j] := Column[i];
end;
end;
k := 0;
for j := 3 to 10 do begin Payload[k] := Matrix[0, j]; Inc(k); end;
for i := 1 to 8 do
for j := 0 to 10 do begin Payload[k] := Matrix[i, j]; Inc(k); end;
Result := IrrecoverableErrors = 0;
end;
function BitsToUInt(const Bits: TDMRBit96; Start, Count: Integer): LongWord;
var
i: Integer;
begin
Result := 0;
for i := 0 to Count - 1 do Result := (Result shl 1) or (Bits[Start + i] and 1);
end;
function GFMul(A, B: Byte): Byte;
var
Carry: Boolean;
P: Byte;
i: Integer;
begin
P := 0;
for i := 0 to 7 do
begin
if (B and 1) <> 0 then P := P xor A;
Carry := (A and $80) <> 0;
A := A shl 1;
if Carry then A := A xor $1D; // GF(256), primitive polynomial x^8+x^4+x^3+x^2+1
B := B shr 1;
end;
Result := P;
end;
procedure BytesToBits(const Bytes: array of Byte; var Bits: TDMRBit96);
var
i, j: Integer;
begin
for i := 0 to 11 do
for j := 0 to 7 do Bits[i * 8 + j] := (Bytes[i] shr (7 - j)) and 1;
end;
procedure BitsToBytes(const Bits: TDMRBit96; var Bytes: array of Byte);
var
i, j: Integer;
begin
for i := 0 to 11 do
begin
Bytes[i] := 0;
for j := 0 to 7 do Bytes[i] := (Bytes[i] shl 1) or (Bits[i * 8 + j] and 1);
end;
end;
procedure RS129Syndrome(const Codeword: array of Byte; out S0, S1, S2: Byte);
var
i: Integer;
begin
S0 := 0; S1 := 0; S2 := 0;
for i := 0 to 11 do
begin
S0 := Codeword[i] xor GFMul(2, S0);
S1 := Codeword[i] xor GFMul(4, S1);
S2 := Codeword[i] xor GFMul(8, S2);
end;
end;
function FullLCMask(DataType: Integer): LongWord;
begin
if DataType = 1 then Result := $969696
else if DataType = 2 then Result := $999999
else Result := 0;
end;
procedure DMRFullLCSetParity(var Payload: TDMRBit96; DataType: Integer);
var
Bytes: array[0..11] of Byte;
Feedback: Byte;
Mask: LongWord;
i: Integer;
begin
BitsToBytes(Payload, Bytes);
Bytes[9] := 0; Bytes[10] := 0; Bytes[11] := 0;
for i := 0 to 8 do
begin
Feedback := Bytes[i] xor Bytes[9];
Bytes[9] := Bytes[10] xor GFMul($0E, Feedback);
Bytes[10] := Bytes[11] xor GFMul($38, Feedback);
Bytes[11] := GFMul($40, Feedback);
end;
Mask := FullLCMask(DataType);
Bytes[9] := Bytes[9] xor Byte(Mask shr 16);
Bytes[10] := Bytes[10] xor Byte(Mask shr 8);
Bytes[11] := Bytes[11] xor Byte(Mask);
BytesToBits(Bytes, Payload);
end;
function ValidateAndCorrectFullLC(var Payload: TDMRBit96; DataType: Integer;
out CorrectedBytes: Integer): Boolean;
var
Bytes: array[0..11] of Byte;
S0, S1, S2: Byte;
Mask: LongWord;
Position, Delta: Integer;
begin
CorrectedBytes := 0;
BitsToBytes(Payload, Bytes);
Mask := FullLCMask(DataType);
Bytes[9] := Bytes[9] xor Byte(Mask shr 16);
Bytes[10] := Bytes[10] xor Byte(Mask shr 8);
Bytes[11] := Bytes[11] xor Byte(Mask);
RS129Syndrome(Bytes, S0, S1, S2);
if (S0 <> 0) or (S1 <> 0) or (S2 <> 0) then
begin
// RS(12,9) has three check symbols and corrects one complete byte. Full
// Berlekamp-Massey machinery is unnecessary at this block size: search
// the 12*255 possible one-symbol corrections and require zero syndrome.
for Position := 0 to 11 do
begin
for Delta := 1 to 255 do
begin
Bytes[Position] := Bytes[Position] xor Byte(Delta);
RS129Syndrome(Bytes, S0, S1, S2);
if (S0 = 0) and (S1 = 0) and (S2 = 0) then
begin
CorrectedBytes := 1;
Break;
end;
Bytes[Position] := Bytes[Position] xor Byte(Delta);
end;
if CorrectedBytes <> 0 then Break;
end;
if CorrectedBytes = 0 then Exit(False);
end;
// Payload data may itself have been the corrected RS symbol.
Bytes[9] := Bytes[9] xor Byte(Mask shr 16);
Bytes[10] := Bytes[10] xor Byte(Mask shr 8);
Bytes[11] := Bytes[11] xor Byte(Mask);
BytesToBits(Bytes, Payload);
Result := True;
end;
procedure DMRParseBurst(const Burst: TDMRDibitBurst; out Info: TDMRBurstInfo);
begin
FillChar(Info, SizeOf(Info), 0);
DMRDecodeCACH(Burst, Info.CACH);
DMRDecodeSlotType(Burst, Info.SlotType);
if Info.SlotType.Valid and (Info.SlotType.DataType in [0..7, 9, 11]) then
begin
Info.BPTCValid := DMRDecodeBPTC19696(Burst, Info.Payload96,
Info.BPTCIrrecoverableErrors);
if Info.BPTCValid and (Info.SlotType.DataType in [1, 2]) then
begin
Info.LinkControl.CRCValid := ValidateAndCorrectFullLC(Info.Payload96,
Info.SlotType.DataType, Info.LinkControl.RSCorrectedBytes);
Info.LinkControl.Decoded := Info.LinkControl.CRCValid;
if Info.LinkControl.Decoded then
begin
Info.LinkControl.ProtectedFlag := Info.Payload96[0] <> 0;
Info.LinkControl.Reserved := Info.Payload96[1] <> 0;
Info.LinkControl.Opcode := BitsToUInt(Info.Payload96, 2, 6);
Info.LinkControl.FeatureSetID := BitsToUInt(Info.Payload96, 8, 8);
Info.LinkControl.ServiceOptions := BitsToUInt(Info.Payload96, 16, 8);
Info.LinkControl.TargetID := BitsToUInt(Info.Payload96, 24, 24);
Info.LinkControl.SourceID := BitsToUInt(Info.Payload96, 48, 24);
end;
end;
end;
end;
function DMRDataTypeName(DataType: Integer): string;
begin
case DataType of
0: Result := 'PI header';
1: Result := 'voice LC';
2: Result := 'terminator LC';
3: Result := 'CSBK';
4: Result := 'MBC header';
5: Result := 'MBC continuation';
6: Result := 'data header';
7: Result := 'rate 1/2 data';
8: Result := 'rate 3/4 data';
9: Result := 'idle';
10: Result := 'rate 1 data';
11: Result := 'unified single block';
else
Result := 'reserved ' + IntToStr(DataType);
end;
end;
end.