diff --git a/.idea/misc.xml b/.idea/misc.xml index 00f52ed7b..769598250 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/identifier/alias/P25TalkerAliasIdentifier.java b/src/main/java/io/github/dsheirer/identifier/alias/P25TalkerAliasIdentifier.java new file mode 100644 index 000000000..7ebb71728 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/alias/P25TalkerAliasIdentifier.java @@ -0,0 +1,54 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.identifier.alias; + +import io.github.dsheirer.protocol.Protocol; + +/** + * Talker alias value provided by the network for the current talker (ie FROM). + */ +public class P25TalkerAliasIdentifier extends TalkerAliasIdentifier +{ + /** + * Constructs an instance. + * @param value of the talker alias + */ + public P25TalkerAliasIdentifier(String value) + { + super(value); + } + + @Override + public boolean isValid() + { + return getValue() != null && !getValue().isEmpty(); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + public static P25TalkerAliasIdentifier create(String value) + { + return new P25TalkerAliasIdentifier(value); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/identifier/P25Location.java b/src/main/java/io/github/dsheirer/module/decode/dmr/identifier/P25Location.java new file mode 100644 index 000000000..2a54f1800 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/identifier/P25Location.java @@ -0,0 +1,60 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.identifier; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.location.LocationIdentifier; +import io.github.dsheirer.identifier.location.Point; +import io.github.dsheirer.protocol.Protocol; + +/** + * P25 location identifier for user radio reported GPS positions. + */ +public class P25Location extends LocationIdentifier +{ + /** + * Constructs an instance + * + * @param value location + * @param role to or from + */ + public P25Location(Point value, Role role) + { + super(value, role); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Utility method to create a new DMR location + * @param latitude of the position + * @param longitude of the position + * @return constructed DMR location identifier + */ + public static P25Location createFrom(double latitude, double longitude) + { + Point point = new Point(latitude, longitude); + return new P25Location(point, Role.FROM); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index f35a4ea70..d1b88213a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -41,6 +41,7 @@ import io.github.dsheirer.module.decode.DecoderType; 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.p25.P25DecodeEvent; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; @@ -87,8 +88,12 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisGpsLocation; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisTalkerAlias; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; +import io.github.dsheirer.protocol.Protocol; +import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -392,6 +397,15 @@ private void processMacMessage(MacMessage message) case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED: broadcast(message, mac, getCurrentChannel(), DecodeEventType.COMMAND, "UNIT REGISTRATION"); break; + case PHASE1_128_L3HARRIS_GPS_LOCATION: + processL3HarrisGps(message, mac); + break; + case PHASE1_168_L3HARRIS_TALKER_ALIAS: + if(mac instanceof L3HarrisTalkerAlias talkerAlias) + { + getIdentifierCollection().update(talkerAlias.getAlias()); + } + break; case PHASE1_176_L3HARRIS_GROUP_REGROUP: if(mac instanceof L3HarrisRegroupCommand regroup) { @@ -587,6 +601,25 @@ private void processIPMWP(MacMessage message, MacStructure mac) { } } + /** + * Broadcasts the L3Harris gps position report as a mappable/plottable event. + */ + private void processL3HarrisGps(MacMessage message, MacStructure structure) + { + if(structure instanceof L3HarrisGpsLocation gps) + { + MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(gps); + + broadcast(PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, message.getTimestamp()) + .protocol(Protocol.APCO25) + .location(gps.getGeoPosition()) + .channel(getCurrentChannel()) + .details(gps.getLocation().toString() + " " + new Date(gps.getTimestampMs())) + .identifiers(collection) + .build()); + } + } + private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor currentChannel, DecodeEventType eventType, String details) { MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(mac); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java index bb19f9d52..9f1a54177 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java @@ -85,6 +85,7 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownStructure; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownVendorMessage; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisTalkerAlias; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; @@ -290,6 +291,8 @@ public static MacStructure createMacStructure(CorrectedBinaryMessage message, in return new AdjacentStatusBroadcastAbbreviated(message, offset); case PHASE1_125_IDENTIFIER_UPDATE: return new FrequencyBandUpdate(message, offset); + case PHASE1_168_L3HARRIS_TALKER_ALIAS: + return new L3HarrisTalkerAlias(message, offset); case PHASE1_176_L3HARRIS_GROUP_REGROUP: return new L3HarrisRegroupCommand(message, offset); case PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java index 47516b455..59d6293cf 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java @@ -76,6 +76,8 @@ public enum MacOpcode PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED(123, "NETWORK STATUS BROADCAST ABBREVIATED", 11), PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED(124, "ADJACENT STATUS BROADCAST ABBREVIATED", 9), PHASE1_125_IDENTIFIER_UPDATE(125, "IDENTIFIER UPDATE", 9), + PHASE1_128_L3HARRIS_GPS_LOCATION(128, "GPS LOCATION", -1), + PHASE1_168_L3HARRIS_TALKER_ALIAS(168, "TALKER ALIAS", -1), PHASE1_176_L3HARRIS_GROUP_REGROUP(176, "REGROUP COMMAND", 17), PHASE1_PARTITION_1_UNKNOWN_OPCODE(-1, "UNKNOWN PHASE 1 OPCODE", -1), diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGpsLocation.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGpsLocation.java new file mode 100644 index 000000000..4b274f850 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGpsLocation.java @@ -0,0 +1,302 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.dmr.identifier.P25Location; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.reference.Vendor; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.TimeZone; +import org.jdesktop.swingx.mapviewer.GeoPosition; + +/** + * L3Harris GPS Location. + * + * Bit field definitions are best-guess from observed samples. + */ +public class L3HarrisGpsLocation extends MacStructure +{ + public static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss"); + static { + SDF.setTimeZone(TimeZone.getTimeZone("UTC")); + } + private static final DecimalFormat GPS_FORMAT = new DecimalFormat("0.000000"); + private static final DecimalFormat FIXED = new DecimalFormat("000"); + private static final int[] OPCODE = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] UNKNOWN = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] VENDOR = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] LENGTH = {24, 25, 26, 27, 28, 29, 30, 31}; //Length is 17.5 bytes ... observed 17 here + + //Bits 32 & 33 not set in sample data - seems unused for a 1/5000th of a minute number system + private static final int[] LATITUDE_MINUTES_FRACTIONAL = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46}; + private static final int LATITUDE_HEMISPHERE = 48; + //Bit 47 & 49 not set in sample data + private static final int[] LATITUDE_MINUTES = {50, 51, 52, 53, 54, 55}; + private static final int[] LATITUDE_DEGREES = {56, 57, 58, 59, 60, 61, 62, 63}; + + //Bits 64 & 65 not set in sample data - seems unused for a 1/5000th of a minute number system + private static final int[] LONGITUDE_MINUTES_FRACTIONAL = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78}; + private static final int LONGITUDE_HEMISPHERE = 80; + //Bit 79 & 81 not set in sample data + private static final int[] LONGITUDE_MINUTES = {82, 83, 84, 85, 86, 87}; + private static final int[] LONGITUDE_DEGREES = {88, 89, 90, 91, 92, 93, 94, 95}; + + //There's a leading bit missing from GPS Time to get from (2^16) to (2^17) needed address space (86,400 total seconds) + private static final int[] GPS_TIME = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; + + private static final int[] U2 = {112, 113, 114, 115, 116, 117, 118, 119}; + private static final int[] U3 = {120, 121, 122, 123, 124, 125, 126, 127}; + private static final int[] U4 = {128, 129, 130, 131, 132, 133, 134, 135}; + private static final int[] U5 = {136, 137, 138, 139, 140, 141, 142, 143}; + private static final int[] U6 = {144, 145, 146, 147, 148, 149, 150, 151}; + private static final int[] U7 = {152, 153, 154, 155, 156, 157, 158, 159}; + private static final int[] U8 = {160, 161, 162, 163, 164, 165, 166, 167}; + + private P25Location mLocation; + private List mIdentifiers; + private GeoPosition mGeoPosition; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public L3HarrisGpsLocation(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * GPS Position time in milliseconds. + * @return time in ms UTC + */ + public long getTimestampMs() + { + return getMessage().getInt(GPS_TIME, getOffset()) * 1000; //Convert seconds to milliseconds. + } + + /** + * GPS Location + * @return location in decimal degrees + */ + public P25Location getLocation() + { + if(mLocation == null) + { + mLocation = P25Location.createFrom(getLatitude(), getLongitude()); + } + + return mLocation; + } + + /** + * Geo position + * @return position + */ + public GeoPosition getGeoPosition() + { + if(mGeoPosition == null) + { + mGeoPosition = new GeoPosition(getLatitude(), getLongitude()); + } + + return mGeoPosition; + } + + /** + * GPS location - latitude + * @return latitude degrees decimal + */ + public double getLatitude() + { + return (getLatitudeDegrees() + (getLatitudeMinutes() / 60.0)) * (getMessage().get(LATITUDE_HEMISPHERE + getOffset()) ? -1 : 1); + } + + /** + * Latitude degrees + */ + private double getLatitudeDegrees() + { + return getMessage().getInt(LATITUDE_DEGREES, getOffset()); + } + + /** + * Latitude minutes + */ + private double getLatitudeMinutes() + { + return getMessage().getInt(LATITUDE_MINUTES, getOffset()) + + getMessage().getInt(LATITUDE_MINUTES_FRACTIONAL, getOffset()) / 5000d; + } + + /** + * GPS location - longitude + * @return longitude degrees decimal + */ + public double getLongitude() + { + return (getLongitudeDegrees() + (getLongitudeMinutes() / 60.0)) * (getMessage().get(LONGITUDE_HEMISPHERE + getOffset()) ? -1 : 1); + } + + /** + * Longitude degrees + */ + private double getLongitudeDegrees() + { + return getMessage().getInt(LONGITUDE_DEGREES, getOffset()); + } + + /** + * Longitude minutes + */ + private double getLongitudeMinutes() + { + return getMessage().getInt(LONGITUDE_MINUTES, getOffset()) + + getMessage().getInt(LONGITUDE_MINUTES_FRACTIONAL, getOffset()) / 5000d; + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(getVendor() == Vendor.HARRIS) + { + sb.append("L3H TALKER GPS "); + } + else + { + sb.append("VENDOR:").append(getVendor()).append(" TALKER GPS "); + } + + sb.append(GPS_FORMAT.format(getLatitude())).append(" ").append(GPS_FORMAT.format(getLongitude())); + sb.append(" TIME:").append(SDF.format(getTimestampMs())); + return sb.toString(); + } + + /** + * Vendor ID. This should be L3Harris unless another vendor is also using this Opcode. + */ + public Vendor getVendor() + { + return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset())); + } + + /** + * Message length. + * + * @return length in bytes, including the opcode. + */ + public int getLength() + { + return getMessage().getInt(LENGTH, getOffset()); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocation()); + } + + return mIdentifiers; + } + + public static void main(String[] args) + { + String[] examples = new String[]{ + "8080AAA41106CC031E150E8A53C0E69200061C05E00D9000", + "8080AAA41106CC031E150E8A53C0E89300061C2A700D9000", + "8080AAA41106CC031E150E8A53C0E99300061C78400D9000", + "8080AAA41106D6031E150E8A53C0F29300061CDB200D9000", + "8080AAA41106D6031E150E8A53C0F39300061C89100D9000", + "8C80AAA41106CC031E150E8A53C11D9800061CE68088C000", + "8C80AAA41106CC031E150E8A53C11E9800061C10D088C000", + "8880AAA41106CC031E150E8A53C0D39100061C6AF0732000", + "8080AAA4111F7C231D0852B152BBB99600001C75700D9000", + "8080AAA4111F7C231D0852B152BBBA9700001CC3700D9000", + "8480AAA4111F86231D0852B152BBE08D00001C8BD0F67000", + "8480AAA4111F86231D0852B152BBE18D00001CD9E0F67000", + "8480AAA4111F7C231D0852B152BBEA8C00001C6560F67000", + "8480AAA4111F7C231D0852B152BBEB8D00001C7700F67000", + "8480AAA4111F7C231D0852B152BBEE8D00001CE480F67000", + "8480AAA4111F7C231D0852B152BBF88A00001CDDB0F67000", + "8480AAA4111F7C231D0852B152BBF98A00001C8F80F67000", + "8480AAA4111F7C231D0852B152BBF88D00001C9570F67000", + "8880AAA4111748212525C6B84DBD9C0F080517E600732000", //19 04642010 + "8880AAA4111748212525C6B84DBDA69F0A08172480732000", + "8880AAA4111748212525C6B84DBDA89F0A08174B40732000", + "8880AAA4111748212525C6B84DBDAAB4010816B9E0732000", + "8880AAA4111748212525C6B84DBDABB401081762A0732000", + "8880AAA4111748212525C6B84DBDAC9C0008170650732000", + "8C80AAA4111734212525DAB84DBE454200071748A088C000", + "9080AAA4111734212525DAB84DBE4E42000717B4E0F0F000", + "9080AAA4111734212525DAB84DBE4F42000717E6D0F0F000", + "8480AAA4111748212525D0B84DBE868E180217A0A0F67000", + "8480AAA4111748212525C6B84DBE87641303170E80F67000", + "8480AAA411173E212525BCB84DBE89641303172190F67000", + "8480AAA41116A821252468B84DC0C3CF3A0915ECF0F67000", + "8480AAA41116A82125247CB84DC0C443130916FBA0F67000", + "8480AAA41116A821252486B84DC0C7272005133230F67000", + "9080AAA41116BC2325178EB24DBE2855050D15E920F0F000", //04642051 + "8C80AAA41116BC2325178EB24DBE2E71060D142C1088C000", + "8C80AAA41116BC2325178EB24DBE2F73030D141C1088C000", + "8480AAA411173E232517D4B24DBF03C1020D14B910F67000", + "9080AAA411260C21251A90804EBF8A9CAD1A166980F0F000",//04642016 << only sample set that is moving, heading south then north + "9080AAA411261621251B1C804EBF8B8C921A16FAA0F0F000", + "8480AAA411263421250F6E814EBFD3982F13168E80F67000", + "8480AAA411263421250EEC814EBFD4982F1316FC70F67000", + "8480AAA411263421250E60814EBFD5982F131645A0F67000", + "8880AAA411087A21251F0E804EC07CB39108143C40732000", + "8880AAA411087A21251F54804EC07DB39108140EE0732000", + "8480AAA4110820212520EE804EC0AA4ED00116E970F67000", + "8480AAA411083E21252094804EC0AB26F702164140F67000", + "8480AAA411086621252058804EC0AC26F702169920F67000", + "8480AAA411088E2125201C804EC0AD26F702167A00F67000", + "8480AAA41108B621251FD6804EC0AEEFE50116DDB0F67000", + "8C80AAA4110AD21F25134C804EBFCDBD040A1801F088C000", //50 04642025 + "8C80AAA4110AD21F25134C804EBFCEBD090A1832C088C000", + "8080AAA4110AFA1F25136A804EBFE9A70B0A18A4F00D9000", + "9080AAA4111BE41E250DD4AE4DC09B5F010314D660F0F000", //53 04642031 + "9080AAA4111BE41E250DD4AE4DC09D5F0903149440F0F000", + "9080AAA4111BE41E250DD4AE4DC09E5F0903146210F0F000", + "8080AAA4111BBC1E250DF2AE4DC0A3022F0714FA900D9000", + "8080AAA4111BBC1E250DE8AE4DC0A4022F071484F00D9000", + "8080AAA4111BD01E250DE8AE4DC0A5022F07146DB00D9000", + }; + + for(String example: examples) + { + CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.loadHex(example)); + L3HarrisGpsLocation gps = new L3HarrisGpsLocation(cbm, 8); + System.out.println(gps); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerAlias.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerAlias.java new file mode 100644 index 000000000..950521a6c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerAlias.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.alias.P25TalkerAliasIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.reference.Vendor; +import java.util.ArrayList; +import java.util.List; + +/** + * L3Harris Talker Alias. + */ +public class L3HarrisTalkerAlias extends MacStructure +{ + private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] LENGTH = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int ALIAS_START = 24; + + private List mIdentifiers; + private P25TalkerAliasIdentifier mAliasIdentifier; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public L3HarrisTalkerAlias(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(getVendor() == Vendor.HARRIS) + { + sb.append("L3HARRIS "); + } + else + { + sb.append("WARNING: UNKNOWN VENDOR:").append(getVendor()); + } + + sb.append(" TALKER ALIAS: "); + sb.append(getAlias()); + return sb.toString(); + } + + /** + * Alias identifier + */ + public P25TalkerAliasIdentifier getAlias() + { + if(mAliasIdentifier == null) + { + int length = getLength() * 8; + String alias = new String(getMessage().getSubMessage(ALIAS_START + getOffset(), length + getOffset()).getBytes()).trim(); + mAliasIdentifier = P25TalkerAliasIdentifier.create(alias); + } + + return mAliasIdentifier; + } + + /** + * Vendor ID. This should be L3Harris unless another vendor is also using this Opcode. + */ + public Vendor getVendor() + { + return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset())); + } + + /** + * Message length. + * + * @return length in bytes, including the opcode. + */ + public int getLength() + { + return getMessage().getInt(LENGTH, getOffset()); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getAlias()); + } + + return mIdentifiers; + } +}