Skip to content

Commit 215e406

Browse files
DSheirerDennis Sheirer
andauthored
#2039 P25P2 bogus talkgroup and radio values showing in calls. Corrects message length error that was causing mis-aligned mac message parsing that contained bad/errant talkgroup and radio values to be included in the call metadata. Adds support for a new Moto feature discovery that appears to be announcing talkgroup member radios that are active on/tuned to the traffic channel. (#2042)
Co-authored-by: Dennis Sheirer <[email protected]>
1 parent 66a0b25 commit 215e406

File tree

8 files changed

+576
-13
lines changed

8 files changed

+576
-13
lines changed

src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
188188
private final PatchGroupManager mPatchGroupManager = new PatchGroupManager();
189189
private final P25P1NetworkConfigurationMonitor mNetworkConfigurationMonitor;
190190
private final Listener<ChannelEvent> mChannelEventListener;
191-
private P25TrafficChannelManager mTrafficChannelManager;
191+
private final P25TrafficChannelManager mTrafficChannelManager;
192192
private ServiceOptions mCurrentServiceOptions;
193193

194194
/**

src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,9 +465,16 @@ private void processMacMessage(MacMessage message)
465465
case PHASE1_90_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED:
466466
if(mac instanceof GroupRegroupVoiceChannelUserAbbreviated gr)
467467
{
468-
mPatchGroupManager.addPatchGroup(gr.getPatchgroup(), message.getTimestamp());
468+
if(gr.hasPatchgroup())
469+
{
470+
mPatchGroupManager.addPatchGroup(gr.getPatchgroup(), message.getTimestamp());
471+
}
472+
473+
if(gr.hasPatchgroup() || gr.hasRadio())
474+
{
475+
processChannelUser(message, mac);
476+
}
469477
}
470-
processChannelUser(message, mac);
471478
break;
472479

473480
/**

src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@
129129
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisUnknownOpcode143;
130130
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.UnknownOpcode136;
131131
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaAcknowledgeResponse;
132+
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaActiveGroupRadiosOpcode130_x82;
133+
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaActiveGroupRadiosOpcode143_x8F;
134+
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaActiveGroupRadiosOpcode191_xBF;
132135
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaDenyResponse;
133136
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupAddCommand;
134137
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantExplicit;
@@ -578,12 +581,18 @@ public static MacStructure createMacStructure(CorrectedBinaryMessage message, in
578581
return new MotorolaGroupRegroupVoiceChannelUserAbbreviated(message, offset);
579582
case MOTOROLA_81_GROUP_REGROUP_ADD:
580583
return new MotorolaGroupRegroupAddCommand(message, offset);
584+
case MOTOROLA_82_ACTIVE_GROUP_RADIOS_130:
585+
return new MotorolaActiveGroupRadiosOpcode130_x82(message, offset);
581586
case MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE:
582587
return new MotorolaGroupRegroupVoiceChannelUpdate(message, offset);
583588
case MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND:
584589
return new MotorolaGroupRegroupExtendedFunctionCommand(message, offset);
585590
case MOTOROLA_89_GROUP_REGROUP_DELETE:
586591
return new MotorolaGroupRegroupDeleteCommand(message, offset);
592+
case MOTOROLA_8F_ACTIVE_GROUP_RADIOS_143:
593+
return new MotorolaActiveGroupRadiosOpcode143_x8F(message, offset);
594+
case MOTOROLA_BF_ACTIVE_GROUP_RADIOS_191:
595+
return new MotorolaActiveGroupRadiosOpcode191_xBF(message, offset);
587596
case MOTOROLA_91_TALKER_ALIAS_HEADER:
588597
return new MotorolaTalkerAliasHeader(message, offset);
589598
case MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK:

src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,15 @@ public enum MacOpcode
115115

116116
MOTOROLA_80_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED(Vendor.MOTOROLA, 128, "MOTOROLA GROUP REGROUP VOICE CHANNEL USER ABBREVIATED", 8),
117117
MOTOROLA_81_GROUP_REGROUP_ADD(Vendor.MOTOROLA, 129, "MOTOROLA GROUP REGROUP ADD", 17),
118-
MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA, 131, "MOTOROLA INDIVIDUAL REGROUP ACTIVE", 8),
118+
MOTOROLA_82_ACTIVE_GROUP_RADIOS_130(Vendor.MOTOROLA, 130, "MOTOROLA ACTIVE GROUP RADIOS 130", Integer.MIN_VALUE),
119+
MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA, 131, "MOTOROLA INDIVIDUAL REGROUP ACTIVE", 7),
119120
MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND(Vendor.MOTOROLA, 133, "MOTOROLA GROUP REGROUP", 11),
120121
MOTOROLA_89_GROUP_REGROUP_DELETE(Vendor.MOTOROLA, 137, "MOTOROLA GROUP REGROUP DELETE", 17),
122+
MOTOROLA_8F_ACTIVE_GROUP_RADIOS_143(Vendor.MOTOROLA, 143, "MOTOROLA ACTIVE GROUP RADIOS 143", Integer.MIN_VALUE),
123+
MOTOROLA_BF_ACTIVE_GROUP_RADIOS_191(Vendor.MOTOROLA, 191, "MOTOROLA ACTIVE GROUP RADIOS 191", Integer.MIN_VALUE),
121124
//Opcode 144 uses STANDARD vendor ID for some reason.
122-
MOTOROLA_91_TALKER_ALIAS_HEADER(Vendor.MOTOROLA, 145, "MOTOROLA GROUP REGROUP UNKNOWN", 17),
123-
MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK(Vendor.MOTOROLA, 149, "MOTOROLA UNKNOWN 149", 17),
125+
MOTOROLA_91_TALKER_ALIAS_HEADER(Vendor.MOTOROLA, 145, "MOTOROLA TALKER ALIAS HEADER", 17),
126+
MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK(Vendor.MOTOROLA, 149, "MOTOROLA TALKER ALIAS DATA BLOCK", 17),
124127
MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED(Vendor.MOTOROLA, 160, "MOTOROLA GROUP REGROUP VOICE CHANNEL USER EXTENDED", 16),
125128
MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT(Vendor.MOTOROLA, 163, "MOTOROLA GROUP REGROUP CHANNEL GRANT IMPLICIT", 11),
126129
MOTOROLA_A4_GROUP_REGROUP_CHANNEL_GRANT_EXPLICIT(Vendor.MOTOROLA, 164, "MOTOROLA GROUP REGROUP CHANNEL GRANT EXPLICIT", 13),

src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
3030
import io.github.dsheirer.module.decode.p25.reference.ServiceOptions;
3131
import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions;
32-
3332
import java.util.ArrayList;
3433
import java.util.List;
3534

@@ -64,11 +63,7 @@ public String toString()
6463
StringBuilder sb = new StringBuilder();
6564
sb.append("GROUP REGROUP VOICE CHANNEL USER ABBREVIATED");
6665
sb.append(" TALKGROUP:").append(getPatchgroup());
67-
if(hasRadio())
68-
{
69-
sb.append(" TALKER RADIO:").append(getRadio());
70-
}
71-
66+
sb.append(" TALKER RADIO:").append(getRadio());
7267
return sb.toString();
7368
}
7469

@@ -91,6 +86,14 @@ public PatchGroupIdentifier getPatchgroup()
9186
return mPatchgroup;
9287
}
9388

89+
/**
90+
* Indicates if this message has a non-zero patch group identifier.
91+
*/
92+
public boolean hasPatchgroup()
93+
{
94+
return getInt(SUPERGROUP) > 0;
95+
}
96+
9497
/**
9598
* Talker radio identifier.
9699
*/
@@ -118,7 +121,11 @@ public List<Identifier> getIdentifiers()
118121
if(mIdentifiers == null)
119122
{
120123
mIdentifiers = new ArrayList<>();
121-
mIdentifiers.add(getPatchgroup());
124+
125+
if(hasPatchgroup())
126+
{
127+
mIdentifiers.add(getPatchgroup());
128+
}
122129

123130
if(hasRadio())
124131
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* *****************************************************************************
3+
* Copyright (C) 2014-2024 Dennis Sheirer
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>
17+
* ****************************************************************************
18+
*/
19+
20+
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
21+
22+
import io.github.dsheirer.bits.CorrectedBinaryMessage;
23+
import io.github.dsheirer.bits.IntField;
24+
import io.github.dsheirer.identifier.Identifier;
25+
import io.github.dsheirer.identifier.radio.RadioIdentifier;
26+
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
27+
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
28+
import java.util.Collections;
29+
import java.util.List;
30+
31+
/**
32+
* Motorola Group Radios Active Feature. Observed on NM-DTRS Phase 2 network. My theory is that this lists the
33+
* subset of talkgroup member radios that are currently active on the traffic channel.
34+
*
35+
* Opcodes 130 and 143 are almost the same, except 143 is mostly transmitted during an ACTIVE call state and 130 is
36+
* mostly transmitted during the HANGTIME state. I say mostly, because there are examples where both opcodes are seen
37+
* in both channel states.
38+
*
39+
* Curiously: opcode 130 is in the middle of the message number sequence where GROUP REGROUP messages are allocated.
40+
*
41+
* Opcode 143 includes an additional 1-byte status field that seems to contain a 2-bit sequence counter that mostly
42+
* cycles through all values, but sometimes it skips values or just toggles between two of the observed values.
43+
* Examples: 0x00, 0x40, 0x80, 0xC0
44+
* There doesn't seem to be any correlation between the value of this nibble and the radio IDs being transmitted.
45+
*
46+
* The lower nibble of this 1-byte field seems to have an additional flag bit that is sometimes transmitted:
47+
* Examples: 0x08, 0x88
48+
*
49+
* Both opcode 130 and 143 employ up to 2x 1-byte fields that always transmit a 0x09 value. The second field is only
50+
* present when the message is sending 3 or 4 radio IDs.
51+
*
52+
* It seems that the complete list of active member radios is transmitted in a fixed sequence with zero to four of those
53+
* radios transmitted per message. The messages are transmitted when the talkgroup is first allocated to the channel
54+
* and eventually the 130/143 messages stop listing radios and the empty 143 and the 191 messages are intermittently
55+
* sent. Occasionally there are radios listed in the message with all zeros values.
56+
*
57+
* Opcode 143 is transmitted with just the status byte and no radio IDs after the full sequence of radios has been
58+
* sent and are no longer being sent.
59+
*
60+
* I'm not sure what Opcode 191 indicates. It's always the same (3-bytes) value: BF9003. It may be some kind of
61+
* feature marker to let the listening radios/devices know that the feature is active on that traffic channel.
62+
*
63+
* These three opcodes seem to be used for this feature.
64+
* Opcode 130/x82 - talkgroup member radios currently active - mostly transmitted during HANGTIME
65+
* Opcode 143/x8F - talkgroup member radios currently active - mostly transmitted during ACTIVE call state
66+
* Opcode 191/xBF - transmitted continuously while channel is allocated to a talkgroup - fixed value: 0xBF9003
67+
*/
68+
public class MotorolaActiveGroupRadiosOpcode130_x82 extends MacStructureVendor
69+
{
70+
private static final IntField SEPARATOR_1 = IntField.length8(OCTET_4_BIT_24); //Value is always 0x09
71+
private static final IntField RADIO_1 = IntField.length24(OCTET_5_BIT_32);
72+
private static final IntField RADIO_2 = IntField.length24(OCTET_8_BIT_56);
73+
private static final IntField SEPARATOR_2 = IntField.length8(OCTET_11_BIT_80); //Value is always 0x09
74+
private static final IntField RADIO_3 = IntField.length24(OCTET_12_BIT_88);
75+
private static final IntField RADIO_4 = IntField.length24(OCTET_15_BIT_112);
76+
private RadioIdentifier mRadio1;
77+
private RadioIdentifier mRadio2;
78+
private RadioIdentifier mRadio3;
79+
private RadioIdentifier mRadio4;
80+
81+
/**
82+
* Constructs the message
83+
*
84+
* @param message containing the message bits
85+
* @param offset into the message for this structure
86+
*/
87+
public MotorolaActiveGroupRadiosOpcode130_x82(CorrectedBinaryMessage message, int offset)
88+
{
89+
super(message, offset);
90+
}
91+
92+
/**
93+
* Textual representation of this message
94+
*/
95+
public String toString()
96+
{
97+
StringBuilder sb = new StringBuilder();
98+
sb.append("MOTOROLA 130 ACTIVE GROUP RADIOS: ");
99+
sb.append(getRadio1());
100+
101+
if(hasRadio2())
102+
{
103+
sb.append(", ").append(getRadio2());
104+
105+
if(hasRadio3())
106+
{
107+
sb.append(", ").append(getRadio3());
108+
109+
if(hasRadio4())
110+
{
111+
sb.append(", ").append(getRadio4());
112+
}
113+
}
114+
}
115+
116+
CorrectedBinaryMessage cbm = getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8));
117+
sb.append(" MSG:").append(cbm.toHexString());
118+
119+
return sb.toString();
120+
}
121+
122+
/**
123+
* Radio identifier or null if the message doesn't contain the value.
124+
*/
125+
public RadioIdentifier getRadio1()
126+
{
127+
if(mRadio1 == null && hasRadio1())
128+
{
129+
mRadio1 = APCO25RadioIdentifier.createAny(getInt(RADIO_1));
130+
}
131+
132+
return mRadio1;
133+
}
134+
135+
/**
136+
* Radio identifier or null if the message doesn't contain the value.
137+
*/
138+
public RadioIdentifier getRadio2()
139+
{
140+
if(mRadio2 == null && hasRadio2())
141+
{
142+
mRadio2 = APCO25RadioIdentifier.createAny(getInt(RADIO_2));
143+
}
144+
145+
return mRadio2;
146+
}
147+
148+
/**
149+
* Radio identifier or null if the message doesn't contain the value.
150+
*/
151+
public RadioIdentifier getRadio3()
152+
{
153+
if(mRadio3 == null && hasRadio3())
154+
{
155+
mRadio3 = APCO25RadioIdentifier.createAny(getInt(RADIO_3));
156+
}
157+
158+
return mRadio3;
159+
}
160+
161+
/**
162+
* Radio identifier or null if the message doesn't contain the value.
163+
*/
164+
public RadioIdentifier getRadio4()
165+
{
166+
if(mRadio4 == null && hasRadio4())
167+
{
168+
mRadio4 = APCO25RadioIdentifier.createAny(getInt(RADIO_4));
169+
}
170+
171+
return mRadio4;
172+
}
173+
174+
/**
175+
* Indicates if this message contains the radio value.
176+
*/
177+
public boolean hasRadio1()
178+
{
179+
return getLength() > 4;
180+
}
181+
182+
/**
183+
* Indicates if this message contains the radio value.
184+
*/
185+
public boolean hasRadio2()
186+
{
187+
return getLength() > 8;
188+
}
189+
190+
/**
191+
* Indicates if this message contains the radio value.
192+
*/
193+
public boolean hasRadio3()
194+
{
195+
return getLength() > 11;
196+
}
197+
198+
/**
199+
* Indicates if this message contains the radio value.
200+
*/
201+
public boolean hasRadio4()
202+
{
203+
return getLength() > 15;
204+
}
205+
206+
@Override
207+
public List<Identifier> getIdentifiers()
208+
{
209+
return Collections.emptyList();
210+
}
211+
}

0 commit comments

Comments
 (0)