diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index df8c035e8..6a5b4be68 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -72,7 +72,10 @@ import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService; import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions; +import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; +import io.github.dsheirer.module.decode.dmr.message.voice.embedded.Arc4EncryptionParameters; +import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters; import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; @@ -289,7 +292,7 @@ private void processSMS(UDTShortMessageService sms) DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp()) .details("MESSAGE: " + sms.getSMS()) .identifiers(new IdentifierCollection(sms.getIdentifiers())) - .timeslot(sms.getTimeslot()) + .timeslot(getTimeslot()) .build(); broadcast(smsEvent); } @@ -312,7 +315,7 @@ private void processPacket(DMRPacketMessage packet) DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp()) .identifiers(mic) - .timeslot(packet.getTimeslot()) + .timeslot(getTimeslot()) .details("SMS:" + hyteraSmsPacket.getSMS()) .build(); broadcast(smsEvent); @@ -324,7 +327,7 @@ else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket) DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp()) .identifiers(mic) - .timeslot(packet.getTimeslot()) + .timeslot(getTimeslot()) .details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString()) .build(); broadcast(unknownTokenEvent); @@ -333,7 +336,7 @@ else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket) { DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp()) .identifiers(getMergedIdentifierCollection(packet.getIdentifiers())) - .timeslot(packet.getTimeslot()) + .timeslot(getTimeslot()) .details(packet.toString()) .build(); @@ -370,6 +373,19 @@ private void processVoice(VoiceMessage message) { broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot())); } + + if(message.getSyncPattern() == DMRSyncPattern.BS_VOICE_FRAME_F && message instanceof VoiceEMBMessage voiceEmb) + { + if(voiceEmb.hasEmbeddedParameters()) + { + EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters(); + + if(embedded.getShortBurst() instanceof Arc4EncryptionParameters arc4) + { + updateEncryptedCall(arc4, true, voiceEmb.getTimestamp()); + } + } + } } /** @@ -778,7 +794,7 @@ private void processCSBK(CSBKMessage csbk) DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp()) .details("Registration Request") .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) + .timeslot(getTimeslot()) .build(); broadcast(event); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); @@ -787,7 +803,7 @@ private void processCSBK(CSBKMessage csbk) DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp()) .details("Registration Response") .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) + .timeslot(getTimeslot()) .build(); broadcast(regRespEvent); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); @@ -829,7 +845,7 @@ private void processCSBK(CSBKMessage csbk) DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp()) .details("TALKGROUP AFFILIATION") .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) + .timeslot(getTimeslot()) .build(); broadcast(affiliateEvent); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); @@ -840,19 +856,20 @@ private void processCSBK(CSBKMessage csbk) } } - private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel, IdentifierCollection mergedIdentifiers) { + private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel, + IdentifierCollection mergedIdentifiers) { return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp()) .channel(channel) .details(csbk.getOpcode().getLabel()) .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) + .timeslot(getTimeslot()) .build(); } private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) { return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp()) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) + .timeslot(getTimeslot()) .details(details) .build(); } @@ -978,7 +995,7 @@ private void processLinkControl(LCMessage message, boolean isTerminator) } } break; - case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER: + case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER: if(message instanceof MotorolaGroupVoiceChannelUser cpgvcu) { if(isTerminator) @@ -1103,7 +1120,7 @@ private void processLinkControl(LCMessage message, boolean isTerminator) DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp()) .identifiers(ic) - .timeslot(message.getTimeslot()) + .timeslot(getTimeslot()) .details("LOCATION:" + gpsInformation.getGPSLocation()) .build(); @@ -1119,6 +1136,41 @@ private void processLinkControl(LCMessage message, boolean isTerminator) } } + /** + * Updates the current call with encryption information. + * @param encryptionParameters decoded from the Voice Frame F + * @param isGroup true for group or false for individual call. + */ + private void updateEncryptedCall(Arc4EncryptionParameters encryptionParameters, boolean isGroup, long timestamp) + { + if(mCurrentCallEvent != null) + { + String details = mCurrentCallEvent.getDetails();; + + if(details == null) + { + details = encryptionParameters.toString(); + } + else if(!details.contains(encryptionParameters.toString())) + { + details += " " + encryptionParameters; + } + + mCurrentCallEvent.setDetails(details); + } + else + { + mCurrentCallEvent = DMRDecodeEvent.builder(isGroup ? DecodeEventType.CALL_GROUP_ENCRYPTED : + DecodeEventType.CALL_ENCRYPTED, timestamp) + .channel(getCurrentChannel()) + .details(encryptionParameters.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .timeslot(getTimeslot()) + .build(); + broadcast(mCurrentCallEvent); + } + } + /** * Updates or creates a current call event. * @@ -1136,13 +1188,14 @@ private void updateCurrentCall(DecodeEventType type, String details, long timest .channel(getCurrentChannel()) .details(details) .identifiers(getIdentifierCollection().copyOf()) + .timeslot(getTimeslot()) .build(); broadcast(mCurrentCallEvent); } else { - if(type != DecodeEventType.CALL) + if(mCurrentCallEvent.getDetails() == null) { mCurrentCallEvent.setDetails(details); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java index fa38204f6..020795876 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java @@ -205,7 +205,7 @@ private void process(FullLCMessage message) { switch(message.getOpcode()) { - case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER: + case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER: if(message instanceof MotorolaGroupVoiceChannelUser cpvcu) { if(mCallSequence == null) diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java index ba5d20da8..4f5f1dc33 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java @@ -113,7 +113,7 @@ else if(message.size() == 96) case FULL_STANDARD_TERMINATOR_DATA: flc = new TerminatorData(message, timestamp, timeslot); break; - case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER: + case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER: flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot); break; case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER: diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java index 5fc477a3e..7c886f170 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java @@ -44,7 +44,7 @@ public enum LCOpcode FULL_STANDARD_TERMINATOR_DATA(Vendor.STANDARD, true, 48, "TERMINATOR DATA"), FULL_STANDARD_UNKNOWN(Vendor.STANDARD,true, -1, "FULL UNKNOWN"), - FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"), + FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"), FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"), //Observed on Cap+ Multi-Site System during an encrypted voice call FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),