From 49cf92c7ab8de5e5eeeda8d4ee3560ed34d02b69 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Fri, 6 Dec 2024 04:33:14 -0500 Subject: [PATCH] #2105 P25 P1/2 Motorola Talker Alias CRC Check (#2106) Co-authored-by: Dennis Sheirer --- .../java/io/github/dsheirer/edac/CRC16.java | 53 +++++++++++++++++++ .../decode/p25/phase1/P25P1DecoderState.java | 2 +- .../LCMotorolaTalkerAliasAssembler.java | 28 ++++++++++ .../motorola/MotorolaTalkerAliasComplete.java | 46 +++++++++------- .../decode/p25/phase2/P25P2DecoderState.java | 2 +- .../MotorolaTalkerAliasAssembler.java | 27 ++++++++++ 6 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/edac/CRC16.java diff --git a/src/main/java/io/github/dsheirer/edac/CRC16.java b/src/main/java/io/github/dsheirer/edac/CRC16.java new file mode 100644 index 000000000..accccf329 --- /dev/null +++ b/src/main/java/io/github/dsheirer/edac/CRC16.java @@ -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 + * **************************************************************************** + */ + +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; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java index ffa49414d..44afee550 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java @@ -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()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkerAliasAssembler.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkerAliasAssembler.java index a343f173b..5bb92bae2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkerAliasAssembler.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkerAliasAssembler.java @@ -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; @@ -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); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/MotorolaTalkerAliasComplete.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/MotorolaTalkerAliasComplete.java index de3b7a01d..f2a692d07 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/MotorolaTalkerAliasComplete.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/MotorolaTalkerAliasComplete.java @@ -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 @@ -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()); @@ -135,8 +140,7 @@ 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 @@ -144,10 +148,9 @@ public P25TalkerAliasIdentifier getAlias() // - 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]; @@ -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); @@ -267,7 +272,12 @@ public List 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; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index 7830a50cb..76551b597 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -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()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaTalkerAliasAssembler.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaTalkerAliasAssembler.java index c1d8f90fb..bf1972896 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaTalkerAliasAssembler.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaTalkerAliasAssembler.java @@ -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; @@ -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); + } }