Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2105 P25 P1/2 Motorola Talker Alias CRC Check #2106

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/main/java/io/github/dsheirer/edac/CRC16.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/

package io.github.dsheirer.edac;

import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.IntField;

/**
* Utility for calculating the CRC checksum for CRC-16 using polynomial 0x1021 and Initial Fill/Residual of 0xFFFF
*/
public class CRC16
{
/**
* Calculates the 16-bit CRC checksum for the message using polynomial 0x1021 and residual 0xFFFF
* @param message with transmitted 16-bit checksum at the end.
* @return true if the check is correct or false if it fails the CRC check.
*/
public static boolean check(BinaryMessage message)
{
BinaryMessage copy = message.copy();
BinaryMessage polynomial = BinaryMessage.loadHex("11021");
polynomial.rotateLeft(3, 0, 20);
polynomial.setSize(message.size());

int previousX = 0;
for(int x = copy.nextSetBit(0); x < copy.size() - 16; x = copy.nextSetBit(x + 1))
{
polynomial.rotateRight(x - previousX, previousX, 17 + x);
previousX = x;
copy.xor(polynomial);
}

IntField crc = IntField.length16(copy.size() - 16);
return copy.getInt(crc) == 0xFFFF;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ public void receive(IMessage iMessage)
break;
}
}
else if(iMessage instanceof MotorolaTalkerAliasComplete tac)
else if(iMessage instanceof MotorolaTalkerAliasComplete tac && tac.isValid())
{
mTrafficChannelManager.getTalkerAliasManager().update(tac.getRadio(), tac.getAlias());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;

import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.CRC16;
import io.github.dsheirer.message.TimeslotMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.protocol.Protocol;
Expand Down Expand Up @@ -137,11 +139,37 @@ public MotorolaTalkerAliasComplete assemble() throws IllegalStateException
offset += DATA_BLOCK_FRAGMENT_LENGTH;
}

trimTalkerAliasLength(reassembled);
MotorolaTalkerAliasComplete complete = new MotorolaTalkerAliasComplete(reassembled, mHeader.getTalkgroup(),
mHeader.getSequence(), TimeslotMessage.TIMESLOT_0, mMostRecentTimestamp, Protocol.APCO25);

// Data: wwwww sss iiiiii aaaa...aaaa cccc
//
// - w = WACN
// - s = system
// - i = id
// - a = encoded alias
// - c = CRC-16/GSM of the previous bytes
complete.setValid(CRC16.check(reassembled));

mHeader = null;
mDataBlocks.clear();
return complete;
}


/**
* Trims the message length to exclude pad zeros so that the CRC calculation knows where to finish.
* @param message containing an encoded talker alias.
*/
public static void trimTalkerAliasLength(BinaryMessage message)
{
int x = 72; //Minimum bit size WACN + SYS + RADIO + 1 CHARACTER = 18 Hex Characters * 4 Bits Each
while(message.nextSetBit(x) > 0 && x < message.size())
{
x += 8;
}

message.setSize(x);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer, 2024 Ilya Smirnov
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -99,10 +99,15 @@ public String toString()
{
sb.append("TS").append(getTimeslot()).append(" ");
}

if(!isValid())
{
sb.append("(CRC FAILED) ");
}

sb.append("MOTOROLA TALKER ALIAS COMPLETE");
sb.append(" RADIO:").append(getRadio());
sb.append(" TG:").append(getTalkgroup());
sb.append(" ENCODED:").append(getEncodedAlias().toHexString());
sb.append(" ALIAS:").append(getAlias());
sb.append(" SEQUENCE:").append(mSequence);
sb.append(" MSG:").append(getMessage().toHexString());
Expand Down Expand Up @@ -135,19 +140,17 @@ public P25TalkerAliasIdentifier getAlias()
{
byte[] encoded = getEncodedAlias().toByteArray();

// TODO: check CRC in last two bytes to validate alias has no errors
//
// Note: CRC check is performed by assembler.
// Data: wwwww sss iiiiii aaaa...aaaa cccc
//
// - w = WACN
// - s = system
// - i = id
// - a = encoded alias
// - c = CRC-16/GSM of the previous bytes

// Get number of bytes and characters excluding checksum
char bytes = (char)(encoded.length - 2);
char chars = (char)(bytes / 2);
char bytes = (char) (encoded.length - 2);
char chars = (char) (bytes / 2);

// Create array for decoded computed bytes (big-endian)
byte[] decoded = new byte[encoded.length];
Expand All @@ -160,35 +163,37 @@ public P25TalkerAliasIdentifier getAlias()
do
{
// Multiplication step 1
char accumMult = (char)(((accumulator + 65536) % 65536) * 293 + 0x72E9);
char accumMult = (char) (((accumulator + 65536) % 65536) * 293 + 0x72E9);

// Lookup table step
byte lut = LOOKUP_TABLE[encoded[byteIndex] + 128];
byte mult1 = (byte)(lut - (byte)(accumMult >> 8));
byte mult1 = (byte) (lut - (byte) (accumMult >> 8));

// Incrementing step
byte mult2 = 1;
byte shortstop = (byte)(accumMult | 0x1);
byte increment = (byte)(shortstop << 1);
byte shortstop = (byte) (accumMult | 0x1);
byte increment = (byte) (shortstop << 1);

while(mult2 != -1 && shortstop != 1) {
shortstop = (byte)(shortstop + increment);
while(mult2 != -1 && shortstop != 1)
{
shortstop = (byte) (shortstop + increment);
mult2 += 2;
}

// Multiplication step 2
decoded[byteIndex] = (byte)(mult1 * mult2);
decoded[byteIndex] = (byte) (mult1 * mult2);

// Update the accumulator
accumulator += (char)(((encoded[byteIndex] + 256) % 256) + 1);
accumulator += (char) (((encoded[byteIndex] + 256) % 256) + 1);
byteIndex += 1;
}
while(byteIndex < bytes);

// Copy decoded bytes (as chars) to our alias string
String alias = "";
for (char i = 0; i < chars; i++) {
alias += (char)((decoded[i * 2] << 8) | decoded[i * 2 + 1]);
for(char i = 0; i < chars; i++)
{
alias += (char) ((decoded[i * 2] << 8) | decoded[i * 2 + 1]);
}

mAlias = P25TalkerAliasIdentifier.create(alias);
Expand Down Expand Up @@ -267,7 +272,12 @@ public List<Identifier> getIdentifiers()
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
mIdentifiers.add(getAlias());

//Only add the alias if it passes the CRC check.
if(isValid())
{
mIdentifiers.add(getAlias());
}
}

return mIdentifiers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ else if(message instanceof EncryptionSynchronizationSequence ess)
continueState(State.CALL);
}
}
else if(message instanceof MotorolaTalkerAliasComplete tac)
else if(message instanceof MotorolaTalkerAliasComplete tac && tac.isValid())
{
mTrafficChannelManager.getTalkerAliasManager().update(tac.getRadio(), tac.getAlias());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;

import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.CRC16;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.MotorolaTalkerAliasComplete;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure;
import io.github.dsheirer.protocol.Protocol;
Expand Down Expand Up @@ -142,11 +144,36 @@ public MotorolaTalkerAliasComplete assemble() throws IllegalStateException
offset += DATA_BLOCK_FRAGMENT_LENGTH;
}

trimTalkerAliasLength(reassembled);
MotorolaTalkerAliasComplete complete = new MotorolaTalkerAliasComplete(reassembled, mHeader.getTalkgroup(),
mHeader.getSequence(), mTimeslot, mMostRecentTimestamp, Protocol.APCO25_PHASE2);

// Data: wwwww sss iiiiii aaaa...aaaa cccc
//
// - w = WACN
// - s = system
// - i = id
// - a = encoded alias
// - c = CRC-16/GSM of the previous bytes
complete.setValid(CRC16.check(reassembled));

mHeader = null;
mDataBlocks.clear();
return complete;
}

/**
* Trims the message length to exclude pad zeros so that the CRC calculation knows where to finish.
* @param message containing an encoded talker alias.
*/
public static void trimTalkerAliasLength(BinaryMessage message)
{
int x = 72; //Minimum bit size WACN + SYS + RADIO + 1 CHARACTER = 18 Hex Characters * 4 Bits Each
while(message.nextSetBit(x) > 0 && x < message.size())
{
x += 8;
}

message.setSize(x);
}
}
Loading