#1641 DMR Hytera RRS packet support.

This commit is contained in:
Dennis Sheirer
2023-08-27 07:44:43 -04:00
parent 8fb56a5342
commit 21fd61d507
11 changed files with 386 additions and 20 deletions

View File

@@ -80,6 +80,7 @@ import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionPar
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
@@ -325,7 +326,22 @@ public class DMRDecoderState extends TimeslotDecoderState
.build();
broadcast(smsEvent);
}
//Hytera Short Data - Radio Registration Service (RRS)
//Hytera Radio Registration Service (RRS)
else if(packet.getPacket() instanceof HyteraRrsPacket rrs)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
StringBuilder sb = new StringBuilder();
sb.append("HYTERA RRS REGISTER RADIO:");
sb.append(rrs.getDestination());
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
.identifiers(mic)
.timeslot(getTimeslot())
.details(sb.toString())
.build();
broadcast(shortDataEvent);
}
//Hytera Short Data
else if(packet.getPacket() instanceof HyteraShortDataPacket hsdp)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
@@ -343,7 +359,6 @@ public class DMRDecoderState extends TimeslotDecoderState
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
.identifiers(mic)
.timeslot(getTimeslot())

View File

@@ -34,6 +34,7 @@ import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.ip.DefinedShortDataPacket;
import io.github.dsheirer.module.decode.ip.UnknownPacket;
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
@@ -243,8 +244,19 @@ public class PacketSequenceMessageFactory
*/
public static IMessage createDefinedShortData(PacketSequence packetSequence, CorrectedBinaryMessage packet)
{
return new DMRPacketMessage(packetSequence, new DefinedShortDataPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
//Test to see if this is Hytera Radio Registration Service (RRS)
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
if(hyteraTokenHeader.isRRSMessage())
{
return new DMRPacketMessage(packetSequence, new HyteraRrsPacket(hyteraTokenHeader), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
else
{
return new DMRPacketMessage(packetSequence, new DefinedShortDataPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
}
/**

View File

@@ -0,0 +1,130 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ip.hytera.rrs;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.ip.IPacket;
import io.github.dsheirer.module.decode.ip.hytera.sds.DestinationId;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraToken;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenType;
import io.github.dsheirer.module.decode.p25.identifier.ipv4.DMRIpAddress;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera SDS Long Data Message with SMS encoded payload
*/
public class HyteraRrsPacket implements IPacket
{
private HyteraTokenHeader mHeader;
private Identifier mDestination;
private List<Identifier> mIdentifiers;
/**
* Constructs a parser for a header contained within a binary message starting at the offset.
*
* @param header to the packet within the message
*/
public HyteraRrsPacket(HyteraTokenHeader header)
{
mHeader = header;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("HYTERA RRS REGISTER");
if(hasDestination())
{
sb.append(" RADIO:").append(getDestination());
}
return sb.toString();
}
@Override
public HyteraTokenHeader getHeader()
{
return mHeader;
}
/**
* Destination radio identifier for the SMS message
*/
public Identifier getDestination()
{
if(mDestination == null)
{
HyteraToken destinationToken = getHeader().getTokenByType(HyteraTokenType.ID_DESTINATION);
if(destinationToken instanceof DestinationId destinationId)
{
if(((DestinationId) destinationToken).isIp())
{
mDestination = DMRIpAddress.createFrom(((DestinationId) destinationToken).getId());
}
else
{
mDestination = DMRRadio.createTo(destinationId.getId());
}
}
}
return mDestination;
}
/**
* Indicates if the SMS message has a destination identifier
*/
public boolean hasDestination()
{
return getDestination() != null;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasDestination())
{
mIdentifiers.add(getDestination());
}
}
return mIdentifiers;
}
@Override
public IPacket getPayload()
{
return null;
}
@Override
public boolean hasPayload()
{
return false;
}
}

View File

@@ -20,6 +20,7 @@
package io.github.dsheirer.module.decode.ip.hytera.sds;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.ipv4.IPV4Address;
/**
* Destination Identifier
@@ -45,10 +46,19 @@ public class DestinationId extends HyteraToken
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("DESTINATION:").append(getId());
sb.append("DESTINATION:").append(getIdFormatted());
return sb.toString();
}
/**
* Indicates if this is an IP address (true) or a Radio ID (false).
* @return true if IP
*/
public boolean isIp()
{
return getId() > 0x00FFFFFF;
}
/**
* Length of the content field in bytes.
* @return content field length.
@@ -69,6 +79,18 @@ public class DestinationId extends HyteraToken
return mMessage.getInt(ID);
}
public String getIdFormatted()
{
if(isIp())
{
return new IPV4Address(getId()).toString() + " [" + (getId() & 0xFFFFFF) + "]";
}
else
{
return String.valueOf(getId());
}
}
@Override
HyteraTokenType getTokenType()
{

View File

@@ -37,11 +37,13 @@ public class HyteraTokenFactory
return switch(token)
{
case ID_DESTINATION -> new DestinationId(message);
case ENCODING -> new Encoding(message);
case ID_MESSAGE -> new MessageId(message);
case PAYLOAD -> new Payload(message);
case MESSAGE_HEADER -> new MessageHeader(message);
case ID_SOURCE -> new SourceId(message);
case ENCODING -> new Encoding(message);
case PAYLOAD -> new Payload(message);
case RRS_UNKNOWN -> new RrsUnknown(message);
case TEXT_MESSAGE_SERVICE_HEADER -> new TmsMessageHeader(message);
case RADIO_REGISTRATION_SERVICE_HEADER -> new RrsMessageHeader(message);
default -> new Unknown(message);
};
}

View File

@@ -72,7 +72,15 @@ public class HyteraTokenHeader implements IHeader
*/
public boolean isSMSMessage()
{
return hasTokens(HyteraTokenType.MESSAGE_HEADER, HyteraTokenType.ENCODING, HyteraTokenType.PAYLOAD);
return hasTokens(HyteraTokenType.TEXT_MESSAGE_SERVICE_HEADER, HyteraTokenType.ENCODING, HyteraTokenType.PAYLOAD);
}
/**
* Indicates if this header has RRS service message.
*/
public boolean isRRSMessage()
{
return hasTokens(HyteraTokenType.RADIO_REGISTRATION_SERVICE_HEADER, HyteraTokenType.ID_DESTINATION);
}
/**
@@ -153,7 +161,7 @@ public class HyteraTokenHeader implements IHeader
length += contentLength * 8; //Actual content
}
HyteraToken field = HyteraTokenFactory.getField(token, mMessage.getSubMessage(offset, offset + length + 1));
HyteraToken field = HyteraTokenFactory.getField(token, mMessage.getSubMessage(offset, offset + length));
offset += length;
mParsedTokens.add(field);
}

View File

@@ -29,13 +29,15 @@ package io.github.dsheirer.module.decode.ip.hytera.sds;
*/
public enum HyteraTokenType
{
//Note: this may be the message type, but is definitely followed by 4-byte total length field. Need more examples
MESSAGE_HEADER(0x0201, 4), //First 2 bytes are total message length, 2nd 2 bytes = unknown
RADIO_REGISTRATION_SERVICE_HEADER(0x0200, 1), //Total message length
TEXT_MESSAGE_SERVICE_HEADER(0x0201, 4), //First 2 bytes are total message length, 2nd 2 bytes = unknown
ID_MESSAGE(0x0001, -1),
ID_SOURCE(0x0002, -1),
ID_DESTINATION(0x0003, -1),
ENCODING(0x0101, -1),
PAYLOAD(0x0102, -1),
RRS_UNKNOWN(0x0703, 0),
UNKNOWN(-1, -1);
private int mOpcode;

View File

@@ -0,0 +1,66 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ip.hytera.sds;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Radio Registration Service (RRS) Message Header
*/
public class RrsMessageHeader extends HyteraToken
{
private static int[] MESSAGE_LENGTH_BCD_TENS = new int[]{16, 17, 18, 19};
private static int[] MESSAGE_LENGTH_BCD_ONES = new int[]{20, 21, 22, 23};
/**
* Constructs an instance
*
* @param message containing both the token, run-length, and content
*/
public RrsMessageHeader(CorrectedBinaryMessage message)
{
super(message);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("RRS HEADER LENGTH:").append(getMessageLength());
return sb.toString();
}
@Override
HyteraTokenType getTokenType()
{
return HyteraTokenType.RADIO_REGISTRATION_SERVICE_HEADER;
}
/**
* Overall message length in bytes
* @return message length.
*/
public int getMessageLength()
{
int length = mMessage.getInt(MESSAGE_LENGTH_BCD_TENS) * 10;
length += mMessage.getInt(MESSAGE_LENGTH_BCD_ONES);
return length;
}
}

View File

@@ -0,0 +1,50 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ip.hytera.sds;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Unknown RRS field parsed from a Hytera Radio Registration Service (RRS) message
*/
public class RrsUnknown extends HyteraToken
{
/**
* Constructs an instance
*
* @param message containing both the token, run-length, and content
*/
public RrsUnknown(CorrectedBinaryMessage message)
{
super(message);
}
@Override
public String toString()
{
return "RRS UNKNOWN TOKEN";
}
@Override
HyteraTokenType getTokenType()
{
return HyteraTokenType.RRS_UNKNOWN;
}
}

View File

@@ -22,16 +22,19 @@ package io.github.dsheirer.module.decode.ip.hytera.sds;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Message Header
*
* Text Message Service (TMS) Message Header
* <p>
* Initial token that specifies the overall message length and some other unidentified bit fields.
*
* <p>
* Note: I've only confirmed this is the start token for long SMS messages, but it may also be the start token for
* other packet data messages like GPS or any other packet overlay application.
*/
public class MessageHeader extends HyteraToken
public class TmsMessageHeader extends HyteraToken
{
private static int[] MESSAGE_LENGTH = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
private static int[] MESSAGE_LENGTH_BCD_THOUSANDS = new int[]{16, 17, 18, 19};
private static int[] MESSAGE_LENGTH_BCD_HUNDREDS = new int[]{20, 21, 22, 23};
private static int[] MESSAGE_LENGTH_BCD_TENS = new int[]{24, 25, 26, 27};
private static int[] MESSAGE_LENGTH_BCD_ONES = new int[]{28, 29, 30, 31};
private static int[] MESSAGE_FLAGS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
/**
@@ -39,7 +42,7 @@ public class MessageHeader extends HyteraToken
*
* @param message containing both the token, run-length, and content
*/
public MessageHeader(CorrectedBinaryMessage message)
public TmsMessageHeader(CorrectedBinaryMessage message)
{
super(message);
}
@@ -56,7 +59,7 @@ public class MessageHeader extends HyteraToken
@Override
HyteraTokenType getTokenType()
{
return HyteraTokenType.MESSAGE_HEADER;
return HyteraTokenType.TEXT_MESSAGE_SERVICE_HEADER;
}
/**
@@ -65,6 +68,7 @@ public class MessageHeader extends HyteraToken
* - Priority/Flash (boolean)
* - Encryption Type (enum of at least 4 values, ie 2+ bits)
* - Emergency (boolean)
*
* @return
*/
public String getFlags()
@@ -78,6 +82,10 @@ public class MessageHeader extends HyteraToken
*/
public int getMessageLength()
{
return mMessage.getInt(MESSAGE_LENGTH);
int length = mMessage.getInt(MESSAGE_LENGTH_BCD_THOUSANDS) * 1000;
length += mMessage.getInt(MESSAGE_LENGTH_BCD_HUNDREDS) * 100;
length += mMessage.getInt(MESSAGE_LENGTH_BCD_TENS) * 10;
length += mMessage.getInt(MESSAGE_LENGTH_BCD_ONES);
return length;
}
}

View File

@@ -0,0 +1,51 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.identifier.ipv4;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.ipv4.IPV4Address;
import io.github.dsheirer.identifier.ipv4.IPV4Identifier;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR IPV4 Address
*/
public class DMRIpAddress extends IPV4Identifier
{
public DMRIpAddress(IPV4Address ipAddress, Role role)
{
super(ipAddress, role);
}
public Protocol getProtocol()
{
return Protocol.DMR;
}
public static DMRIpAddress createTo(int value)
{
return new DMRIpAddress(new IPV4Address(value), Role.TO);
}
public static DMRIpAddress createFrom(int value)
{
return new DMRIpAddress(new IPV4Address(value), Role.FROM);
}
}