From 7962758e056fa867535377b73f53d158e13bf5c9 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sat, 25 Jul 2020 07:55:12 -0400 Subject: [PATCH] #866 Cellocator Packet Support (#875) Resolves #866 Implements cellocator packet support. --- .../dsheirer/module/decode/ip/IPacket.java | 14 +- .../decode/ip/PacketMessageFactory.java | 9 + .../ip/UnknownCompressedHeaderPacket.java | 9 + .../module/decode/ip/UnknownPacket.java | 9 + .../module/decode/ip/ars/ARSPacket.java | 9 + .../ip/cellocator/AcknowledgeMessage.java | 141 +++++++++++ .../cellocator/CellocatorRadioIdentifier.java | 68 +++++ .../module/decode/ip/cellocator/Command.java | 109 ++++++++ .../ip/cellocator/CommunicationControl.java | 111 ++++++++ .../ip/cellocator/ForwardedDataMessage.java | 135 ++++++++++ .../decode/ip/cellocator/FragmentControl.java | 70 ++++++ .../ip/cellocator/GenericCommandMessage.java | 93 +++++++ .../ip/cellocator/LocationStatusMessage.java | 237 ++++++++++++++++++ .../decode/ip/cellocator/MCGPHeader.java | 127 ++++++++++ .../ip/cellocator/MCGPMessageFactory.java | 77 ++++++ .../decode/ip/cellocator/MCGPMessageType.java | 127 ++++++++++ .../decode/ip/cellocator/MCGPPacket.java | 79 ++++++ .../decode/ip/cellocator/ModularDataType.java | 44 ++++ .../ip/cellocator/ModularRequestMessage.java | 110 ++++++++ .../ip/cellocator/ModularResponseMessage.java | 139 ++++++++++ .../decode/ip/cellocator/PacketControl.java | 60 +++++ .../cellocator/ProgrammingCommandMessage.java | 104 ++++++++ .../cellocator/ProgrammingStatusMessage.java | 101 ++++++++ .../ip/cellocator/UnknownMCGPMessage.java | 70 ++++++ .../module/decode/ip/ipv4/IPV4Packet.java | 23 ++ .../module/decode/ip/udp/UDPPacket.java | 31 ++- .../decode/p25/phase1/P25P1DecoderState.java | 28 +++ .../decode/p25/phase1/P25P1MessageFramer.java | 22 +- .../message/pdu/packet/PacketMessage.java | 6 +- .../io/github/dsheirer/protocol/Protocol.java | 1 + 30 files changed, 2137 insertions(+), 26 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/AcknowledgeMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CellocatorRadioIdentifier.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/Command.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CommunicationControl.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ForwardedDataMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/FragmentControl.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/GenericCommandMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/LocationStatusMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPHeader.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageFactory.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageType.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPPacket.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularDataType.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularRequestMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularResponseMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/PacketControl.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingCommandMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingStatusMessage.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/cellocator/UnknownMCGPMessage.java diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/IPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/IPacket.java index 90d1e4f6..8bad8945 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/IPacket.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/IPacket.java @@ -20,6 +20,9 @@ package io.github.dsheirer.module.decode.ip; +import io.github.dsheirer.identifier.Identifier; +import java.util.List; + /** * Data packet interface */ @@ -28,16 +31,21 @@ public interface IPacket /** * Header for the data packet. */ - public IHeader getHeader(); + IHeader getHeader(); /** * Optional payload for the packet * @return null if there is no payload */ - public IPacket getPayload(); + IPacket getPayload(); /** * Indicates if this packet contains an optional payload */ - public boolean hasPayload(); + boolean hasPayload(); + + /** + * List of identifiers for this packet and any child payload packets. + */ + List getIdentifiers(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/PacketMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/ip/PacketMessageFactory.java index 81b771bd..72423691 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/PacketMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/PacketMessageFactory.java @@ -22,6 +22,8 @@ package io.github.dsheirer.module.decode.ip; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.module.decode.ip.ars.ARSPacket; +import io.github.dsheirer.module.decode.ip.cellocator.MCGPHeader; +import io.github.dsheirer.module.decode.ip.cellocator.MCGPMessageFactory; import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header; import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; import io.github.dsheirer.module.decode.ip.udp.UDPPacket; @@ -91,6 +93,13 @@ public class PacketMessageFactory { switch(destinationPort) { + case 231: + //Cellocator + if(MCGPHeader.isCellocatorMessage(binaryMessage, offset)) + { + return MCGPMessageFactory.create(binaryMessage, offset); + } + break; case 4001: //Location Service break; case 4004: //XCMP Service diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/UnknownCompressedHeaderPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownCompressedHeaderPacket.java index 37100ed6..b7c07b42 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/UnknownCompressedHeaderPacket.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownCompressedHeaderPacket.java @@ -21,7 +21,10 @@ package io.github.dsheirer.module.decode.ip; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketHeader; +import java.util.Collections; +import java.util.List; public class UnknownCompressedHeaderPacket implements IPacket { @@ -87,4 +90,10 @@ public class UnknownCompressedHeaderPacket implements IPacket { return false; } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/UnknownPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownPacket.java index e154583e..dd257118 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/UnknownPacket.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownPacket.java @@ -21,6 +21,9 @@ package io.github.dsheirer.module.decode.ip; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import java.util.Collections; +import java.util.List; public class UnknownPacket implements IPacket { @@ -78,4 +81,10 @@ public class UnknownPacket implements IPacket { return false; } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPacket.java index 5f0d5bfa..00f945e6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPacket.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPacket.java @@ -21,8 +21,11 @@ package io.github.dsheirer.module.decode.ip.ars; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.ip.IPacket; import io.github.dsheirer.module.decode.ip.Packet; +import java.util.Collections; +import java.util.List; public class ARSPacket extends Packet { @@ -64,4 +67,10 @@ public class ARSPacket extends Packet //There are no payloads for ARS packets ... the header parses all of the information. return null; } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/AcknowledgeMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/AcknowledgeMessage.java new file mode 100644 index 00000000..ee48d73b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/AcknowledgeMessage.java @@ -0,0 +1,141 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * Controller (Inbound) Acknowledge Packet + */ +public class AcknowledgeMessage extends MCGPPacket +{ + //This field is byte reversed (ie big endian) + private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMAND_NUMERATOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39}; + //This field is (possibly) byte reversed (ie big endian) + private static final int[] AUTHENTICATION_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61, + 62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] ACTION_CODE = new int[]{72, 73, 74, 75, 76, 77, 78, 79}; + //This field is byte reversed (ie big endian) + private static final int[] MAIN_ACKNOWLEDGE_NUMBER = new int[]{88, 89, 90, 91, 92, 93, 94, 95, 80, 81, 82, 83, 84, + 85, 86, 87}; + //This field is byte reversed (ie big endian) + private static final int[] SECONDARY_ACKNOWLEDGE_NUMBER = new int[]{104, 105, 106, 107, 108, 109, 110, 111, 96, 97, + 98, 99, 100, 101, 102, 103}; + private static final int[] RESERVED = new int[]{112, 113, 114, 115, 116, 117, 118, 119}; + private static final int[] YEAR = new int[]{121, 122, 123, 124, 125, 126, 127}; + private static final int[] MONTH = new int[]{133, 134, 135, 120}; + private static final int[] DAY = new int[]{128, 129, 130, 131, 132}; + private static final int[] RESERVED_2 = new int[]{152, 153, 154, 155, 156, 157, 158}; //Always 0x40 + private static final int[] HOUR = new int[]{139, 140, 141, 142, 143}; + private static final int[] MINUTE = new int[]{149, 150, 151, 136, 137, 138}; + private static final int[] SECOND = new int[]{159, 144, 145, 146, 147, 148}; + + private CellocatorRadioIdentifier mTargetRadioId; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public AcknowledgeMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + public CellocatorRadioIdentifier getRadioId() + { + if(mTargetRadioId == null) + { + mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset())); + } + + return mTargetRadioId; + } + + public MCGPMessageType getAcknowledgedCommand() + { + int command = getMessage().getInt(COMMAND_NUMERATOR, getOffset()); + return MCGPMessageType.getOutboundMessageType(command); + } + + public String getAuthenticationCode() + { + return Integer.toHexString(getMessage().getInt(AUTHENTICATION_CODE, getOffset())).toUpperCase(); + } + + /** + * Always set to zero for an acknowledge message + */ + public int getActionCode() + { + return getMessage().getInt(ACTION_CODE, getOffset()); + } + + public int getMainAcknowledgeNumber() + { + return getMessage().getInt(MAIN_ACKNOWLEDGE_NUMBER, getOffset()); + } + + /** + * Currently not used and set to zero. + */ + public int getSecondaryAcknowledgeNumber() + { + return getMessage().getInt(SECONDARY_ACKNOWLEDGE_NUMBER, getOffset()); + } + + public long getTimestamp() + { + Calendar calendar = new GregorianCalendar(); + calendar.clear(); + + int year = 2000 + getMessage().getInt(YEAR, getOffset()); + int month = getMessage().getInt(MONTH, getOffset()) - 1; //Calendar month is zero based + int day = getMessage().getInt(DAY, getOffset()); + int hour = getMessage().getInt(HOUR, getOffset()); + int minute = getMessage().getInt(MINUTE, getOffset()); + int second = getMessage().getInt(SECOND, getOffset()); + + calendar.set(year, month, day, hour, minute, second); + + return calendar.getTimeInMillis(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR RADIO:"); + sb.append(getRadioId()); + sb.append(" ACKNOWLEDGE:").append(getAcknowledgedCommand()); + sb.append(" MESSAGE NUMBER:").append(getMainAcknowledgeNumber()); + sb.append(" TIME:").append(new Date(getTimestamp())); + sb.append(" AUTH:").append(getAuthenticationCode()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CellocatorRadioIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CellocatorRadioIdentifier.java new file mode 100644 index 00000000..80629123 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CellocatorRadioIdentifier.java @@ -0,0 +1,68 @@ +/* + * + * * ****************************************************************************** + * * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Cellocator Radio Identifier + */ +public class CellocatorRadioIdentifier extends RadioIdentifier +{ + public CellocatorRadioIdentifier(Integer value, Role role) + { + super(value, role); + } + + @Override + public Protocol getProtocol() + { + return Protocol.CELLOCATOR; + } + + /** + * Creates a TO radio identifier + */ + public static CellocatorRadioIdentifier createTo(int radioId) + { + return new CellocatorRadioIdentifier(radioId, Role.TO); + } + + /** + * Creates a FROM radio identifier + */ + public static CellocatorRadioIdentifier createFrom(int radioId) + { + return new CellocatorRadioIdentifier(radioId, Role.FROM); + } + + /** + * Creates an ANY radio identifier + */ + public static CellocatorRadioIdentifier createAny(int radioId) + { + return new CellocatorRadioIdentifier(radioId, Role.ANY); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/Command.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/Command.java new file mode 100644 index 00000000..acad4099 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/Command.java @@ -0,0 +1,109 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import java.util.Map; +import java.util.TreeMap; + +/** + * Cellocator - Commands Enumeration + */ +public enum Command +{ + IMMEDIATE_STATUS_REQUEST(0x0, "IMMEDIATE STATUS AND LOCATION"), + UNIT_STATUS_CHANGE(0x2, "CHANGE UNIT STATUS"), + OUTPUT_STATE_CHANGE(0x3, "CHANGE OUTPUT STATE"), + DISABLE_ACTIVE_TRANSMISSIONS(0x4, "DISABLE ACTIVE TRANSMISSIONS"), + TRACKING_CONTROL(0x5, "TRACKING CONTROL"), + ALARM_CADENCE_CONTROL(0x6, "ALARM CADENCE CONTROL"), + COMMENCE_GRADUAL_ENGINE_STOP(0x7, "COMMENCE GRADUAL ENGINE STOP"), + INITIATE_CSD_SESSION(0xC, "INITIATE CSD SESSION"), + ERASE_TRACKING_LOG(0xD, "ERASE TRACKING LOG"), + RESET_GPS_RECEIVER(0xE, "RESET GPS RECEIVER"), + SEQUENCE_DETECTION_LEARN(0xF, "SEQUENCE DETECTION LEARN"), + FORCE_GPS_ENERGIZING(0x10, "FORCE GPS ACTIVATION"), + CONNECT_TO_SERVER(0x12, "CONNECT TO SERVER"), + MANUFACTURER_RESERVED(0x13, "MANUFACTURER RESERVED"), + CALIBRATE_FREQUENCY_COUNTERS(0x14, "CALIBRATE FREQUENCY COUNTERS"), + CONTROL_COM_PORT_TRANSPARENT_MODE(0x15, "CONTROL COM PORT TRANSPARENT MODE"), + QUERY_CONNECTED_TRAILER_ID(0x16, "QUERY CONNECTED TRAILER ID"), + CAMERA_SUPPORT(0x18, "CAMERA SUPPORT"), + NANO_WAKEUP_AND_STATUS_REQUEST(0x19, "NANO WAKEUP AND STATUS REQUEST"), + ENABLE_MODEM_FOTA_SESSION(0x1A, "ENABLE MODEM FOTA SESSION"), + PHSN_CONTROL(0x1B, "PHSN CONTROL"), + CALL_CONTROL(0x1C, "CALL SHARING 2 CONTROL"), + CELLO_TRACK_T(0x1D, "CELLO TRACK T"), + CALIBRATE_CAN_GPS_SPEED(0x1F, "CALIBRATE CAN GPS SPEED"), + + UNKNOWN(-1, "UNKNOWN COMMAND"); + + private int mValue; + private String mLabel; + + /** + * Constructs an instance + * @param value of the command + * @param label for pretty printing + */ + Command(int value, String label) + { + mValue = value; + mLabel = label; + } + + /** + * Value of the command + */ + public int getValue() + { + return mValue; + } + + private static Map LOOKUP_MAP = new TreeMap<>(); + + static + { + for(Command command: Command.values()) + { + LOOKUP_MAP.put(command.getValue(), command); + } + } + + /** + * Lookup a command from its value + * @param value of command to lookup + * @return command or UNKNOWN + */ + public static Command fromValue(int value) + { + if(LOOKUP_MAP.containsKey(value)) + { + return LOOKUP_MAP.get(value); + } + + return UNKNOWN; + } + + @Override + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CommunicationControl.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CommunicationControl.java new file mode 100644 index 00000000..48289953 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/CommunicationControl.java @@ -0,0 +1,111 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +/** + * Communication Control Field parser + */ +public class CommunicationControl +{ + public enum Initiative {ACTIVE, RESPONSE}; + public enum SpeedType {MOMENTARY, MAX}; + + private static final int MASK_CAN_ORIGINATED_ODOMETER = 0x8000; + private static final int MASK_CAN_ORIGINATED_SPEED = 0x4000; + private static final int MASK_MULTI_PURPOSE_FIELD_CONTENT = 0x3000; + private static final int MASK_CR200_CR300_MESSAGE_SOURCE = 0x800; + private static final int MASK_GARMIN_CONNECTED = 0x400; + private static final int MASK_GARMIN_ENABLED = 0x200; + private static final int MASK_CR200_CR300_MESSAGE_INITIATIVE = 0x100; + private static final int MASK_CR200_CR300_GSM_HIBERNATION_INDICATOR = 0x80; + private static final int MASK_MOMENTARY_OR_MAX_SPEED_VALUE = 0x40; + private static final int MASK_BUSINESS_OR_PRIVATE_MODE = 0x20; + private static final int MASK_FIRMWARE_SUB_VERSION = 0x1F; + + private int mValue; + + /** + * Constructs an instance + * + * @param value of the 16-bit field + */ + public CommunicationControl(int value) + { + mValue = value; + } + + /** + * Indicates if the odometer value originated from the vehicle CAN bus + */ + public boolean isCANOriginatedOdometer() + { + return isSet(MASK_CAN_ORIGINATED_ODOMETER); + } + + /** + * Indicates if the speed value originated from the vehicle CAN bus + */ + public boolean isCANOriginatedSpeed() + { + return isSet(MASK_CAN_ORIGINATED_SPEED); + } + + /** + * Indicates if the Garmin GPS is connected to the unit + */ + public boolean isGarminConnected() + { + return isSet(MASK_GARMIN_CONNECTED); + } + + /** + * Indicates if the Garmin GPS is enabled + */ + public boolean isGarminEnabled() + { + return isSet(MASK_GARMIN_ENABLED); + } + + /** + * Indicates if the message was initiated by the unit or if the message is a response to a request. + */ + public Initiative getMessageSource() + { + return isSet(MASK_CR200_CR300_MESSAGE_INITIATIVE) ? Initiative.RESPONSE : Initiative.ACTIVE; + } + + /** + * Indicates the context of the speed value contained in the message, momentary or max recorded from last event. + */ + public SpeedType getSpeedType() + { + return isSet(MASK_MOMENTARY_OR_MAX_SPEED_VALUE) ? SpeedType.MAX : SpeedType.MOMENTARY; + } + + /** + * Indicates if the masked value is set for this field + * @param mask to check + * @return true if all set bits of the mask value are also set in the field bits + */ + private boolean isSet(int mask) + { + return (mValue & mask) == mask; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ForwardedDataMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ForwardedDataMessage.java new file mode 100644 index 00000000..3e332eaf --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ForwardedDataMessage.java @@ -0,0 +1,135 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; + +/** + * Unit (Outbound) data being forwarded over the wireless interface to a central application + */ +public class ForwardedDataMessage extends MCGPPacket +{ + //This field is byte reversed (ie big endian) + private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMAND_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] FORWARDED_MESSAGE_NUMBER = new int[]{88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] FRAGMENT_CONTROL = new int[]{96, 97, 98, 99, 100, 101, 102, 103}; + private static final int[] PAYLOAD_LENGTH = new int[]{112, 113, 114, 115, 116, 117, 118, 119, 104, 105, 106, 107, + 108, 109, 110, 111}; + private static final int PAYLOAD_START = 120; + + private CellocatorRadioIdentifier mTargetRadioId; + private FragmentControl mFragmentControl; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public ForwardedDataMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + /** + * Target radio ID to receive the forwarded data + */ + public CellocatorRadioIdentifier getRadioId() + { + if(mTargetRadioId == null) + { + mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset())); + } + + return mTargetRadioId; + } + + /** + * Message fragment control structure + */ + public FragmentControl getFragmentControl() + { + if(mFragmentControl == null) + { + mFragmentControl = new FragmentControl(getMessage().getInt(FRAGMENT_CONTROL, getOffset())); + } + + return mFragmentControl; + } + + /** + * This message number + */ + public int getMessageNumerator() + { + return getMessage().getInt(COMMAND_NUMERATOR, getOffset()); + } + + /** + * Message number for the forwarded message + */ + public int getForwardedMessageNumerator() + { + return getMessage().getInt(FORWARDED_MESSAGE_NUMBER, getOffset()); + } + + /** + * Length of the payload in bytes + */ + private int getPayloadLength() + { + return getMessage().getInt(PAYLOAD_LENGTH, getOffset()); + } + + /** + * Hexadecimal representation of the payload data. + */ + public String getPayloadData() + { + int payloadEnd = PAYLOAD_START + (getPayloadLength() * 8); + return getMessage().get(PAYLOAD_START, payloadEnd).toHexString(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR TO RADIO:"); + sb.append(getRadioId()); + sb.append(" MESSAGE NUMBER:").append(getMessageNumerator()); + + if(getHeader().getMessageType() == MCGPMessageType.OUTBOUND_FORWARDED_REALTIME_DATA) + { + sb.append(" FORWARDED SERIAL PORT DATA"); + } + else + { + sb.append(" FORWARDED LOGGED DATA"); + } + + sb.append(" MESSAGE NUMBER:").append(getForwardedMessageNumerator()); + sb.append(" ").append(getFragmentControl()); + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/FragmentControl.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/FragmentControl.java new file mode 100644 index 00000000..a1b383e6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/FragmentControl.java @@ -0,0 +1,70 @@ +package io.github.dsheirer.module.decode.ip.cellocator; + +/** + * Provides fragment information for messages that are too long and have to be fragmented. + */ +public class FragmentControl +{ + public enum Origination{UNIT,APPLICATION}; + private static final int BIT_MASK_ORIGINATION = 0x80; + private static final int BIT_MASK_LAST_FRAGMENT = 0x40; + private static final int BIT_MASK_FRAGMENT_INDEX = 0x3F; + private int mValue; + + /** + * Constructs an instance + * @param value of the fragment control field byte + */ + public FragmentControl(int value) + { + mValue = value; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOrigination().name()); + sb.append(" ORIGINATED FRAGMENT #").append(getFragmentIndex()); + + if(isLastFragment()) + { + sb.append(" FINAL"); + } + + return sb.toString(); + } + + /** + * Indicates the origination of the message fragment + * @return origination, unit or application + */ + public Origination getOrigination() + { + return isSet(BIT_MASK_ORIGINATION) ? Origination.UNIT : Origination.APPLICATION; + } + + /** + * Indicates if this is the last fragment + */ + public boolean isLastFragment() + { + return isSet(BIT_MASK_LAST_FRAGMENT); + } + + /** + * Fragment index for this fragment, 0-31 + */ + public int getFragmentIndex() + { + return (mValue & BIT_MASK_FRAGMENT_INDEX); + } + + /** + * Indicates if the bits that are set in the bit mask are also set in the value field + */ + private boolean isSet(int bitMask) + { + return (mValue & bitMask) == bitMask; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/GenericCommandMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/GenericCommandMessage.java new file mode 100644 index 00000000..e5b8c8bd --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/GenericCommandMessage.java @@ -0,0 +1,93 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; + +/** + * Controller (Inbound) Generic Command + */ +public class GenericCommandMessage extends MCGPPacket +{ + //This field is byte reversed (ie big endian) + private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMAND_NUMERATOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39}; + //This field is (possibly) byte reversed (ie big endian) + private static final int[] AUTHENTICATION_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61, + 62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] COMMAND_CODE = new int[]{72, 73, 74, 75, 76, 77, 78, 79}; + + private CellocatorRadioIdentifier mTargetRadioId; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public GenericCommandMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + public CellocatorRadioIdentifier getRadioId() + { + if(mTargetRadioId == null) + { + mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset())); + } + + return mTargetRadioId; + } + + public int getCommandNumerator() + { + return getMessage().getInt(COMMAND_NUMERATOR, getOffset()); + } + + public String getAuthenticationCode() + { + return Integer.toHexString(getMessage().getInt(AUTHENTICATION_CODE, getOffset())).toUpperCase(); + } + + public int getCommandCode() + { + return getMessage().getInt(COMMAND_CODE, getOffset()); + } + + public Command getCommand() + { + return Command.fromValue(getCommandCode()); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR RADIO:"); + sb.append(getRadioId()); + sb.append(" REQUEST:").append(getCommand()); + sb.append(" SEQUENCE:").append(getCommandNumerator()); + sb.append(" AUTH:").append(getAuthenticationCode()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/LocationStatusMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/LocationStatusMessage.java new file mode 100644 index 00000000..446c5407 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/LocationStatusMessage.java @@ -0,0 +1,237 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; +import org.apache.commons.math3.util.FastMath; + +/** + * Unit (Outbound) Status and Location + */ +public class LocationStatusMessage extends MCGPPacket +{ + private static final int[] SOURCE_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMUNICATION_CONTROL = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47}; + private static final int[] MESSAGE_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] HARDWARE_VERSION = new int[]{56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] SOFTWARE_VERSION = new int[]{64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] PROTOCOL_VERSION = new int[]{72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] UNIT_STATUS = new int[]{80, 81, 82, 83}; + private static final int[] CURRENT_GSM_OPERATOR = new int[]{84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 152, + 153, 154, 155, 156, 157, 158, 159}; + private static final int[] TRANSMISSION_REASON_SPECIFIC_DATA = new int[]{96, 97, 98, 99, 100, 101, 102, 103}; + private static final int[] TRANSMISSION_REASON = new int[]{104, 105, 106, 107, 108, 109, 110, 111}; + private static final int[] UNIT_MODE_OF_OPERATION = new int[]{112, 113, 114, 115, 116, 117, 118, 119}; //Byte 20 + private static final int[] UNIT_IO_STATUS = new int[]{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}; + private static final int[] ANALOG_INPUT_1 = new int[]{160, 161, 162, 163, 164, 165, 166, 167}; + private static final int[] ANALOG_INPUT_2 = new int[]{168, 169, 170, 171, 172, 173, 174, 175}; + private static final int[] ANALOG_INPUT_3 = new int[]{176, 177, 178, 179, 180, 181, 182, 183}; + private static final int[] ANALOG_INPUT_4 = new int[]{184, 185, 186, 187, 188, 189, 190, 191}; + private static final int[] MILEAGE_COUNTER = new int[]{192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215}; + private static final int[] MULTI_PURPOSE_FIELD = new int[]{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}; + private static final int[] LAST_GPS_FIX = new int[]{264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, + 277, 278, 279}; + private static final int[] LOCATION_STATUS = new int[]{280, 281, 282, 283, 284, 285, 286, 287}; + private static final int[] GPS_MODE_1 = new int[]{288, 289, 290, 291, 292, 293, 294, 295}; + private static final int[] GPS_MODE_2 = new int[]{296, 297, 298, 299, 300, 301, 302, 303}; + private static final int[] GPS_SATELLITES_USED = new int[]{304, 305, 306, 307, 308, 309, 310, 311}; + + //Original signed 32-bit big endian field rearranged in little endian byte format + private static final int[] LONGITUDE = new int[]{336, 337, 338, 339, 340, 341, 342, 343, 328, 329, 330, 331, 332, + 333, 334, 335, 320, 321, 322, 323, 324, 325, 326, 327, 312, 313, 314, 315, 316, 317, 318, 319}; + + //Original signed 32-bit big endian field rearranged in little endian byte format + private static final int[] LATITUDE = new int[]{368, 369, 370, 371, 372, 373, 374, 375, 360, 361, 362, 363, 364, + 365, 366, 367, 352, 353, 354, 355, 356, 357, 358, 359, 344, 345, 346, 347, 348, 349, 350, 351}; + + //Original signed 32-bit big endian field rearranged in little endian byte format + private static final int[] ALTITUDE = new int[]{400, 401, 402, 403, 404, 405, 406, 407, 392, 393, 394, 395, 396, + 397, 398, 399, 384, 385, 386, 387, 388, 389, 390, 391, 376, 377, 378, 379, 380, 381, 382, 383}; + + //Original signed 32-bit big endian field rearranged in little endian byte format + private static final int[] GROUND_SPEED = new int[]{432, 433, 434, 435, 436, 437, 438, 439, 424, 425, 426, 427, 428, + 429, 430, 431, 416, 417, 418, 419, 420, 421, 422, 423, 408, 409, 410, 411, 412, 413, 414, 415}; + + //Original signed 32-bit big endian field rearranged in little endian byte format + private static final int[] HEADING_TRUE = new int[]{448, 449, 450, 451, 452, 453, 454, 455, 440, 441, 442, 443, + 444, 445, 446, 447}; + + private static final int[] UTC_TIME_SECOND = new int[]{456, 457, 458, 459, 460, 461, 462, 463}; + private static final int[] UTC_TIME_MINUTE = new int[]{464, 465, 466, 467, 468, 469, 470, 471}; + private static final int[] UTC_TIME_HOUR = new int[]{472, 473, 474, 475, 476, 477, 478, 479}; + private static final int[] UTC_TIME_DAY = new int[]{480, 481, 482, 483, 484, 485, 486, 487}; + private static final int[] UTC_TIME_MONTH = new int[]{488, 489, 490, 491, 492, 493, 494, 495}; + private static final int[] UTC_TIME_YEAR = new int[]{496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, + 508, 509, 510, 511}; + + private CellocatorRadioIdentifier mSourceRadioId; + private CommunicationControl mCommunicationControl; + private Long mUTCTimestamp; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public LocationStatusMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + /** + * Source radio or unit ID + */ + public CellocatorRadioIdentifier getRadioId() + { + if(mSourceRadioId == null) + { + mSourceRadioId = CellocatorRadioIdentifier.createFrom(getMessage().getInt(SOURCE_UNIT_ID, getOffset())); + } + + return mSourceRadioId; + } + + /** + * Communication control bitmap field parser. + */ + public CommunicationControl getCommunicationControl() + { + if(mCommunicationControl == null) + { + mCommunicationControl = new CommunicationControl(getMessage().getInt(COMMUNICATION_CONTROL, getOffset())); + } + + return mCommunicationControl; + } + + /** + * Message number. This is a one-up sequence maintained by the source unit that is reset to zero after + * each power cycle and used to track the message through receipt. + */ + public int getMessageNumerator() + { + return getMessage().getInt(MESSAGE_NUMERATOR, getOffset()); + } + + /** + * Number of satellites used by the GPS for calculating the position/fix. + */ + public int getSatelliteCount() + { + return getMessage().getInt(GPS_SATELLITES_USED, getOffset()); + } + + /** + * Latitude in degrees decimal format for WGS-84 datum + */ + public double getLatitude() + { + int value = getMessage().getInt(LATITUDE, getOffset()); + double radians = (double)value / 1E8d; + return FastMath.toDegrees(radians); + } + + /** + * Longitude in degrees decimal format for WGS-84 datum + */ + public double getLongitude() + { + int value = getMessage().getInt(LONGITUDE, getOffset()); + double radians = (double)value / 1E8d; + return FastMath.toDegrees(radians); + } + + /** + * Altitude in meters + */ + public double getAltitude() + { + int centimeters = getMessage().getInt(ALTITUDE, getOffset()); + return (double)centimeters / 1E2d; + } + + /** + * Speed over ground in kph + */ + public double getSpeed() + { + int centimetersPerSecond = getMessage().getInt(GROUND_SPEED, getOffset()); + double kilometersPerSecond = (double)centimetersPerSecond / 1E5d; + return kilometersPerSecond * 3600; + } + + /** + * Heading relative to true North, 0-360 degrees. + */ + public double getHeading() + { + int headingThousandthsRadian = getMessage().getInt(HEADING_TRUE, getOffset()); + double radians = (double)headingThousandthsRadian / 1E3d; + return FastMath.toDegrees(radians); + } + + /** + * UTC timestamp for the location and status + * @return timestamp in milliseconds since epoch + */ + public long getUTCTimestamp() + { + if(mUTCTimestamp == null) + { + int year = getMessage().getInt(UTC_TIME_YEAR, getOffset()); + int month = getMessage().getInt(UTC_TIME_MONTH, getOffset()); + int day = getMessage().getInt(UTC_TIME_DAY, getOffset()); + int hour = getMessage().getInt(UTC_TIME_HOUR, getOffset()); + int minute = getMessage().getInt(UTC_TIME_MINUTE, getOffset()); + int second = getMessage().getInt(UTC_TIME_SECOND, getOffset()); + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + calendar.clear(); + calendar.set(year, month, day, hour, minute, second); + mUTCTimestamp = calendar.getTimeInMillis(); + } + + return mUTCTimestamp; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR RADIO:").append(getRadioId()); + sb.append(" GPS LOCATION: ").append(getLatitude()).append(" ").append(getLongitude()); + sb.append(" HEADING:").append(getHeading()); + sb.append(" SPEED:").append(getSpeed()).append("kph"); + sb.append(" AT:").append(new Date(getUTCTimestamp())); + sb.append(" MESSAGE #").append(getMessageNumerator()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPHeader.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPHeader.java new file mode 100644 index 00000000..5e1aa51d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPHeader.java @@ -0,0 +1,127 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.Header; + +/** + * Cellocator MCGP Header + */ +public class MCGPHeader extends Header +{ + private static final int[] M_CHARACTER = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] C_CHARACTER = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] G_CHARACTER = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] P_CHARACTER = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int M_VALUE = 0x4D; + private static final int C_VALUE = 0x43; + private static final int G_VALUE = 0x47; + private static final int P_VALUE = 0x50; + + private static final int[] MESSAGE_TYPE = {32, 33, 34, 35, 36, 37, 38, 39}; + public static final int HEADER_LENGTH = 40; + + private MCGPMessageType mMessageType; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public MCGPHeader(BinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Performs simple check of the first 32 bits of the message looking for the + * signature 'MCGP' header for cellocator messages. + * @param message to inspect + * @param offset to the cellocator payload + * @return true if the payload starts with MCGP + */ + public static boolean isCellocatorMessage(BinaryMessage message, int offset) + { + return message.getInt(M_CHARACTER, offset) == M_VALUE && + message.getInt(C_CHARACTER, offset) == C_VALUE && + message.getInt(G_CHARACTER, offset) == G_VALUE && + message.getInt(P_CHARACTER, offset) == P_VALUE; + } + + @Override + public int getLength() + { + return HEADER_LENGTH; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR ").append(getMessageType()); + + if(getMessageType() == MCGPMessageType.UNKNOWN) + { + sb.append(" PACKET:").append(getMessage().toHexString()); + } + + return sb.toString(); + } + + /** + * Indicates the type of message + */ + public MCGPMessageType getMessageType() + { + if(mMessageType == null) + { + mMessageType = getMessageType(getMessage(), getOffset()); + } + + return mMessageType; + } + + /** + * Sets/Overrides the message type + */ + void setMessageType(MCGPMessageType type) + { + mMessageType = type; + } + + /** + * Indicates the type of MCGP message contained in the message starting at the offset index. + */ + public static MCGPMessageType getMessageType(BinaryMessage message, int offset) + { + int payloadLengthBytes = (message.size() - offset) / 8; + return MCGPMessageType.fromValue(message.getInt(MESSAGE_TYPE, offset), payloadLengthBytes); + } + + /** + * Numeric value for the message type for this header + */ + public int getMessageTypeValue() + { + return getMessage().getInt(MESSAGE_TYPE, getOffset()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageFactory.java new file mode 100644 index 00000000..3176f193 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageFactory.java @@ -0,0 +1,77 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; + +/** + * Creates an MCGP packet and header for parsing the contents of the packet. + */ +public class MCGPMessageFactory +{ + /** + * Creates an MCGP packet message from the binary message at the specified binary message offset. + * @param message containing transmitted bits + * @param offset into the message for the start of the MCGP payload + * @return MCGP packet message parser + */ + public static MCGPPacket create(BinaryMessage message, int offset) + { + MCGPHeader header = new MCGPHeader(message, offset); + int payloadOffset = offset + MCGPHeader.HEADER_LENGTH; + + switch(header.getMessageType()) + { + case INBOUND_GENERIC_COMMAND: + return new GenericCommandMessage(header, message, payloadOffset); + case INBOUND_GENERAL_ACKNOWLEDGE: + return new AcknowledgeMessage(header, message, payloadOffset); + case INBOUND_PROGRAMMING_COMMAND: + return new ProgrammingCommandMessage(header, message, payloadOffset); + case OUTBOUND_LOCATION_STATUS: + return new LocationStatusMessage(header, message, payloadOffset); + case OUTBOUND_PROGRAMMING_STATUS: + return new ProgrammingStatusMessage(header, message, payloadOffset); + case OUTBOUND_FORWARDED_REALTIME_DATA: + case OUTBOUND_FORWARDED_LOGGED_DATA_FRAGMENT: + return new ForwardedDataMessage(header, message, payloadOffset); + + case INBOUND_MODULAR_REQUEST: + case OUTBOUND_MODULAR_RESPONSE: + //Both messages are variable length and use message type 9 so we have to inspect the total message + //length field for the response message which should be 0 for the request variant and non-zero for + //the response variant. + if(ModularResponseMessage.getTotalMessageLength(message, payloadOffset) > 0) + { + header.setMessageType(MCGPMessageType.OUTBOUND_MODULAR_RESPONSE); + return new ModularResponseMessage(header, message, payloadOffset); + } + else + { + return new ModularRequestMessage(header, message, payloadOffset); + } + + case UNKNOWN: + default: + return new UnknownMCGPMessage(header, message, offset); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageType.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageType.java new file mode 100644 index 00000000..c50dc14b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPMessageType.java @@ -0,0 +1,127 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import java.util.EnumSet; + +/** + * Cellocator MCGP Message Types enumeration with indicated message lengths. + * + * Note: message length of -1 indicates a variable length message. + */ +public enum MCGPMessageType +{ + INBOUND_GENERIC_COMMAND(0, 25 * 8, "COMMAND"), + INBOUND_PROGRAMMING_COMMAND(1, 34 * 8, "PROGRAMMING COMMAND"), + INBOUND_GENERAL_ACKNOWLEDGE(4, 28 * 8, "ACKNOWLEDGE"), + INBOUND_FORWARD_DATA_COMMAND(5, -1, "FORWARD DATA COMMAND"), + INBOUND_MODULAR_REQUEST(9, -1, "MODULAR MESSAGE REQUEST"), + + OUTBOUND_LOCATION_STATUS(0, 70 * 8, "LOCATION AND STATUS"), + OUTBOUND_PROGRAMMING_STATUS(3, 31 * 8, "CONFIGURATION"), + OUTBOUND_FORWARDED_LOGGED_DATA_FRAGMENT(7, -1, "FORWARDED LOGGED DATA"), + OUTBOUND_FORWARDED_REALTIME_DATA(8, -1, "FORWARDED SERIAL_PORT DATA"), + OUTBOUND_MODULAR_RESPONSE(9, -1, "MODULAR MESSAGE RESPONSE"), + OUTBOUND_FIRMWARE_UPDATE(10,-1, "FIRMWARE UPDATE"), + + UNKNOWN(-1, -1, "UNKNOWN"); + + private int mValue; + private int mLength; + private String mLabel; + + MCGPMessageType(int value, int length, String label) + { + mValue = value; + mLength = length; + mLabel = label; + } + + public static EnumSet INBOUND = EnumSet.range(INBOUND_GENERIC_COMMAND, INBOUND_MODULAR_REQUEST); + public static EnumSet OUTBOUND = EnumSet.range(OUTBOUND_LOCATION_STATUS, OUTBOUND_FIRMWARE_UPDATE); + + public int getValue() + { + return mValue; + } + + public int getLength() + { + return mLength; + } + + /** + * Utility method to lookup an entry from a value. + */ + public static MCGPMessageType fromValue(int value, int messageByteLength) + { + switch(value) + { + case 0: + if(messageByteLength == 25) + { + return INBOUND_GENERIC_COMMAND; + } + else if(messageByteLength == 70) + { + return OUTBOUND_LOCATION_STATUS; + } + break; + case 1: + return INBOUND_PROGRAMMING_COMMAND; + case 3: + return OUTBOUND_PROGRAMMING_STATUS; + case 4: + return INBOUND_GENERAL_ACKNOWLEDGE; + case 5: + return INBOUND_FORWARD_DATA_COMMAND; + case 7: + return OUTBOUND_FORWARDED_LOGGED_DATA_FRAGMENT; + case 8: + return OUTBOUND_FORWARDED_REALTIME_DATA; + case 9: + //Defer to the message factory to detect inbound vs outbound message type + return INBOUND_MODULAR_REQUEST; + case 10: + return OUTBOUND_FIRMWARE_UPDATE; + } + + return UNKNOWN; + } + + public static MCGPMessageType getOutboundMessageType(int value) + { + for(MCGPMessageType type: OUTBOUND) + { + if(type.getValue() == value) + { + return type; + } + } + + return UNKNOWN; + } + + @Override + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPPacket.java new file mode 100644 index 00000000..44b92a1a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/MCGPPacket.java @@ -0,0 +1,79 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ip.IPacket; +import io.github.dsheirer.module.decode.ip.Packet; +import java.util.Collections; +import java.util.List; + +/** + * Cellocator MCGP Packet + */ +public abstract class MCGPPacket extends Packet +{ + private MCGPHeader mHeader; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public MCGPPacket(MCGPHeader header, BinaryMessage message, int offset) + { + super(message, offset); + mHeader = header; + } + + @Override + public MCGPHeader getHeader() + { + return mHeader; + } + + /** + * Unit ID (to/from) for this packet + */ + protected abstract CellocatorRadioIdentifier getRadioId(); + + @Override + public IPacket getPayload() + { + return null; + } + + @Override + public List getIdentifiers() + { + CellocatorRadioIdentifier radioIdentifier = getRadioId(); + + if(radioIdentifier != null) + { + return Collections.singletonList(radioIdentifier); + } + + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularDataType.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularDataType.java new file mode 100644 index 00000000..8c317c6c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularDataType.java @@ -0,0 +1,44 @@ +package io.github.dsheirer.module.decode.ip.cellocator; + +/** + * Modular message data types + */ +public enum ModularDataType +{ + FIRMWARE_PLATFORM_MANIFEST(1), + CAN_DATA(2), + CAN_TRIGGER_DATA(3), + TIME_AND_LOCATION_DATA(4), + ACCELEROMETER_DATA(5), + PSP_ALARM_SYSTEM_DATA(6), + USAGE_COUNTER_DATA(7), + COMMAND_AUTHENTICATION_TABLE_DATA(8), + GSM_NEIGHBOR_LIST(9), + MAINTENANCE_SERVER_PLATFORM_MANIFEST(10), + UNKNOWN(0); + + private int mValue; + + ModularDataType(int value) + { + mValue = value; + } + + public int getValue() + { + return mValue; + } + + public static ModularDataType fromValue(int value) + { + for(ModularDataType type: ModularDataType.values()) + { + if(type.getValue() == value) + { + return type; + } + } + + return UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularRequestMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularRequestMessage.java new file mode 100644 index 00000000..c905d9b8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularRequestMessage.java @@ -0,0 +1,110 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; + +/** + * Inbound (from Controller) Modular Request Message + */ +public class ModularRequestMessage extends MCGPPacket +{ + //This field is byte reversed (ie big endian) + private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMAND_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] PACKET_CONTROL = new int[]{88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] TOTAL_MESSAGE_LENGTH = new int[]{96, 97, 98, 99, 100, 101, 102, 103}; + private static final int[] SUB_DATA_1_MESSAGE_TYPE = new int[]{104, 105, 106, 107, 108, 109, 110, 111}; + + private CellocatorRadioIdentifier mTargetRadioId; + private PacketControl mPacketControl; + + /** + * Constructs a parser for the message. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public ModularRequestMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + /** + * Static access to message total length (bytes) field. + * @param message containing modular request message + * @param offset to start of modular request message + * @return value from the total message length field + */ + public static int getTotalMessageLength(BinaryMessage message, int offset) + { + return message.getInt(TOTAL_MESSAGE_LENGTH, offset); + } + + /** + * Radio/Unit identifier, either source or target as specified in the PacketControl field. + */ + public CellocatorRadioIdentifier getRadioId() + { + if(mTargetRadioId == null) + { + int radioId = getMessage().getInt(TARGET_UNIT_ID, getOffset()); + mTargetRadioId = CellocatorRadioIdentifier.createTo(radioId); + } + + return mTargetRadioId; + } + + public PacketControl getPacketControl() + { + if(mPacketControl == null) + { + mPacketControl = new PacketControl(getMessage().getInt(PACKET_CONTROL, getOffset())); + } + + return mPacketControl; + } + + public int getCommandNumerator() + { + return getMessage().getInt(COMMAND_NUMERATOR, getOffset()); + } + + /** + * Modular data type for the first sub-chunk + */ + public ModularDataType getModularDataType() + { + return ModularDataType.fromValue(getMessage().getInt(SUB_DATA_1_MESSAGE_TYPE, getOffset())); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR RADIO:"); + sb.append(getRadioId()); + sb.append(" MODULAR REQUEST TYPE:").append(getModularDataType()); + sb.append(" MESSAGE NUMBER:").append(getCommandNumerator()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularResponseMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularResponseMessage.java new file mode 100644 index 00000000..d8491512 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ModularResponseMessage.java @@ -0,0 +1,139 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; + +/** + * Modular Message + */ +public class ModularResponseMessage extends MCGPPacket +{ + //This field is byte reversed (ie big endian) + private static final int[] UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMUNICATION_CONTROL = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47}; + private static final int[] MESSAGE_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] PACKET_CONTROL = new int[]{56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] TOTAL_MESSAGE_LENGTH = new int[]{64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] SUB_DATA_1_MESSAGE_TYPE = new int[]{72, 73, 74, 75, 76, 77, 78, 79}; + + private CellocatorRadioIdentifier mRadioId; + private CommunicationControl mCommunicationControl; + private PacketControl mPacketControl; + + /** + * Constructs a parser for the message. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public ModularResponseMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + /** + * Static access to message total length (bytes) field. + * @param message containing modular response message + * @param offset to start of modular response message + * @return value from the total message length field + */ + public static int getTotalMessageLength(BinaryMessage message, int offset) + { + return message.getInt(TOTAL_MESSAGE_LENGTH, offset); + } + + /** + * Radio/Unit identifier, either source or target as specified in the PacketControl field. + */ + public CellocatorRadioIdentifier getRadioId() + { + if(mRadioId == null) + { + int radioId = getMessage().getInt(UNIT_ID, getOffset()); + + if(getPacketControl().getDirection() == PacketControl.Direction.FROM_UNIT) + { + mRadioId = CellocatorRadioIdentifier.createFrom(radioId); + } + else + { + mRadioId = CellocatorRadioIdentifier.createTo(radioId); + } + } + + return mRadioId; + } + + public PacketControl getPacketControl() + { + if(mPacketControl == null) + { + mPacketControl = new PacketControl(getMessage().getInt(PACKET_CONTROL, getOffset())); + } + + return mPacketControl; + } + + public CommunicationControl getCommunicationControl() + { + if(mCommunicationControl == null) + { + mCommunicationControl = new CommunicationControl(getMessage().getInt(COMMUNICATION_CONTROL, getOffset())); + } + + return mCommunicationControl; + } + + public int getMessageNumerator() + { + return getMessage().getInt(MESSAGE_NUMERATOR, getOffset()); + } + + /** + * Modular data type for the first sub-chunk + */ + public ModularDataType getModularDataType() + { + return ModularDataType.fromValue(getMessage().getInt(SUB_DATA_1_MESSAGE_TYPE, getOffset())); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR RADIO:"); + if(getPacketControl().getDirection() == PacketControl.Direction.FROM_UNIT) + { + sb.append(" FROM:"); + } + else + { + sb.append(" TO:"); + } + sb.append(getRadioId()); + sb.append(" MODULAR TYPE:").append(getModularDataType()); + sb.append(" MESSAGE NUMBER:").append(getMessageNumerator()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/PacketControl.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/PacketControl.java new file mode 100644 index 00000000..ef5db32c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/PacketControl.java @@ -0,0 +1,60 @@ +package io.github.dsheirer.module.decode.ip.cellocator; + +/** + * Provides fragment information for messages that are too long and have to be fragmented. + */ +public class PacketControl +{ + public enum Direction {TO_UNIT,FROM_UNIT}; + private static final int BIT_MASK_DIRECTION = 0x80; + private static final int BIT_MASK_OUT_OF_SPACE = 0x40; + private int mValue; + + /** + * Constructs an instance + * @param value of the fragment control field byte + */ + public PacketControl(int value) + { + mValue = value; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(" MODULAR PACKET ").append(getDirection()); + + if(isOutOfSpace()) + { + sb.append(" (MESSAGE EXCEEDED SPACE)"); + } + + return sb.toString(); + } + + /** + * Indicates the origination of the message fragment + * @return origination, unit or application + */ + public Direction getDirection() + { + return isSet(BIT_MASK_DIRECTION) ? Direction.TO_UNIT : Direction.FROM_UNIT; + } + + /** + * Indicates if the message contents exceeded the available space in this message + */ + public boolean isOutOfSpace() + { + return isSet(BIT_MASK_OUT_OF_SPACE); + } + + /** + * Indicates if the bits that are set in the bit mask are also set in the value field + */ + private boolean isSet(int bitMask) + { + return (mValue & bitMask) == bitMask; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingCommandMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingCommandMessage.java new file mode 100644 index 00000000..5a34284d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingCommandMessage.java @@ -0,0 +1,104 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; +import org.apache.commons.lang3.StringUtils; + +/** + * Controller (Inbound) Programming Command + */ +public class ProgrammingCommandMessage extends MCGPPacket +{ + //This field is byte reversed (ie big endian) + private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMAND_NUMERATOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39}; + //This field is (possibly) byte reversed (ie big endian) + private static final int[] AUTHENTICATION_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61, + 62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_CODE = new int[]{72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BITMASK = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int DATA_BLOCK_START = 96; + private static final int DATA_BLOCK_END = 224; + + private CellocatorRadioIdentifier mTargetRadioId; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public ProgrammingCommandMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + public CellocatorRadioIdentifier getRadioId() + { + if(mTargetRadioId == null) + { + mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset())); + } + + return mTargetRadioId; + } + + public int getCommandNumerator() + { + return getMessage().getInt(COMMAND_NUMERATOR, getOffset()); + } + + public String getAuthenticationCode() + { + return Integer.toHexString(getMessage().getInt(AUTHENTICATION_CODE, getOffset())).toUpperCase(); + } + + public int getBlockCode() + { + return getMessage().getInt(BLOCK_CODE, getOffset()); + } + + public String getBitMask() + { + return StringUtils.leftPad(Integer.toHexString(getMessage().getInt(BITMASK, getOffset())).toUpperCase(), 4, "0"); + } + + public String getDataBlock() + { + return getMessage().getSubMessage(DATA_BLOCK_START + getOffset(), DATA_BLOCK_END + getOffset()).toHexString(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR RADIO:"); + sb.append(getRadioId()); + sb.append(" PROGRAM SEQUENCE NUMBER:").append(getCommandNumerator()); + sb.append(" AUTH:").append(getAuthenticationCode()); + sb.append(" BLOCK:").append(getBlockCode()); + sb.append(" BITMASK:").append(getBitMask()); + sb.append(" DATA:").append(getDataBlock()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingStatusMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingStatusMessage.java new file mode 100644 index 00000000..0b0a2162 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/ProgrammingStatusMessage.java @@ -0,0 +1,101 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; + +/** + * Unit (Outbound) Programming Status/Response + */ +public class ProgrammingStatusMessage extends MCGPPacket +{ + //This field is byte reversed (ie big endian) + private static final int[] SOURCE_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, + 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] COMMUNICATION_CONTROL = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47}; + private static final int[] MESSAGE_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SPARE = new int[]{56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] BLOCK_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71}; + private static final int DATA_BLOCK_START = 72; + private static final int DATA_BLOCK_END = DATA_BLOCK_START + 128; + + private CellocatorRadioIdentifier mSourceRadioId; + private CommunicationControl mCommunicationControl; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public ProgrammingStatusMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + public CellocatorRadioIdentifier getRadioId() + { + if(mSourceRadioId == null) + { + mSourceRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(SOURCE_UNIT_ID, getOffset())); + } + + return mSourceRadioId; + } + + public CommunicationControl getCommunicationControl() + { + if(mCommunicationControl == null) + { + mCommunicationControl = new CommunicationControl(getMessage().getInt(COMMUNICATION_CONTROL, getOffset())); + } + + return mCommunicationControl; + } + + public int getMessageNumerator() + { + return getMessage().getInt(MESSAGE_NUMERATOR, getOffset()); + } + + public int getBlockCode() + { + return getMessage().getInt(BLOCK_CODE, getOffset()); + } + + public String getDataBlock() + { + return getMessage().getSubMessage(DATA_BLOCK_START + getOffset(), DATA_BLOCK_END + getOffset()).toHexString(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR RADIO:"); + sb.append(getRadioId()); + sb.append(" PROGRAM MESSAGE NUMBER:").append(getMessageNumerator()); + sb.append(" BLOCK:").append(getBlockCode()); + sb.append(" DATA:").append(getDataBlock()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/UnknownMCGPMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/UnknownMCGPMessage.java new file mode 100644 index 00000000..5609387e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/cellocator/UnknownMCGPMessage.java @@ -0,0 +1,70 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 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.module.decode.ip.cellocator; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.IPacket; +import io.github.dsheirer.module.decode.ip.UnknownPacket; + +/** + * MCGP Packet - Unknown Message Type + */ +public class UnknownMCGPMessage extends MCGPPacket +{ + private IPacket mPayload; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param header for this message + * @param message containing the packet + * @param offset to the packet within the message + */ + public UnknownMCGPMessage(MCGPHeader header, BinaryMessage message, int offset) + { + super(header, message, offset); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CELLOCATOR UNKNOWN MESSAGE TYPE ["); + sb.append(getHeader().getMessageTypeValue()).append("] ").append(getPayload()); + return sb.toString(); + } + + @Override + public IPacket getPayload() + { + if(mPayload == null) + { + mPayload = new UnknownPacket(getMessage(), getOffset()); + } + + return mPayload; + } + + @Override + protected CellocatorRadioIdentifier getRadioId() + { + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Packet.java b/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Packet.java index 33eeb32e..c404d3c7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Packet.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Packet.java @@ -21,10 +21,13 @@ package io.github.dsheirer.module.decode.ip.ipv4; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.ip.IPacket; import io.github.dsheirer.module.decode.ip.Packet; import io.github.dsheirer.module.decode.ip.PacketMessageFactory; import io.github.dsheirer.module.decode.ip.UnknownPacket; +import java.util.ArrayList; +import java.util.List; /** * IPV4 packet @@ -33,6 +36,7 @@ public class IPV4Packet extends Packet { private IPV4Header mHeader; private IPacket mPayload; + private List mIdentifiers; /** * Constructs an IPV4 packet parser @@ -109,4 +113,23 @@ public class IPV4Packet extends Packet return mPayload; } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(hasPayload()) + { + mIdentifiers.addAll(getPayload().getIdentifiers()); + } + + mIdentifiers.add(getHeader().getToAddress()); + mIdentifiers.add(getHeader().getFromAddress()); + } + + return mIdentifiers; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPacket.java index 570e693d..81e0ca6c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPacket.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPacket.java @@ -21,10 +21,13 @@ package io.github.dsheirer.module.decode.ip.udp; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.ip.IPacket; import io.github.dsheirer.module.decode.ip.Packet; import io.github.dsheirer.module.decode.ip.PacketMessageFactory; import io.github.dsheirer.module.decode.ip.UnknownPacket; +import java.util.Collections; +import java.util.List; public class UDPPacket extends Packet { @@ -73,16 +76,30 @@ public class UDPPacket extends Packet @Override public IPacket getPayload() { - if(mPayload == null && getHeader().isValid()) + if(mPayload == null) { - mPayload = PacketMessageFactory.createUDPPayload(getHeader().getDestinationPort(), getMessage(), getOffset() + getHeader().getLength()); - } - else - { - //Set the offset to the message offset plus 64 bits for UDP header - mPayload = new UnknownPacket(getMessage(), getOffset() + 64); + if(getHeader().isValid()) + { + mPayload = PacketMessageFactory.createUDPPayload(getHeader().getDestinationPort(), getMessage(), getOffset() + getHeader().getLength()); + } + else + { + //Set the offset to the message offset plus 64 bits for UDP header + mPayload = new UnknownPacket(getMessage(), getOffset() + 64); + } } return mPayload; } + + @Override + public List getIdentifiers() + { + if(hasPayload()) + { + return getPayload().getIdentifiers(); + } + + return Collections.emptyList(); + } } 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 c5ab733c..e034554d 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 @@ -43,6 +43,7 @@ import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.ip.IPacket; import io.github.dsheirer.module.decode.ip.ars.ARSPacket; +import io.github.dsheirer.module.decode.ip.cellocator.MCGPPacket; import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; import io.github.dsheirer.module.decode.ip.udp.UDPPacket; import io.github.dsheirer.module.decode.p25.P25DecodeEvent; @@ -935,6 +936,10 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList ARSPacket arsPacket = (ARSPacket)udpPayload; MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + for(Identifier identifier: packet.getIdentifiers()) + { + ic.update(identifier); + } DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) .channel(getCurrentChannel()) @@ -945,9 +950,32 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList broadcast(packetEvent); } + else if(udpPayload instanceof MCGPPacket) + { + MCGPPacket mcgpPacket = (MCGPPacket)udpPayload; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + for(Identifier identifier: packet.getIdentifiers()) + { + ic.update(identifier); + } + + DecodeEvent cellocatorEvent = P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription("CELLOCATOR") + .identifiers(ic) + .details(mcgpPacket.toString() + " " + ipv4.toString()) + .build(); + + broadcast(cellocatorEvent); + } else { MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + for(Identifier identifier: packet.getIdentifiers()) + { + ic.update(identifier); + } DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) .channel(getCurrentChannel()) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java index 3f87b1f2..70269382 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java @@ -25,7 +25,6 @@ import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.alias.id.record.Record; -import io.github.dsheirer.alias.id.talkgroup.Talkgroup; import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; import io.github.dsheirer.bits.BitSetFullException; import io.github.dsheirer.bits.CorrectedBinaryMessage; @@ -34,7 +33,6 @@ import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.dsp.symbol.ISyncDetectListener; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.message.IMessageProvider; import io.github.dsheirer.message.Message; import io.github.dsheirer.message.MessageProviderModule; import io.github.dsheirer.message.StuffBitsMessage; @@ -42,23 +40,17 @@ import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule; -import io.github.dsheirer.module.decode.p25.audio.P25P2AudioModule; import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; import io.github.dsheirer.module.decode.p25.phase1.message.P25MessageFactory; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessageFactory; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessageFactory; -import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; -import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState; -import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageFramer; -import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageProcessor; -import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.record.AudioRecordingManager; import io.github.dsheirer.record.binary.BinaryReader; -import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; @@ -458,7 +450,7 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect public static void main(String[] args) { - Path directory = Paths.get("/home/denny/temp/Harris P25-2 Logs/bits"); + Path directory = Paths.get("/home/denny/Documents/TMR/APCO25/GPS/cnyicc_cellocator"); // Path directory = Paths.get("/media/denny/500G1EXT4/PBITRecordings"); UserPreferences userPreferences = new UserPreferences(); @@ -479,11 +471,13 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect MessageProviderModule messageProviderModule = new MessageProviderModule(); processingChain.addModule(messageProviderModule); + mLog.info("Processing Directory: " + directory.toString()); + try(OutputStream logOutput = Files.newOutputStream(directory.resolve("log.txt"))) { try { - DirectoryStream stream = Files.newDirectoryStream(directory, "*PHASE1_Hills*TRAFFIC.bits"); + DirectoryStream stream = Files.newDirectoryStream(directory, "*.bits"); stream.forEach(new Consumer() { @@ -520,7 +514,11 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect { mLog.error("Error", ioe); } - mLog.debug(message.toString()); + + if(message instanceof PacketMessage) + { + mLog.debug(message.toString()); + } } } }); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java index 4b4a4e10..8b86d21c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java @@ -264,11 +264,9 @@ public class PacketMessage extends P25Message mIdentifiers = new ArrayList<>(); mIdentifiers.add(getPDUSequence().getHeader().getLLID()); - if(getPacket() instanceof IPV4Packet) + if(getPacket() != null) { - IPV4Packet ipv4 = (IPV4Packet)getPacket(); - mIdentifiers.add(ipv4.getHeader().getFromAddress()); - mIdentifiers.add(ipv4.getHeader().getToAddress()); + mIdentifiers.addAll(getPacket().getIdentifiers()); } } diff --git a/src/main/java/io/github/dsheirer/protocol/Protocol.java b/src/main/java/io/github/dsheirer/protocol/Protocol.java index 769fb5c0..a30dfc42 100644 --- a/src/main/java/io/github/dsheirer/protocol/Protocol.java +++ b/src/main/java/io/github/dsheirer/protocol/Protocol.java @@ -30,6 +30,7 @@ public enum Protocol { APCO25("APCO-25", "APCO25PHASE1", 9600), APCO25_PHASE2("APCO-25 P2", "APCO25PHASE2", 12000), + CELLOCATOR("CELLOCATOR", "CELLOCATOR", 0), FLEETSYNC("Fleetsync", "FLEETSYNC", 1200), IPV4("IPV4", "IPV4", 0), LOJACK("LoJack", "LOJACK", 1200),