Compare commits

...

19 Commits

Author SHA1 Message Date
Dennis Sheirer
85b2a38058 Version 0.6.0 Beta 1 2023-08-19 05:16:20 -04:00
Denny Sheirer
c78517ee75 Merge pull request #1631 from DSheirer/1630-flc-arc4-encryption-parameters
#1630 DMR FLC ARC4/EP Encryption Parameters
2023-08-18 07:08:30 -04:00
Dennis Sheirer
ae116d3dfe #1630 Updates ARC4 FLC encryption parameters message to detect and parse both Hytera & Motorola message formats. 2023-08-18 07:06:26 -04:00
Denny Sheirer
93252f9a40 Merge pull request #1629 from DSheirer/1628-libusb-device-list-error
#1628 LibUsb Error -99 During Device Discovery
2023-08-18 03:57:50 -04:00
Dennis Sheirer
3c03a4db2e #1628 Handle libusb get device list error condition and retry with increased logging during error code -99 to determine what causes this error condition, primarily on some Linux systems. 2023-08-18 03:55:42 -04:00
Denny Sheirer
d0fcd617aa Merge pull request #1627 from DSheirer/1626-dmr-call-events-missing-timeslot
#1626 Updates DMR call events to include timeslot and adds DMR encryption info when available.
2023-08-15 04:01:52 -04:00
Dennis Sheirer
6ca0decbd6 #1626 Updates DMR call events to include timeslot and adds DMR encryption information when available. 2023-08-15 04:01:27 -04:00
Denny Sheirer
ddd9ca933e Merge pull request #1624 from DSheirer/1623-unknown-opcode-message-filter
#1623 P25 Message Filter Set - Unknown Vendor Opcode
2023-08-14 04:04:30 -04:00
Dennis Sheirer
b2cc29569e #1623 resolves issue with handling of unknown P25 trunking opcode in message filter set. 2023-08-14 04:03:06 -04:00
Denny Sheirer
411735772b Merge pull request #1622 from DSheirer/dmr-cap-plus-identifier-updates
#1618 DMR Decoder Message Enhancements
2023-08-13 08:38:12 -04:00
sheirerd
2ffb2963af #1618 Update Cap+ radio & group parsing, process TXI & voice frame encryption parameters, and many other changes to the DMR message processing. 2023-08-13 08:30:23 -04:00
Denny Sheirer
227c88c8a2 Merge pull request #1614 from DSheirer/1612-dcs-alias-identifier
#1612 Enhance aliasing to support DCS tones
2023-07-24 04:34:46 -04:00
Dennis Sheirer
cbe9c79498 #1612 Enhance aliasing to support DCS tones 2023-07-24 04:30:16 -04:00
Denny Sheirer
1b8c100b43 Merge pull request #1611 from DSheirer/1605-tuner-editor-fails-to-display
#1605 Blank Tuner Editor Panel on Certain Linux Configurations
2023-07-22 06:00:38 -04:00
Dennis Sheirer
30c5fd64f2 #1605 Resolves issue where certain Linux users were unable to display the tuner editor panel due to an issue with the frequency control editor's usage of custom cursor. 2023-07-22 05:55:43 -04:00
Denny Sheirer
3a6b486340 Merge pull request #1610 from DSheirer/1604-event-panel-row-layout
#1604 Event Panel UI Layout Creates Excess Space On Resizing
2023-07-22 05:25:11 -04:00
Dennis Sheirer
ded7335dbf #1604 Resolves issue with event panel UI layout creating excess space between history management panel and events table. 2023-07-22 05:21:26 -04:00
Denny Sheirer
005a4f8119 Merge pull request #1608 from DSheirer/1603-duplicate-events
#1603 De-Duplicate Call Events
2023-07-22 05:06:11 -04:00
Dennis Sheirer
1ac49bfa7f #1603 Resolves issue with de-duplicating events to allow call duration values to display correctly and minimize the quantity of duplicate events. 2023-07-22 04:57:26 -04:00
82 changed files with 2696 additions and 930 deletions

2
.idea/misc.xml generated
View File

@@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -53,7 +53,7 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.6.0-alpha7'
version = '0.6.0-beta1'
//Java 20 is required for this version of the Project Panama preview/incubator feature
java {

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -19,22 +19,15 @@
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -45,11 +38,10 @@ import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AliasFactory
{
@@ -64,6 +56,11 @@ public class AliasFactory
BroadcastChannel copyBroadcast = new BroadcastChannel();
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
return copyBroadcast;
case DCS:
Dcs originalDcs = (Dcs)id;
Dcs copyDcs = new Dcs();
copyDcs.setDCSCode(originalDcs.getDCSCode());
return copyDcs;
case ESN:
Esn originalESN = (Esn)id;
Esn copyESN = new Esn();
@@ -234,65 +231,4 @@ public class AliasFactory
return copy;
}
public static AliasID getAliasID(AliasIDType type)
{
switch(type)
{
case BROADCAST_CHANNEL:
return new BroadcastChannel();
case ESN:
return new Esn();
case FLEETSYNC:
return new FleetsyncID();
case LTR_NET_UID:
return new UniqueID();
case LOJACK:
return new LoJackFunctionAndID();
case MDC1200:
return new MDC1200ID();
case MIN:
return new Min();
case MPT1327:
return new MPT1327ID();
case NON_RECORDABLE:
return new NonRecordable();
case PRIORITY:
return new Priority();
case RADIO_ID:
return new Radio();
case RADIO_ID_RANGE:
return new RadioRange();
case RECORD:
return new Record();
case TALKGROUP:
return new Talkgroup();
case TALKGROUP_RANGE:
return new TalkgroupRange();
case SITE:
return new SiteID();
case STATUS:
return new UserStatusID();
case LEGACY_TALKGROUP:
return new LegacyTalkgroupID();
default:
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
}
}
public static AliasAction getAliasAction(AliasActionType type)
{
switch(type)
{
case BEEP:
return new BeepAction();
case CLIP:
return new ClipAction();
case SCRIPT:
return new ScriptAction();
default:
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -21,6 +21,7 @@ package io.github.dsheirer.alias;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -32,6 +33,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
import io.github.dsheirer.identifier.esn.ESNIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
@@ -41,12 +43,8 @@ import io.github.dsheirer.identifier.status.UserStatusIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.module.decode.dcs.DCSCode;
import io.github.dsheirer.protocol.Protocol;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -57,6 +55,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
@@ -67,6 +69,7 @@ public class AliasList
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
private Map<String,Alias> mESNMap = new HashMap<>();
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
@@ -178,6 +181,12 @@ public class AliasList
radioRangeAliasList.add(radioRange, alias);
break;
case DCS:
if(id instanceof Dcs dcs)
{
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
}
break;
case ESN:
String esn = ((Esn)id).getEsn();
@@ -418,7 +427,7 @@ public class AliasList
return toList(radioAliasList.getAlias(radio));
}
break;
case ESN:
case DCS:
if(identifier instanceof ESNIdentifier)
{
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
@@ -439,9 +448,9 @@ public class AliasList
}
break;
case TONE:
if(identifier instanceof ToneIdentifier)
if(identifier instanceof ToneIdentifier toneIdentifier)
{
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
ToneSequence toneSequence = toneIdentifier.getValue();
if(toneSequence != null && toneSequence.hasTones())
{
@@ -454,6 +463,15 @@ public class AliasList
}
}
}
else if(identifier instanceof DCSIdentifier dcsIdentifier)
{
DCSCode dcsCode = dcsIdentifier.getValue();
if(dcsCode != null)
{
return toList(mDCSCodeAliasMap.get(dcsCode));
}
}
break;
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.alias.id;
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
@@ -54,6 +52,7 @@ import javafx.util.Callback;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.alias.id;
@@ -26,6 +23,7 @@ import java.util.EnumSet;
public enum AliasIDType
{
BROADCAST_CHANNEL("Audio Broadcast Channel"),
DCS("Digital Coded Squelch (DCS)"),
ESN("ESN"),
INVERT("Audio Inversion"),
LOJACK("LoJack"),

View File

@@ -0,0 +1,111 @@
/*
* *****************************************************************************
* 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.alias.id.dcs;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.module.decode.dcs.DCSCode;
/**
* Digital Coded Squelch (DCS) tone identifier
*/
public class Dcs extends AliasID implements Comparable<Dcs>
{
private DCSCode mDCSCode;
@Override
public AliasIDType getType()
{
return AliasIDType.DCS;
}
@Override
public boolean matches(AliasID id)
{
if(isValid() && id instanceof Dcs other)
{
return other.isValid() && getDCSCode().equals(other.getDCSCode());
}
return false;
}
/**
* DCS code
* @return DCS code or null.
*/
@JacksonXmlProperty(isAttribute = true, localName = "code")
public DCSCode getDCSCode()
{
return mDCSCode;
}
/**
* Sets the DCS code value.
* @param dcsCode to set
*/
public void setDCSCode(DCSCode dcsCode)
{
mDCSCode = dcsCode;
updateValueProperty();
}
@Override
public boolean isValid()
{
return mDCSCode != null;
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@Override
public String toString()
{
if(isValid())
{
return getDCSCode().toString();
}
return "DCS-Invalid - No Tone Selected";
}
@Override
public int compareTo(Dcs o)
{
if(isValid())
{
if(o.isValid())
{
return getDCSCode().compareTo(o.getDCSCode());
}
else
{
return 1;
}
}
return -1;
}
}

View File

@@ -0,0 +1,125 @@
/*
* *****************************************************************************
* 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.edac;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
* <p>
* See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
*/
public class BPTC_16_2
{
private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
/**
* Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
*
* @param message with 32 interleaved bits.
* @return descrambled and error checked message or null if the process fails or there are too many errors.
*/
public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
{
return null;
}
//Check for even parity. Bits 0-15 should be the same as bits 16-31.
for(int x = 0; x < 16; x++)
{
if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
{
return null;
}
}
return deinterleaved;
}
/**
* Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
*
* @param binaryMessage with 32 interleaved bits.
* @return descrambled and error checked message or null if the process fails or there are too many errors.
*/
public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
System.out.println(" DEINTER: " + deinterleaved.toHexString());
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
System.out.println(" DECODED: " + deinterleaved.toHexString());
System.out.println("FEC:" + fec);
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
{
return null;
}
//Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
for(int x = 0; x < 16; x++)
{
if(deinterleaved.get(x) == deinterleaved.get(x + 16))
{
return null;
}
}
return deinterleaved;
}
/**
* Performs deinterleave of the interleaved message.
*
* @param original to deinterleave
* @return deinterleaved message
*/
public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
{
CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
for(int x = 0; x < 32; x++)
{
if(original.get(x))
{
delinterleaved.set(DEINTERLEAVE[x]);
}
}
return delinterleaved;
}
public static void main(String[] args)
{
String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
for(String msg : msgs)
{
CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
System.out.println("ORIGINAL: " + original.toHexString());
CorrectedBinaryMessage decoded = decodeReverseChannel(original);
System.out.println("-------------------------");
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -226,17 +226,19 @@ public class CRCUtil
{
mLog.debug("Starting");
//DMR message
// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011";
String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110";
mLog.debug(raw);
raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000";
BinaryMessage message = BinaryMessage.load(raw);
mLog.debug(message.toString());
long poly = 0x13l;
long[] checksums = generate(32, 4, poly, 0, true);
long polynomial = 0x11021l;
decode(message, 0, 80, polynomial, 16);
mLog.debug(message.toString());
mLog.debug("Finished");
StringBuilder sb = new StringBuilder();
sb.append("private static int[] CHECKSUMS = new int[]{");
for(long checksum: checksums)
{
sb.append("0x").append(Long.toHexString(checksum).toUpperCase());
sb.append(",");
}
sb.append("};");
System.out.println("Checksums:\n" + sb);
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* 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.edac;
import io.github.dsheirer.bits.BinaryMessage;
@@ -5,27 +24,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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/>
* -----------------------------------------------------------------------
* Galois24 decoder based on Hank Wallace's tutorial/algorithm located at:
* http://www.aqdi.com/golay.htm
******************************************************************************/
/**
* Galois 24/12/7 decoder
*/
@@ -160,4 +158,17 @@ public class Golay24
return (checksum ^ calculated);
}
public static void main(String[] args)
{
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));
System.out.println("M:" + bm.toHexString());
int a = Golay24.checkAndCorrect(bm, 0);
System.out.println("M:" + bm.toHexString());
System.out.println("A:" + a);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -22,22 +22,9 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.InvalidFrequencyException;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
@@ -49,27 +36,39 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* Custom frequency controller for displaying or editing frequency values in MHz.
*/
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
private Color mHighlightColor = Color.YELLOW;
private long mFrequency;
private Cursor mBlankCursor;
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
/**
* Constructor
*/
public JFrequencyControl()
{
init();
}
/**
* Initializes the control/layout.
*/
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
@@ -104,19 +103,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
/**
* Create a blank cursor to use when the mouse is over the digits
*/
//Create an empty byte array
byte[] imageByte = new byte[0];
//Create image for cursor using empty array
Image cursorImage = Toolkit.getDefaultToolkit().createImage(imageByte);
mBlankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "cursor");
revalidate();
}
/**
* Enables or disables the control.
* @param enabled true if this component should be enabled, false otherwise
*/
@Override
public void setEnabled(boolean enabled)
{
@@ -157,11 +150,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
/**
* Current frequency value for the control.
* @return frequency
*/
public long getFrequency()
{
return mFrequency;
}
/**
* Updates the frequency value according to the manually changed digit value.
*/
private void updateFrequency()
{
long frequency = 0;
@@ -174,14 +174,15 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
mFrequency = frequency;
}
/**
* Fires a source changed event
* @throws SourceException of there's an error
*/
private void fireSourceChanged() throws SourceException
{
updateFrequency();
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
while(it.hasNext())
{
it.next().process(event);
@@ -217,12 +218,21 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
mProcessors.remove(processor);
}
/**
* Custom single digit value editor.
*/
public class Digit extends JTextField
{
private static final long serialVersionUID = 1L;
private int mPower = 0;
private long mValue = 0;
/**
* Constructor
* @param position of the digit where a position of 1 is the ones unit of MHz and a position of -1 is the tenths
* unit of MHz.
* @throws ParseException if there is an error
*/
private Digit(int position) throws ParseException
{
super("0");
@@ -236,6 +246,11 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
this.addMouseWheelListener(listener);
}
/**
* Generates a custom tooltip for the current hover-over digit.
* @param power of the digit in units of 10
* @return custom tooptip.
*/
private static String getTooltip(int power)
{
StringBuilder sb = new StringBuilder();
@@ -288,23 +303,32 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
{
//Strip the digits higher than this one
long lower = frequency % (long)(FastMath.pow(10, mPower + 1));
//Set the value to int value of dividing by 10 to this power
long value = (long)(lower / (long)(FastMath.pow(10, mPower)));
set(value, fireChangeEvent);
}
/**
* Frequency value.
* @return frequency value.
*/
public long getFrequency()
{
return mValue * (long)FastMath.pow(10, mPower);
}
/**
* Increments the unit value and fires a change event
*/
public void increment()
{
increment(true);
}
/**
* Increments the unit value and fires a change event
* @param fireChangeEvent true to fire an event
*/
public void increment(boolean fireChangeEvent)
{
if(isEnabled())
@@ -313,11 +337,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
/**
* Decrements the unit value and fires a change event.
*/
public void decrement()
{
decrement(true);
}
/**
* Decrements the unit value and optionally fires a change event.
* @param fireChangeEvent set to true to fire an event
*/
public void decrement(boolean fireChangeEvent)
{
if(isEnabled())
@@ -349,7 +380,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
while(mValue < 0)
{
mValue += 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
@@ -363,7 +393,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
while(mValue > 9)
{
mValue -= 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
@@ -403,10 +432,8 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
if(se instanceof InvalidFrequencyException)
if(se instanceof InvalidFrequencyException ife)
{
InvalidFrequencyException ife = (InvalidFrequencyException)se;
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
}
@@ -415,13 +442,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
setText(String.valueOf(mValue));
repaint();
}
private class Listener implements KeyListener,
MouseListener,
MouseWheelListener
/**
* Multiple Listener interface implementation
*/
private class Listener implements KeyListener, MouseListener, MouseWheelListener
{
@Override
public void keyReleased(KeyEvent e)
@@ -503,14 +530,9 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
@Override
public void keyPressed(KeyEvent e)
{
}
public void keyPressed(KeyEvent e) {}
@Override
public void keyTyped(KeyEvent e)
{
}
public void keyTyped(KeyEvent e) {}
@Override
public void mouseClicked(MouseEvent e)
@@ -528,21 +550,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e)
{
Digit.this.setBackground(mHighlightColor);
setCursor(mBlankCursor);
repaint();
}
@@ -550,9 +564,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
public void mouseExited(MouseEvent e)
{
Digit.this.setBackground(Color.WHITE);
setCursor(Cursor.getDefaultCursor());
repaint();
}

View File

@@ -459,6 +459,12 @@ public class DMRRecordingViewer extends VBox
if(mFindText == null)
{
mFindText = new TextField();
mFindText.setOnKeyPressed(event -> {
if(event.getCode().equals(KeyCode.ENTER))
{
getFindButton().fire();
}
});
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* 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.gui.playlist.alias;
@@ -37,6 +34,7 @@ import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -63,6 +61,11 @@ import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
@@ -106,12 +109,6 @@ import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Editor for configuring individual aliases
*/
@@ -717,6 +714,7 @@ public class AliasItemEditor extends Editor<Alias>
Menu nbfmMenu = new ProtocolMenu(Protocol.NBFM);
nbfmMenu.getItems().add(new AddTalkgroupItem(Protocol.NBFM));
nbfmMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.NBFM));
nbfmMenu.getItems().add(new AddDcsItem());
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
passportMenu.getItems().add(new AddTalkgroupItem(Protocol.PASSPORT));
@@ -1281,6 +1279,24 @@ public class AliasItemEditor extends Editor<Alias>
}
}
/**
* Add Digital Coded Squelch (DCS) alias identifier menu item
*/
public class AddDcsItem extends MenuItem
{
public AddDcsItem()
{
super("Digital Coded Squelch (DCS)");
setOnAction(event -> {
Dcs dcs = new Dcs();
getIdentifiersList().getItems().add(dcs);
getIdentifiersList().getSelectionModel().select(dcs);
getIdentifiersList().scrollTo(dcs);
modifiedProperty().set(true);
});
}
}
/**
* Menu Item for adding a new protocol-specific Radio ID alias identifier
*/

View File

@@ -0,0 +1,98 @@
/*
* *****************************************************************************
* 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.gui.playlist.alias.identifier;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.module.decode.dcs.DCSCode;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Editor for Digital Coded Squelch (DCS) alias identifiers
*/
public class DcsEditor extends IdentifierEditor<Dcs>
{
private static final Logger mLog = LoggerFactory.getLogger(DcsEditor.class);
private ComboBox<DCSCode> mDCSCodeComboBox;
/**
* Constructs an instance
*/
public DcsEditor()
{
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
Label typeLabel = new Label("DCS Code");
GridPane.setConstraints(typeLabel, 0, 0);
gridPane.getChildren().add(typeLabel);
GridPane.setConstraints(getDCSCodeComboBox(), 1, 0);
gridPane.getChildren().add(getDCSCodeComboBox());
getChildren().add(gridPane);
}
@Override
public void setItem(Dcs item)
{
super.setItem(item);
if(item.isValid())
{
getDCSCodeComboBox().getSelectionModel().select(item.getDCSCode());
}
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
/**
* Combo-box loaded with DCS codes
* @return
*/
private ComboBox<DCSCode> getDCSCodeComboBox()
{
if(mDCSCodeComboBox == null)
{
mDCSCodeComboBox = new ComboBox<>();
mDCSCodeComboBox.getItems().addAll(DCSCode.STANDARD_CODES);
mDCSCodeComboBox.getItems().addAll(DCSCode.INVERTED_CODES);
mDCSCodeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
getItem().setDCSCode(getDCSCodeComboBox().getSelectionModel().getSelectedItem());
modifiedProperty().set(true);
});
}
return mDCSCodeComboBox;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -35,6 +35,8 @@ public class IdentifierEditorFactory
{
switch(type)
{
case DCS:
return new DcsEditor();
case ESN:
return new EsnEditor();
case LOJACK:

View File

@@ -32,6 +32,7 @@ public enum Form
CHANNEL_DESCRIPTOR,
CHANNEL_NAME,
CHANNEL_FREQUENCY,
DCS,
DECODER_TYPE,
DTMF,
ENCRYPTION_KEY,

View File

@@ -213,7 +213,12 @@ public enum DCSCode
/**
* Inverted DCS codes
*/
private static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
public static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
/**
* Standard DCS codes
*/
public static final EnumSet<DCSCode> STANDARD_CODES = EnumSet.range(N023,N754);
/**
* Lookup map for quickly finding a DCS code from the transmitted value.

View File

@@ -47,7 +47,7 @@ public class DCSMessage extends Message
@Override
public String toString()
{
return "Digital Coded Squelch (DCS) Detected: " + mDCSCode.name();
return "Digital Coded Squelch (DCS) Detected: " + mDCSCode.toString();
}
/**

View File

@@ -65,14 +65,17 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoice
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
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 @@ public class DMRDecoderState extends TimeslotDecoderState
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 @@ public class DMRDecoderState extends TimeslotDecoderState
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 @@ public class DMRDecoderState extends TimeslotDecoderState
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 @@ public class DMRDecoderState extends TimeslotDecoderState
{
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 @@ public class DMRDecoderState extends TimeslotDecoderState
{
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());
}
}
}
}
/**
@@ -646,12 +662,12 @@ public class DMRDecoderState extends TimeslotDecoderState
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -679,12 +695,12 @@ public class DMRDecoderState extends TimeslotDecoderState
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -711,12 +727,12 @@ public class DMRDecoderState extends TimeslotDecoderState
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel, mergedIdentifiers);
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -756,12 +772,12 @@ public class DMRDecoderState extends TimeslotDecoderState
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -778,7 +794,7 @@ public class DMRDecoderState extends TimeslotDecoderState
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 @@ public class DMRDecoderState extends TimeslotDecoderState
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()));
@@ -807,12 +823,12 @@ public class DMRDecoderState extends TimeslotDecoderState
}
else
{
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
{
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
mDetectedCallEventsMap.put(channel.getValue(), detectedEvent);
}
else
{
@@ -829,7 +845,7 @@ public class DMRDecoderState extends TimeslotDecoderState
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 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
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,11 +995,9 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
break;
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusGroupVoiceChannelUser cpgvcu)
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
if(message instanceof MotorolaGroupVoiceChannelUser cpgvcu)
{
updateRestChannel(cpgvcu.getRestChannel());
if(isTerminator)
{
getIdentifierCollection().remove(Role.FROM);
@@ -1105,7 +1120,7 @@ public class DMRDecoderState extends TimeslotDecoderState
DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp())
.identifiers(ic)
.timeslot(message.getTimeslot())
.timeslot(getTimeslot())
.details("LOCATION:" + gpsInformation.getGPSLocation())
.build();
@@ -1121,6 +1136,41 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
/**
* 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.
*
@@ -1138,13 +1188,14 @@ public class DMRDecoderState extends TimeslotDecoderState
.channel(getCurrentChannel())
.details(details)
.identifiers(getIdentifierCollection().copyOf())
.timeslot(getTimeslot())
.build();
broadcast(mCurrentCallEvent);
}
else
{
if(type != DecodeEventType.CALL)
if(mCurrentCallEvent.getDetails() == null)
{
mCurrentCallEvent.setDetails(details);
}

View File

@@ -44,6 +44,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
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.VoiceSuperFrameProcessor;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +61,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
private DecodeConfigDMR mConfigDMR;
private VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
private MBCAssembler mMBCAssembler = new MBCAssembler();
@@ -89,11 +93,45 @@ public class DMRMessageProcessor implements Listener<IMessage>
@Override
public void receive(IMessage message)
{
if(message instanceof FullLCMessage flc)
{
if(flc.getTimeslot() == 1)
{
mSuperFrameProcessor1.process(flc);
}
else
{
mSuperFrameProcessor2.process(flc);
}
}
else if(message instanceof VoiceMessage voiceMessage)
{
if(voiceMessage.getTimeslot() == 1)
{
mSuperFrameProcessor1.process(voiceMessage);
}
else
{
mSuperFrameProcessor2.process(voiceMessage);
}
}
else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
{
if(dmrBurst.getTimeslot() == 1)
{
mSuperFrameProcessor1.reset();
}
else
{
mSuperFrameProcessor2.reset();
}
}
//Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings
if(message instanceof ITimeslotFrequencyReceiver)
{
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
int[] lsns = receiver.getLogicalTimeslotNumbers();
int[] lsns = receiver.getLogicalSlotNumbers();
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();

View File

@@ -279,7 +279,7 @@ public class DMRNetworkConfigurationMonitor
*/
private void addDmrChannel(DMRChannel dmrChannel)
{
mObservedChannelMap.put(dmrChannel.getLogicalSlotNumber(), dmrChannel);
mObservedChannelMap.put(dmrChannel.getValue(), dmrChannel);
}
public void reset()

View File

@@ -322,7 +322,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
Opcode opcode, long timestamp, boolean encrypted)
{
int lsn = channel.getLogicalSlotNumber();
int lsn = channel.getValue();
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 Dennis Sheirer
* 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
@@ -32,17 +32,16 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChann
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio
* frames, optional encryption and call identifiers.
@@ -206,8 +205,8 @@ public class DMRCallSequenceRecorder extends MBECallSequenceRecorder
{
switch(message.getOpcode())
{
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu)
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
if(message instanceof MotorolaGroupVoiceChannelUser cpvcu)
{
if(mCallSequence == null)
{

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.dmr.channel;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -56,9 +56,9 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
}
/**
* Repeater number for the channel.
* Repeater number or channel number for the channel.
*/
public int getRepeater()
public int getChannel()
{
return getValue();
}
@@ -72,36 +72,6 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
return mTimeslot;
}
/**
* Logical slot number for this channel. LSN is a 1-based index value where repeater one, timeslot 1 is
* LSN 1, timeslot 2 is LSN 2, etc.
*
* Formula: LSN = ((channel - 1) * 2) + timeslot
*
* @return logical slot number, a 1-based index value
*/
public int getLogicalSlotNumber()
{
int repeater = getRepeater();
if(repeater > 0)
{
return ((repeater - 1) * 2) + getTimeslot();
}
return 0;
}
/**
* Returns an array of length 1 containing this channel's logical slot number
*/
public int[] getLSNArray()
{
int[] logicalSlotNumbers = new int[1];
logicalSlotNumbers[0] = getLogicalSlotNumber();
return logicalSlotNumbers;
}
/**
* Number of timeslots for the DMR channel.
* @return 2 always.
@@ -121,6 +91,13 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
return true;
}
@Override
public String toString()
{
return "CHAN:" + getChannel() + ":" + getTimeslot();
}
/**
* Not implemented
*/

View File

@@ -19,29 +19,60 @@
package io.github.dsheirer.module.decode.dmr.channel;
import java.util.List;
/**
* DMR logical channel. This channel uses a logical channel number and a timeslot.
* DMR Logical Slot Number (LSN) channel.
*
* Note: LSN 1-16 are represented in memory using the following channel and timeslot values:
*
* LSN: CHANNEL/TIMESLOT
* 1: 1/1
* 2: 1/2
* 3: 2/1
* 4: 2/2
* 5: 3/1
* 6: 3/2
* 7: 4/1
* 8: 4/2
* 9: 5/1
* 10: 5/2
* 11: 6/1
* 12: 6/2
* 13: 7/1
* 14: 7/2
* 15: 8/1
* 16: 8/2
*/
public class DMRLogicalChannel extends DMRChannel
public class DMRLsn extends DMRChannel implements ITimeslotFrequencyReceiver
{
private TimeslotFrequency mTimeslotFrequency;
/**
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
* calculated logical slot value for visual compatibility for users.
* Constructs an instance
*
* @param channel number or repeater number
* @param logicalSlotNumber - zero based index.
* @param lsn in range 1 - 16
*/
public DMRLogicalChannel(int channel, int timeslot)
public DMRLsn(int lsn)
{
super(channel, timeslot);
super(((lsn - 1) / 2) + 1, ((lsn - 1) % 2) + 1);
}
@Override
public String toString()
{
return "LSN:" + getLsn();
}
/**
* Downlink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
* Logical Slot Number
* @return lsn
*/
public int getLsn()
{
return ((getChannel() - 1) * 2) + getTimeslot();
}
@Override
public long getDownlinkFrequency()
{
@@ -53,10 +84,6 @@ public class DMRLogicalChannel extends DMRChannel
return 0;
}
/**
* Uplink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
public long getUplinkFrequency()
{
@@ -68,23 +95,40 @@ public class DMRLogicalChannel extends DMRChannel
return 0;
}
@Override
public int[] getLogicalSlotNumbers()
{
return new int[]{getLsn()};
}
/**
* Sets the timeslot frequency mapping
* @param timeslotFrequency
* Sets the lsn to frequency mapper value.
* @param timeslotFrequency to set
*/
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
{
mTimeslotFrequency = timeslotFrequency;
}
/**
* Formatted channel number
*/
public String toString()
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
StringBuilder sb = new StringBuilder();
sb.append("LSN:").append(getLogicalSlotNumber());
// sb.append(" LCN:").append(getValue());
return sb.toString();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getLsn())
{
setTimeslotFrequency(timeslotFrequency);
return;
}
}
}
public static void main(String[] args)
{
for(int x = 1; x <= 16; x++)
{
DMRLsn lsn = new DmrRestLsn(x);
System.out.println(lsn);
}
}
}

View File

@@ -1,16 +1,37 @@
/*
* *****************************************************************************
* 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.dmr.channel;
/**
* DMR Tier III Trunking Channel
* DMR Tier III Trunking channel. This channel uses a logical channel number and a timeslot.
*/
public class DMRTier3Channel extends DMRLogicalChannel
public class DMRTier3Channel extends DMRChannel
{
private TimeslotFrequency mTimeslotFrequency;
/**
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
* calculated logical slot value for visual compatibility for users.
*
* @param channel number or repeater number
* @param timeslot
* @param logicalSlotNumber - zero based index.
*/
public DMRTier3Channel(int channel, int timeslot)
{
@@ -18,23 +39,51 @@ public class DMRTier3Channel extends DMRLogicalChannel
}
/**
* Logical slot number for this channel.
*
* Formula: LSN = (channel * 2) + timeslot
*
* @return logical slot number, a 1-based index value
* Downlink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
public int getLogicalSlotNumber()
public long getDownlinkFrequency()
{
int repeater = getRepeater();
if(repeater > 0)
if(mTimeslotFrequency != null)
{
return ((repeater) * 2) + getTimeslot();
return mTimeslotFrequency.getDownlinkFrequency();
}
return 0;
}
/**
* Uplink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
public long getUplinkFrequency()
{
if(mTimeslotFrequency != null)
{
return mTimeslotFrequency.getUplinkFrequency();
}
return 0;
}
/**
* Sets the timeslot frequency mapping
* @param timeslotFrequency
*/
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
{
mTimeslotFrequency = timeslotFrequency;
}
/**
* Formatted channel number
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(" LCN:").append(getValue());
return sb.toString();
}
}

View File

@@ -0,0 +1,42 @@
/*
* *****************************************************************************
* 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.dmr.channel;
/**
* DMR Rest Channel logical slot number (LSN).
*/
public class DmrRestLsn extends DMRLsn
{
/**
* Constructs an instance
*
* @param lsn in range 1 - 16
*/
public DmrRestLsn(int lsn)
{
super(lsn);
}
@Override
public String toString()
{
return "REST:" + getLsn();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -26,7 +26,7 @@ public interface ITimeslotFrequencyReceiver
/**
* Provides the logical slot number(s) that require a matching timeslot frequency mapping
*/
public int[] getLogicalTimeslotNumbers();
public int[] getLogicalSlotNumbers();
/**
* Applies the list of timeslot frequency mappings to the implementer

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -31,7 +31,7 @@ import javafx.beans.property.SimpleLongProperty;
import javafx.util.Callback;
/**
* Maps a timeslot number to a pair of channel frequency values
* Maps a logical slot number (LSN) to a pair of channel frequency values
*/
public class TimeslotFrequency
{
@@ -73,7 +73,7 @@ public class TimeslotFrequency
* Downlink Frequency property
*/
@JsonIgnore
public LongProperty downlinkFrequencyPropertyProperty()
public LongProperty downlinkFrequencyProperty()
{
return mDownlinkFrequencyProperty;
}
@@ -168,7 +168,7 @@ public class TimeslotFrequency
*/
public static Callback<TimeslotFrequency,Observable[]> extractor()
{
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyPropertyProperty(),
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(),
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
}
}

View File

@@ -1,5 +1,25 @@
/*
* *****************************************************************************
* 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.dmr.message;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
/**
@@ -53,4 +73,13 @@ public abstract class DMRBurst extends DMRMessage
{
return mSyncPattern;
}
/**
* Extracts the sync payload from between the two payload fragments.
* @return binary message containing just the sync payload bits (64).
*/
public BinaryMessage getSyncPayload()
{
return getMessage().get(SYNC_START, PAYLOAD_2_START);
}
}

View File

@@ -0,0 +1,34 @@
/*
* *****************************************************************************
* 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.dmr.message;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
/**
* DMR message that exposes a service options configuration
*/
public interface IServiceOptionsProvider
{
/**
* Service Options
* @return service options
*/
ServiceOptions getServiceOptions();
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -45,7 +45,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataH
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
@@ -132,7 +132,7 @@ public class DMRDataMessageFactory
}
else
{
MotorolaProprietaryDataHeader mprdh = new MotorolaProprietaryDataHeader(pattern,
MotorolaDataEncryptionHeader mprdh = new MotorolaDataEncryptionHeader(pattern,
payload, cach, slotType, timestamp, timeslot);
mprdh.setValid(valid);
return mprdh;

View File

@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
@@ -39,7 +39,6 @@ import java.util.List;
public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] LC_START_STOP = new int[]{16, 17};
private static final int TIMESLOT = 18;
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
private static final int ASYNC = 24;
private static final int[] SITE = new int[]{25, 26, 27, 28};
@@ -58,7 +57,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
private static final int[] NEIGHBOR_6_REST = new int[]{76, 77, 78, 79};
private DMRLogicalChannel mRestChannel;
private DmrRestLsn mRestChannel;
private DMRSite mSite;
private List<Identifier> mIdentifiers;
@@ -286,37 +285,23 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
return getMessage().getInt(REST_LSN);
}
/**
* Rest repeater
*/
public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
/**
* Rest timeslot
* @return timeslot 1 or 2
*/
public int getRestTimeslot()
{
return (getRestLSN() % 2 == 0) ? 2 : 1;
}
/**
* DMR Channel
*/
public DMRLogicalChannel getRestChannel()
public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
return getRestChannel().getLSNArray();
return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -329,7 +314,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getRestChannel().getLogicalSlotNumber())
if(timeslotFrequency.getNumber() == getRestChannel().getValue())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -39,13 +39,11 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
private static final int[] BYTE = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] TWO_BYTES = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] SEGMENT_INDICATOR = new int[]{16, 17};
private static final int TIMESLOT = 18;
private static final int RESERVED = 19;
private static final int[] REST_LSN = new int[]{20, 21, 22, 23};
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
private static final int[] LSN_VOICE_BITMAP = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int LSN_1_8_BITMAP_START = 24;
private DMRLogicalChannel mRestChannel;
private DmrRestLsn mRestChannel;
private List<Identifier> mIdentifiers;
/**
@@ -80,7 +78,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
sb.append(" RAS:").append(getBPTCReservedBits());
}
sb.append(" CSBK CAP+ SITE STATUS REST ").append(getRestChannel());
sb.append(" CSBK CAP+ SITE STATUS ").append(getRestChannel());
sb.append(" ").append(getSegmentIndicator());
sb.append(" ");
@@ -277,11 +275,11 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
/**
* Current rest channel for this site.
*/
public DMRLogicalChannel getRestChannel()
public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
@@ -297,31 +295,13 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
return getMessage().getInt(REST_LSN);
}
/**
* Rest Channel Repeater
*/
public int getRestRepeater()
{
return (int) Math.ceil(getRestLSN() / 2.0);
}
/**
* Rest Channel Timeslot
*
* @return 1 or 2
*/
public int getRestTimeslot()
{
return (getRestLSN() % 2 == 0) ? 2 : 1;
}
/**
* Logical slot numbers that require slot to frequency mappings.
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
return getRestChannel().getLSNArray();
return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -334,7 +314,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -40,14 +40,13 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
{
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] REPEATER = new int[]{40, 41, 42, 43};
private static final int[] CHANNEL_GRANT_TIMESLOT = new int[]{44};
private static final int[] REPEATER = new int[]{40, 41, 42, 43, 44};
//Analysis: this field correlates to UNKNOWN_FIELD_1(bits: 40-48) in ConnectPlusTerminateChannelGrant.
private static final int[] UNKNOWN_FIELD = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private RadioIdentifier mTargetRadio;
private DMRLogicalChannel mDMRLogicalChannel;
private DMRLsn mDmrLsn;
private List<Identifier> mIdentifiers;
/**
@@ -117,32 +116,23 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
return getMessage().getInt(REPEATER);
}
/**
* Channel grant timeslot
* @return 1 or 2
*/
public int getChannelGrantTimeslot()
{
return getMessage().getInt(CHANNEL_GRANT_TIMESLOT) + 1;
}
/**
* DMR Channel
*/
public DMRLogicalChannel getChannel()
public DMRLsn getChannel()
{
if(mDMRLogicalChannel == null)
if(mDmrLsn == null)
{
mDMRLogicalChannel = new DMRLogicalChannel(getRepeater(), getChannelGrantTimeslot());
mDmrLsn = new DMRLsn(getRepeater());
}
return mDMRLogicalChannel;
return mDmrLsn;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
return getChannel().getLSNArray();
return getChannel().getLogicalSlotNumbers();
}
/**
@@ -154,7 +144,7 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
if(timeslotFrequency.getNumber() == getChannel().getValue())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -44,11 +44,10 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
private static final int[] MESSAGE_TYPE = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] VERSION = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] UNKNOWN = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63};
private static final int[] DATA_REPEATER = new int[]{64, 65, 66, 67};
private static final int[] DATA_TIMESLOT = new int[]{68};
57, 58, 59, 60, 61, 62};
private static final int[] DATA_REPEATER = new int[]{63, 64, 65, 66, 67};
private DMRLogicalChannel mDataChannel;
private DMRLsn mDataChannel;
private List<Identifier> mIdentifiers;
/**
@@ -107,32 +106,23 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
return getMessage().getInt(DATA_REPEATER);
}
/**
* Data Timeslot
* @return 1 or 2
*/
public int getDataTimeslot()
{
return getMessage().getInt(DATA_TIMESLOT) + 1;
}
/**
* DMR Channel where the data is available
*/
public DMRLogicalChannel getDataChannel()
public DMRLsn getDataChannel()
{
if(mDataChannel == null)
{
mDataChannel = new DMRLogicalChannel(getDataRepeater(), getDataTimeslot());
mDataChannel = new DMRLsn(getDataRepeater());
}
return mDataChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
return getDataChannel().getLSNArray();
return getDataChannel().getLogicalSlotNumbers();
}
/**
@@ -145,7 +135,7 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getDataChannel().getLogicalSlotNumber())
if(timeslotFrequency.getNumber() == getDataChannel().getValue())
{
getDataChannel().setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -24,7 +24,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -44,13 +44,12 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] TRAFFIC_CHANNEL_REPEATER = new int[]{64, 65, 66, 67};
private static final int[] TRAFFIC_CHANNEL_TIMESLOT = new int[]{68};
private static final int[] TRAFFIC_CHANNEL_REPEATER = new int[]{64, 65, 66, 67, 68};
private static final int[] UNKNOWN_FIELD = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private DMRLogicalChannel mDMRLogicalChannel;
private DMRLsn mDmrLsn;
private List<Identifier> mIdentifiers;
/**
@@ -132,32 +131,23 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
return getMessage().getInt(TRAFFIC_CHANNEL_REPEATER);
}
/**
* Traffic channel timeslot
* @return 1 or 2
*/
public int getTrafficChannelTimeslot()
{
return getMessage().getInt(TRAFFIC_CHANNEL_TIMESLOT) + 1;
}
/**
* DMR Channel
*/
public DMRLogicalChannel getChannel()
public DMRLsn getChannel()
{
if(mDMRLogicalChannel == null)
if(mDmrLsn == null)
{
mDMRLogicalChannel = new DMRLogicalChannel(getTrafficChannelRepeater(), getTrafficChannelTimeslot());
mDmrLsn = new DMRLsn(getTrafficChannelRepeater());
}
return mDMRLogicalChannel;
return mDmrLsn;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
return getChannel().getLSNArray();
return getChannel().getLogicalSlotNumbers();
}
/**
@@ -169,7 +159,7 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
if(timeslotFrequency.getNumber() == getChannel().getValue())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -25,7 +25,7 @@ import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -224,11 +224,11 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
if(getMoveToChannel() instanceof DMRLogicalChannel)
if(getMoveToChannel() instanceof DMRLsn dmrLsn)
{
return ((DMRLogicalChannel)getMoveToChannel()).getLSNArray();
return dmrLsn.getLogicalSlotNumbers();
}
return new int[0];
@@ -241,13 +241,13 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getMoveToChannel() instanceof DMRLogicalChannel)
if(getMoveToChannel() instanceof DMRTier3Channel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getMoveToChannel();
DMRTier3Channel channel = (DMRTier3Channel)getMoveToChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
if(channel.getValue() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -23,24 +23,20 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Announcement Message - Adjacent/Neighbor Site Information
*/
public class AdjacentSiteInformation extends Announcement implements ITimeslotFrequencyReceiver
public class AdjacentSiteInformation extends Announcement
{
private static final int NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET = 21;
private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56;
@@ -235,39 +231,4 @@ public class AdjacentSiteInformation extends Announcement implements ITimeslotFr
return mIdentifiers;
}
/**
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
if(getNeighborChannel() instanceof DMRLogicalChannel)
{
return ((DMRLogicalChannel)getNeighborChannel()).getLSNArray();
}
return new int[0];
}
/**
* Applies the timeslot frequency lookup information to channels contained in this message
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getNeighborChannel() instanceof DMRLogicalChannel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getNeighborChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.ArrayList;
import java.util.List;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -33,7 +33,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
@@ -240,11 +239,11 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
if(getChannel() instanceof DMRLogicalChannel)
if(getChannel() instanceof DMRLsn dmrLsn)
{
return ((DMRLogicalChannel)getChannel()).getLSNArray();
return dmrLsn.getLogicalSlotNumbers();
}
return new int[0];
@@ -257,13 +256,13 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getChannel() instanceof DMRLogicalChannel)
if(getChannel() instanceof DMRTier3Channel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
DMRTier3Channel channel = (DMRTier3Channel)getChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
if(channel.getValue() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -32,7 +32,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.List;
/**
@@ -159,11 +158,11 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
if(getChannel() instanceof DMRLogicalChannel)
if(getChannel() instanceof DMRLsn dmrLsn)
{
return ((DMRLogicalChannel)getChannel()).getLSNArray();
return dmrLsn.getLogicalSlotNumbers();
}
return new int[0];
@@ -176,13 +175,13 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getChannel() instanceof DMRLogicalChannel)
if(getChannel() instanceof DMRTier3Channel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
DMRTier3Channel channel = (DMRTier3Channel)getChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
if(channel.getValue() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -25,7 +25,6 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.DefinedDataFormat;
import java.util.ArrayList;
import java.util.List;
@@ -39,6 +38,7 @@ public class DefinedShortDataHeader extends PacketSequenceHeader
private static final int RESYNCHRONIZE_FLAG = 70;
private static final int FULL_MESSAGE_FLAG = 71;
private static final int[] BIT_PADDING = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private static final int[] HEADER_CRC = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95};
private List<Identifier> mIdentifiers;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -34,6 +34,11 @@ public class ProprietaryDataHeader extends DataHeader
{
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] VENDOR_DATA = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private static final int[] HEADER_CRC = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95};
/**
* Constructs an instance.
@@ -62,10 +67,20 @@ public class ProprietaryDataHeader extends DataHeader
sb.append(" PROPRIETARY DATA HEADER");
sb.append(" VENDOR:").append(getVendor());
sb.append(" ").append(getServiceAccessPoint());
sb.append(" VENDOR DATA:").append(getVendorData());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Vendor defined data payload from this proprietary header.
* @return hex values.
*/
public String getVendorData()
{
return getMessage().getHex(VENDOR_DATA, 16);
}
/**
* Optional packet prefix.
* @return packet prefix fragment or null.

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -66,7 +66,8 @@ public class MNISProprietaryDataHeader extends ProprietaryDataHeader
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA MNIS HEADER");
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOTOROLA MNIS HEADER");
if(getApplicationType() == ApplicationType.UNKNOWN)
{
sb.append(" APPLICATION TYPE:0x").append(Integer.toHexString(getApplicationTypeNumber()).toUpperCase());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -31,10 +31,15 @@ import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
/**
* Motorola Proprietary Data Header
*/
public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
public class MotorolaDataEncryptionHeader extends ProprietaryDataHeader
{
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] ALGORITHM = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] KEY_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] UNKNOWN = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] INITIALIZATION_VECTOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
/**
* Constructs an instance.
@@ -46,7 +51,7 @@ public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public MotorolaProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
public MotorolaDataEncryptionHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@@ -55,14 +60,57 @@ public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA PROPRIETARY DATA HEADER");
sb.append(" ").append(getServiceAccessPoint());
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOTOROLA DATA ENCRYPTION HEADER");
sb.append(" SAP:").append(getServiceAccessPoint());
sb.append(" ALGORITHM?:").append(getAlgorithm());
sb.append(" KEY?:").append(getKeyId());
sb.append(" IV?:").append(getInitializationVector());
sb.append(" UNK:").append(getUnknown());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Unknown message field(s).
* @return hex value.
*/
public String getUnknown()
{
return getMessage().getHex(UNKNOWN, 4);
}
/**
* Encryption key ID
* @return key ID
*/
public int getKeyId()
{
return getMessage().getInt(KEY_ID);
}
/**
* Encryption Algorithm
* @return algorithm ID
*/
public int getAlgorithm()
{
return getMessage().getInt(ALGORITHM);
}
/**
* Encryption initialization vector
*
* @return vector in hex
*/
public String getInitializationVector()
{
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
}
/**
* Utility method to lookup the vendor from a CSBK message
*
* @param message containing CSBK bits
* @return vendor
*/

View File

@@ -33,12 +33,14 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasHead
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TerminatorData;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraArc4EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaArc4EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ActivityUpdateMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusControlChannel;
@@ -112,8 +114,8 @@ public class LCMessageFactory
case FULL_STANDARD_TERMINATOR_DATA:
flc = new TerminatorData(message, timestamp, timeslot);
break;
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
flc = new CapacityPlusGroupVoiceChannelUser(message, timestamp, timeslot);
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot);
break;
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
@@ -121,7 +123,35 @@ public class LCMessageFactory
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot);
break;
case FULL_ARC4_ENCRYPTION_PARAMETERS:
boolean isHytera = false;
//This is apparently now a DMR standard FLC opcode, even though it's using Motorola's vendor ID (0x10).
//As observed on a known Hytera system, it's not using the standard FLC RS-12/9/4 check, which fails.
//It's apparently using the CRC-CCITT with 0x9696 initial fill. Not sure if both Hytera and Motorola
// are using this FEC. But, by using only 16 bits for checksum versus the standad 24, they're able to
// include a full 24-bit group/radio ID in the message. For now we'll identify as either Motorola
//(using reed solomon) or Hytera (using crc-ccitt).
if(!valid)
{
int bitErrors = CRCDMR.correctCCITT80(message, 0, 80, 0x9696);
if(bitErrors < 2)
{
valid = true;
isHytera = true;
}
}
if(isHytera)
{
flc = new HyteraArc4EncryptionParameters(message, timestamp, timeslot);
}
else
{
flc = new MotorolaArc4EncryptionParameters(message, timestamp, timeslot);
}
break;
case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER:
flc = new HyteraGroupVoiceChannelUser(message, timestamp, timeslot);
break;

View File

@@ -44,10 +44,12 @@ 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"),
//Observed on Cap+ Multi-Site System during an encrypted voice call
FULL_ARC4_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ARC4/EP ENCRYPTION PARAMETERS"),
//Cap+ opcodes from https://forums.radioreference.com/threads/understanding-capacity-plus-trunking-some-more.452566/
//FLCO 0: Group Call Maintenance
//FLCO 3: Private Call Maintenance (TermLC)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -20,6 +20,7 @@
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
/**
@@ -27,7 +28,7 @@ import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
*
* ETSI TS 102 361-2 7.1.1.2
*/
public abstract class AbstractVoiceChannelUser extends FullLCMessage
public abstract class AbstractVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* 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.dmr.message.data.lc.full.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera ARC4/EP Encryption Parameters
* <p>
* Note: observed as FLC payload for a PI_HEADER slot type.
* Note: observed on a Hytera system that was configured as IP Site Connect compatible.
*/
public class HyteraArc4EncryptionParameters extends FullLCMessage
{
private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79};
//Hytera version uses CRC-CCITT with 0x9696 initial fill, bits: 80-95
private DMRTalkgroup mTalkgroup;
private List<Identifier> mIdentifiers;
/**
* Constructor
* @param message bits
* @param timestamp for the message
* @param timeslot of the message
*/
public HyteraArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
if(isEncrypted())
{
sb.append(" *ENCRYPTED*");
}
if(isReservedBitSet())
{
sb.append(" *RESERVED-BIT*");
}
sb.append("FLC HYTERA ARC4/EP ENCRYPTION PARAMETERS");
sb.append(" KEY:").append(getKeyId());
sb.append(" IV:").append(getInitializationVector());
sb.append(" TALKGROUP:").append(getTalkgroup());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public DMRTalkgroup getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = new DMRTalkgroup(getMessage().getInt(DESTINATION_GROUP));
}
return mTalkgroup;
}
public int getKeyId()
{
return getMessage().getInt(KEY_ID);
}
public String getInitializationVector()
{
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
}
return mIdentifiers;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -22,13 +22,14 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
/**
* Hytera Full Link Control
*/
public abstract class HyteraFullLC extends FullLCMessage
public abstract class HyteraFullLC extends FullLCMessage implements IServiceOptionsProvider
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] FREE_REPEATER = new int[]{24, 25, 26, 27};

View File

@@ -37,7 +37,7 @@ public class CapacityPlusEncryptedVoiceChannelUser extends CapacityPlusVoiceChan
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] UNKNOWN_2 = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] UNKNOWN_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
//Reed Solomon FEC: 72-95
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
@@ -94,10 +94,6 @@ public class CapacityPlusEncryptedVoiceChannelUser extends CapacityPlusVoiceChan
{
return getMessage().getHex(UNKNOWN_2, 2);
}
public String getUnknown3()
{
return getMessage().getHex(UNKNOWN_3, 2);
}
/**
* Source radio address

View File

@@ -1,236 +0,0 @@
/*
* *****************************************************************************
* 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.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Group Voice Channel User
*/
public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
{
private static final int[] CAPACITY_PLUS_GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] CONVENTIONAL_GROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] REST_CHANNEL = new int[]{51, 52, 53, 54};
private static final int[] REST_CHANNEL_TIMESLOT = new int[]{55};
private static final int[] CAPACITY_PLUS_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] CONVENTIONAL_SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] UNKNOWN = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private DMRLogicalChannel mRestChannel;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance.
*
* @param message for the link control payload
*/
public CapacityPlusGroupVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
if(isReservedBitSet())
{
sb.append(" RESERVED-BIT");
}
if(getServiceOptions().isCapacityPlus())
{
sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER");
}
else
{
sb.append("FLC MOTOROLA CONV/IP SITE GROUP VOICE CHANNEL USER");
}
sb.append(" FM:").append(getRadio());
sb.append(" TO:").append(getTalkgroup());
if(hasRestChannel())
{
sb.append(" REST:");
sb.append(getRestChannel());
}
sb.append(" ").append(getServiceOptions());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Unknown 8-bit field
*/
public String getUnknown()
{
return getMessage().getHex(UNKNOWN, 2);
}
/**
* Logical channel number (ie repeater number).
*/
public DMRLogicalChannel getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestChannelRepeater(), getRestChannelTimeslot());
}
return mRestChannel;
}
/**
* Rest repeater number
*/
public int getRestChannelRepeater()
{
return getMessage().getInt(REST_CHANNEL) + 1;
}
/**
* Rest timeslot
*
* @return
*/
public int getRestChannelTimeslot()
{
return getMessage().getInt(REST_CHANNEL_TIMESLOT) + 1;
}
/**
* Indicates if this message has a rest channel indicated for the call
*/
public boolean hasRestChannel()
{
return getServiceOptions().isCapacityPlus() && getRestChannelRepeater() != 0;
}
/**
* Source radio address
*/
public RadioIdentifier getRadio()
{
if(mRadio == null)
{
if(getServiceOptions().isCapacityPlus())
{
mRadio = DMRRadio.createFrom(getMessage().getInt(CAPACITY_PLUS_SOURCE_ADDRESS));
}
else
{
mRadio = DMRRadio.createFrom(getMessage().getInt(CONVENTIONAL_SOURCE_ADDRESS));
}
}
return mRadio;
}
/**
* Talkgroup address
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
if(getServiceOptions().isCapacityPlus())
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CAPACITY_PLUS_GROUP_ADDRESS));
}
else
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CONVENTIONAL_GROUP_ADDRESS));
}
}
return mTalkgroup;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
if(hasRestChannel())
{
mIdentifiers.add(getRestChannel());
}
}
return mIdentifiers;
}
/**
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
return getRestChannel().getLSNArray();
}
/**
* Applies the LSN to frequency map to the rest channel.
*
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
}
}
}

View File

@@ -20,15 +20,17 @@
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.type.CapacityPlusServiceOptions;
/**
* Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options.
*/
public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage
public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
//Reed Solomon FEC: 72-95
private CapacityPlusServiceOptions mServiceOptions;

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -32,21 +32,23 @@ import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Wide Area (Multi-Site) Voice Channel User
* Motorola Capacity Plus - Wide Area (Linked or Multi-Site) Voice Channel User
*
* Note: in Linked Capacity Plus talkgroups range 1-255 and radio IDs range 1-65535
*/
public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
{
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
//private static final int[] REST_REPEATER = new int[]{51, 52, 53, 54};
//private static final int[] REST_TIMESLOT = new int[]{55};
private static final int[] REST_LSN = new int[]{52, 53, 54, 55};
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] UNKNOWN_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
//Bits 16-23: Service Options
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] GROUP_ID = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] UNUSED = new int[]{48, 49, 50};
private static final int[] REST_LSN = new int[]{51, 52, 53, 54, 55};
private static final int[] RADIO_ID = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
//Reed Solomon FEC: 72-95
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private DMRLogicalChannel mRestChannel;
private DmrRestLsn mRestChannel;
private List<Identifier> mIdentifiers;
/**
@@ -82,18 +84,12 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:");
sb.append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" REST:");
if(hasRestChannel())
{
sb.append(getRestChannel());
sb.append(" ").append(getRestChannel());
}
else
{
sb.append("--");
}
sb.append(" UNK1:").append(getUnknown1());
sb.append(" UNK2:").append(getUnknown2());
sb.append(" ").append(getServiceOptions());
sb.append(" UNK1:").append(getUnknown1());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@@ -101,27 +97,19 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
/**
* Unknown1 8-bit field
*/
public int getUnknown1()
public String getUnknown1()
{
return getMessage().getInt(UNKNOWN_1);
}
/**
* Unknown2 8-bit field
*/
public int getUnknown2()
{
return getMessage().getInt(UNKNOWN_3);
return getMessage().getHex(UNKNOWN_1, 4);
}
/**
* Logical channel number (ie repeater number).
*/
public DMRLogicalChannel getRestChannel()
public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
@@ -136,25 +124,12 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
return getMessage().getInt(REST_LSN);
}
/**
* Rest channel timeslot
*/
public int getRestTimeslot()
{
return (getRestLSN() % 2 == 0) ? 2 : 1;
}
/**
* Rest channel repeater number
*/
public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
/**
* Indicates if this message has a reset channel defined.
*/
public boolean hasRestChannel()
{
return getRestRepeater() != 0;
return getRestLSN() != 0;
}
/**
@@ -164,7 +139,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
{
if(mRadio == null)
{
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO_ID));
}
return mRadio;
@@ -177,7 +152,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ID));
}
return mTalkgroup;
@@ -201,9 +176,9 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
return getRestChannel().getLSNArray();
return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -216,7 +191,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -0,0 +1,116 @@
/*
* *****************************************************************************
* 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.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola ARC4/EP Encryption Parameters
* <p>
* Note: observed as FLC payload for a PI_HEADER slot type.
* Note: observed on a possible Hytera (clone) system that was configured as IP Site Connect compatible.
*/
public class MotorolaArc4EncryptionParameters extends FullLCMessage
{
private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
//Motorola Reed Solomon FEC bits: 72-95
private DMRTalkgroup mTalkgroup;
private List<Identifier> mIdentifiers;
/**
* Constructor
* @param message bits
* @param timestamp for the message
* @param timeslot of the message
*/
public MotorolaArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
if(isEncrypted())
{
sb.append(" *ENCRYPTED*");
}
if(isReservedBitSet())
{
sb.append(" *RESERVED-BIT*");
}
sb.append("FLC MOTOROLA ARC4/EP ENCRYPTION PARAMETERS");
sb.append(" KEY:").append(getKeyId());
sb.append(" IV:").append(getInitializationVector());
sb.append(" TALKGROUP:").append(getTalkgroup());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public DMRTalkgroup getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = new DMRTalkgroup(getMessage().getInt(DESTINATION_GROUP));
}
return mTalkgroup;
}
public int getKeyId()
{
return getMessage().getInt(KEY_ID);
}
public String getInitializationVector()
{
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,122 @@
/*
* *****************************************************************************
* 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.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Group Voice Channel User
*/
public class MotorolaGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser
{
private static final int[] GROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
//Reed Solomon FEC: 72-95
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance.
*
* @param message for the link control payload
*/
public MotorolaGroupVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
if(isReservedBitSet())
{
sb.append(" RESERVED-BIT");
}
sb.append("FLC MOTOROLA GROUP VOICE CHANNEL USER");
sb.append(" FM:").append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" ").append(getServiceOptions());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Source radio address
*/
public RadioIdentifier getRadio()
{
if(mRadio == null)
{
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mRadio;
}
/**
* Talkgroup address
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
}
return mTalkgroup;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
}
return mIdentifiers;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.message.type.Activity;
import java.util.Collections;
import java.util.List;
@@ -55,13 +54,18 @@ public class ActivityUpdateMessage extends ShortLCMessage
{
sb.append("[CRC ERROR] ");
}
sb.append("SLC ACTIVITY UPDATE TS1 [");
sb.append(getHashAddressTS1()).append("] ");
sb.append("SLC TS1:");
sb.append(getActivityTS1());
sb.append(" / TS2 [");
sb.append(getHashAddressTS2()).append("] ");
if(getActivityTS1() != Activity.IDLE)
{
sb.append(" [").append(getHashAddressTS1()).append("]");
}
sb.append(" TS2:");
sb.append(getActivityTS2());
if(getActivityTS2() != Activity.IDLE)
{
sb.append(" [").append(getHashAddressTS2()).append("]");
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}

View File

@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
@@ -34,13 +34,11 @@ import java.util.List;
public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslotFrequencyReceiver
{
private static final int[] UNKNOWN = new int[]{12, 13, 14};
//private static final int[] REST_REPEATER = new int[]{15, 16, 17, 18};
private static final int[] REST_LSN = new int[]{15, 16, 17, 18, 19};
//private static final int[] REST_TIMESLOT = new int[]{19};
private static final int[] SITE = new int[]{20, 21, 22, 23, 24};
private static final int[] UNKNOWN_2 = new int[]{25, 26, 27};
private DMRLogicalChannel mRestChannel;
private DmrRestLsn mRestChannel;
private DMRSite mSite;
private List<Identifier> mIdentifiers;
@@ -63,7 +61,7 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
sb.append("[CRC ERROR] ");
}
sb.append("SLC MOTOROLA CAP+ SITE:").append(getSite());
sb.append(" REST:").append(getRestChannel());
sb.append(" ").append(getRestChannel());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@@ -84,11 +82,11 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
/**
* Rest Channel Number
*/
public DMRLogicalChannel getRestChannel()
public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
@@ -103,26 +101,13 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
return getMessage().getInt(REST_LSN);
}
/**
* Rest repeater
*/
public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
/**
* Rest timeslot
*/
public int getRestTimeslot()
{
return (getRestLSN() % 2 == 0) ? 2 : 1;
}
/**
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalSlotNumbers()
{
return getRestChannel().getLSNArray();
return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -134,7 +119,7 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import java.util.Collections;
import java.util.List;
@@ -48,8 +47,7 @@ public class NullMessage extends ShortLCMessage
{
sb.append("[CRC ERROR] ");
}
sb.append("SLC IDLE/NULL MESSAGE");
sb.append(" MSG:").append(getMessage().toHexString());
sb.append("SLC TS1:IDLE TS2:IDLE");
return sb.toString();
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.ip.IPacket;
import java.util.ArrayList;
import java.util.List;
@@ -75,6 +74,10 @@ public class DMRPacketMessage extends DMRMessage
sb.append("CC:").append(getPacketSequence().getPacketSequenceHeader().getSlotType().getColorCode());
sb.append(" FM:").append(getPacketSequence().getPacketSequenceHeader().getSourceLLID());
sb.append(" TO:").append(getPacketSequence().getPacketSequenceHeader().getDestinationLLID());
if(getPacketSequence().isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" ").append(getPacket().toString());
return sb.toString();
}

View File

@@ -24,6 +24,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
import java.util.ArrayList;
import java.util.List;
@@ -89,6 +90,15 @@ public class PacketSequence
return false;
}
/**
* Indicates if this sequence contains a Motorola data encryption header.
* @return true if so
*/
public boolean isEncrypted()
{
return hasProprietaryDataHeader() && getProprietaryDataHeader() instanceof MotorolaDataEncryptionHeader;
}
public int getTimeslot()
{
return mTimeslot;

View File

@@ -29,7 +29,10 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataH
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
import io.github.dsheirer.module.decode.ip.DefinedShortDataPacket;
import io.github.dsheirer.module.decode.ip.UnknownPacket;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
@@ -179,9 +182,20 @@ public class PacketSequenceMessageFactory
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
}
else if(secondaryHeader instanceof MotorolaDataEncryptionHeader &&
packetSequence.getPacketSequenceHeader().getDataPacketFormat() == DataPacketFormat.DEFINED_SHORT_DATA)
{
return createDefinedShortData(packetSequence, packet);
}
else
{
mLog.info("Unknown Proprietary Packet Header Type - creating unknown packet.");
if(packetSequence.getProprietaryDataHeader() != null)
{
mLog.info("Unknown Proprietary Packet Header Type - creating unknown packet. Data Packet Format: " +
packetSequence.getPacketSequenceHeader().getDataPacketFormat() + " Proprietary Header: " +
packetSequence.getProprietaryDataHeader().getClass());
}
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
@@ -217,8 +231,7 @@ public class PacketSequenceMessageFactory
*/
public static IMessage createDefinedShortData(PacketSequence packetSequence, CorrectedBinaryMessage packet)
{
mLog.info("Unknown Short Data Packet Header Type - creating unknown packet.");
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
return new DMRPacketMessage(packetSequence, new DefinedShortDataPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}

View File

@@ -83,7 +83,7 @@ public class CapacityPlusServiceOptions extends ServiceOptions
if(isInterruptible())
{
flags.add("TXI CALL");
flags.add("INTERRUPTIBLE CALL"); //Indicates another user can interrupt this call
}
if(getPriority() > 0)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -21,18 +21,16 @@ package io.github.dsheirer.module.decode.dmr.message.voice;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
/**
* DMR Voice Frame B-F Embedded Signalling Chunk
*/
public class EMB
{
private final static Logger mLog = LoggerFactory.getLogger(EMB.class);
public static final int[] CRC_CHECKSUMS = new int[]{0x02F, 0x11E, 0x1B7, 0x1E2, 0x1C9, 0x0E5, 0x073};
private static final int[] VALID_WORDS = new int[]{
0x0000, 0x0273, 0x04E5, 0x0696, 0x09C9, 0x0BBA, 0x0D2C, 0x0F5F, 0x11E2, 0x1391, 0x1507, 0x1774, 0x182B, 0x1A58,
0x1CCE, 0x1EBD, 0x21B7, 0x23C4, 0x2552, 0x2721, 0x287E, 0x2A0D, 0x2C9B, 0x2EE8, 0x3055, 0x3226, 0x34B0, 0x36C3,

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
/**
* DMR Voice Frames B - F
@@ -35,6 +36,7 @@ public class VoiceEMBMessage extends VoiceMessage
private static final int PAYLOAD_END = 172;
private EMB mEMB;
private EmbeddedParameters mEmbeddedParameters;
/**
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
@@ -61,7 +63,11 @@ public class VoiceEMBMessage extends VoiceMessage
sb.append(getSyncPattern().toString());
if(getEMB().isValid() && getEMB().isEncrypted())
if(hasEmbeddedParameters())
{
sb.append(" ").append(getEmbeddedParameters());
}
else if(getEMB().isValid() && getEMB().isEncrypted())
{
sb.append(" ENCRYPTED");
}
@@ -97,4 +103,34 @@ public class VoiceEMBMessage extends VoiceMessage
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
/**
* Optional embedded parameters for this voice super-frame.
* @return encryption parameters or null.
*/
public EmbeddedParameters getEmbeddedParameters()
{
return mEmbeddedParameters;
}
/**
* Sets the embedded parameters for this voice message that apply to the entire voice super-frame.
*
* These parameters are normally extracted by an external process and applied to voice frame F and the parameters
* apply to the entire voice super-frame.
* @param embeddedParameters to set
*/
public void setEmbeddedParameters(EmbeddedParameters embeddedParameters)
{
mEmbeddedParameters = embeddedParameters;
}
/**
* Indicates if this voice message contains embedded parameters.
* @return true if it contains.
*/
public boolean hasEmbeddedParameters()
{
return mEmbeddedParameters != null;
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* 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.dmr.message.voice;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
@@ -5,7 +24,6 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -15,6 +33,14 @@ import java.util.List;
*/
public abstract class VoiceMessage extends DMRBurst
{
/**
* Initialization vector (IV) fragments can be embedded into the AMBE voice frame for late entry to encrypted calls
* using bits 71, 67, 63, and 59 from the interleaved (ie transmitted order) voice frame.
*/
public static int[] FRAME_1_IV_FRAGMENT = new int[]{95, 91, 87, 83};
public static int[] FRAME_2_IV_FRAGMENT = new int[]{215, 211, 207, 203};
public static int[] FRAME_3_IV_FRAGMENT = new int[]{287, 283, 279, 275};
/**
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
* station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames
@@ -65,6 +91,24 @@ public abstract class VoiceMessage extends DMRBurst
return frames;
}
/**
* Extracts the four low-order bits from the deinterleaved C3 vector of each of the three AMBE frames that carry
* fragments of the initialization vector (IV) for encrypted calls. Since we don't have access to the deinterleaved
* voice frame here, we perform delinterleaving of the 4-bit nibble using the FRAME_x_IV_FRAGMENT constants.
* <p>
* See patent: https://patents.google.com/patent/EP2347540B1/en
*
* @return a three-byte array with the low-order four bits from each frame's C3 vector stored in the low nibble.
*/
public byte[] getIvFragments()
{
byte[] fragments = new byte[3];
fragments[0] = (byte)getMessage().getInt(FRAME_1_IV_FRAGMENT);
fragments[1] = (byte)getMessage().getInt(FRAME_2_IV_FRAGMENT);
fragments[2] = (byte)getMessage().getInt(FRAME_3_IV_FRAGMENT);
return fragments;
}
@Override
public boolean isValid()
{

View File

@@ -0,0 +1,282 @@
/*
* *****************************************************************************
* 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.dmr.message.voice;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.BPTC_16_2;
import io.github.dsheirer.edac.Golay24;
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
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.dmr.message.voice.embedded.NonStandardShortBurst;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NullShortBurst;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurst;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurstOpcode;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.TransmitInterrupt;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.UnknownShortBurst;
/**
* Monitors audio call voice frame messaging to detect encrypted audio calls and extract the encryption parameters
* that are embedded into the six voice frames that comprise a voice super-frame.
* <p>
* See patent: https://patents.google.com/patent/EP2347540B1/en - embedding encryption parameters in voice super frame
* See patent: https://patents.google.com/patent/US8271009B2 - TXI - interrupting voice transmissions
*/
public class VoiceSuperFrameProcessor
{
private static final int[] IV = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] CRC4 = new int[]{56, 57, 58, 59};
private boolean mEncrypted = false;
private int mAlgorithm;
private int mKey;
private byte[] mFragmentA;
private byte[] mFragmentB;
private byte[] mFragmentC;
private byte[] mFragmentD;
private byte[] mFragmentE;
/**
* Constructor.
*/
public VoiceSuperFrameProcessor()
{
}
/**
* Indicates if this collector is in collecting mode.
*
* @return true if collecting.
*/
public boolean isCollecting()
{
return mEncrypted;
}
/**
* Fully resets this collector
*/
public void reset()
{
mEncrypted = false;
softReset();
}
private void softReset()
{
mAlgorithm = 0;
mKey = 0;
mFragmentA = null;
mFragmentB = null;
mFragmentC = null;
mFragmentD = null;
mFragmentE = null;
}
/**
* Indicates if the IV fragments from voice frames A-E have been collected and this is an encrypted call.
*
* @return true if the fragments are non-null.
*/
private boolean isComplete()
{
return mEncrypted && mFragmentA != null && mFragmentB != null && mFragmentC != null && mFragmentD != null &&
mFragmentE != null;
}
/**
* Process full link control message to determine if the current call sequence is encrypted.
* @param flc to inspect
*/
public void process(FullLCMessage flc)
{
if(flc instanceof IServiceOptionsProvider provider && provider.getServiceOptions().isEncrypted())
{
mEncrypted = true;
}
}
/**
* Processes the voice frame to extract the IV fragments and the encryption parameters.
*
* @param voiceMessage to process.
*/
public void process(VoiceMessage voiceMessage)
{
if(voiceMessage instanceof VoiceEMBMessage voiceEMB)
{
boolean valid = voiceEMB.getEMB().isValid();
boolean encrypted = voiceEMB.getEMB().isEncrypted();
if(voiceEMB.getEMB().isValid() && voiceEMB.getEMB().isEncrypted())
{
mEncrypted = true;
}
}
switch(voiceMessage.getSyncPattern())
{
case BASE_STATION_VOICE:
mFragmentA = voiceMessage.getIvFragments();
break;
case BS_VOICE_FRAME_B:
mFragmentB = voiceMessage.getIvFragments();
break;
case BS_VOICE_FRAME_C:
mFragmentC = voiceMessage.getIvFragments();
break;
case BS_VOICE_FRAME_D:
mFragmentD = voiceMessage.getIvFragments();
break;
case BS_VOICE_FRAME_E:
mFragmentE = voiceMessage.getIvFragments();
break;
case BS_VOICE_FRAME_F:
if(voiceMessage instanceof VoiceEMBMessage voiceFrame6)
{
BinaryMessage frameFFragment = voiceFrame6.getFLCFragment();
ShortBurst shortBurst = extractShortBurst(frameFFragment);
EmbeddedParameters embeddedParameters = new EmbeddedParameters(shortBurst);
if(isComplete())
{
String iv = extractIV(voiceMessage.getIvFragments());
embeddedParameters.setIv(iv);
}
voiceFrame6.setEmbeddedParameters(embeddedParameters);
}
softReset();
break;
}
}
/**
* Processes the short burst from the Voice Frame F FLC fragment payload and combines with the optionally available
* initialization vector to return a parameters object.
*
* @param frameFFragment containing the short-burst FLC fragment
* @param iv that was previously extracted from the voice super-frame
* @return embedded parameters.
*/
private ShortBurst extractShortBurst(BinaryMessage frameFFragment)
{
CorrectedBinaryMessage decoded = BPTC_16_2.decodeShortBurst(new CorrectedBinaryMessage(frameFFragment));
if(decoded == null)
{
return new NonStandardShortBurst(BPTC_16_2.deinterleave(new CorrectedBinaryMessage(frameFFragment)));
}
ShortBurstOpcode opcode = ShortBurst.getOpcode(decoded);
return switch(opcode)
{
case NULL -> new NullShortBurst(decoded);
case ARC4_ENCRYPTION -> new Arc4EncryptionParameters(decoded);
case TXI_DELAY -> new TransmitInterrupt(decoded);
default -> new UnknownShortBurst(decoded);
};
}
/**
* Extracts the encryption initialization vector (IV) from the voice frame fragments, performs error detection and
* correction and if the error correction passes, returns the hex string version of the 32-bit IV.
*
* @param mFragmentF voice frame 6 IV fragments.
* @return 32-bit IV as hex string or null if the extraction process couldn't extract/correct the IV.
*/
private String extractIV(byte[] mFragmentF)
{
CorrectedBinaryMessage reassembled = new CorrectedBinaryMessage(72);
reassembled.setByte(0, combine(mFragmentA[0], mFragmentB[0]));
reassembled.setByte(8, combine(mFragmentC[0], mFragmentD[0]));
reassembled.setByte(16, combine(mFragmentE[0], mFragmentF[0]));
reassembled.setByte(24, combine(mFragmentA[1], mFragmentB[1]));
reassembled.setByte(32, combine(mFragmentC[1], mFragmentD[1]));
reassembled.setByte(40, combine(mFragmentE[1], mFragmentF[1]));
reassembled.setByte(48, combine(mFragmentA[2], mFragmentB[2]));
reassembled.setByte(56, combine(mFragmentC[2], mFragmentD[2]));
reassembled.setByte(64, combine(mFragmentE[2], mFragmentF[2]));
int check1 = Golay24.checkAndCorrect(reassembled, 0);
int check2 = Golay24.checkAndCorrect(reassembled, 24);
int check3 = Golay24.checkAndCorrect(reassembled, 48);
if(check1 == 2 || check2 == 2 || check3 == 2)
{
return null;
}
int iv = reassembled.getInt(IV);
int checksum = reassembled.getInt(CRC4);
boolean passes = crc4(iv, checksum);
if(passes)
{
return String.format("%04X", iv);
}
else
{
return String.format("%04X", iv) + "(CRC-FAIL " + check1 + "/" + check2 + "/" + check3 + "/" +
reassembled.getCorrectedBitCount() + ")";
}
}
/**
* Combines the low nibble from the hi byte with the low nibble from the lo byte
*
* @param high byte containing a low order nibble
* @param low byte containing a low order nibble
* @return low order nibbles from hi and lo bytes combined.
*/
private byte combine(byte high, byte low)
{
return (byte)(((high & 0xF) << 4) | (low & 0xF));
}
/**
* Calculates a CRC value from the polynomial: x4 + x1 + 1 (0x13) using an initial fill of 0xF.
* @param value to calculate CRC from
* @param crc to compare
* @return true if the calculated CRC from the value matches the crc argument value.
*/
public static boolean crc4(int value, int crc)
{
long checksum = (value & 0x0FFFFFFFFl) << 4;
checksum ^= 0xF; //Initial fill
long polynomial = 0x013l << 31;
long checkBit = 0x1l << 35;
for(int x = 31; x >= 0; x--)
{
if((checksum & checkBit) == checkBit)
{
checksum ^= polynomial;
}
polynomial >>= 1;
checkBit >>= 1;
}
return (int)checksum == crc;
}
}

View File

@@ -0,0 +1,80 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Encryption parameters short burst payload.
*/
public class Arc4EncryptionParameters extends ShortBurst
{
private static final int[] KEY = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] ALGORITHM = new int[]{8, 9, 10};
/**
* Constructor
*
* @param message containing the de-interleaved and error-corrected short burst payload.
*/
public Arc4EncryptionParameters(CorrectedBinaryMessage message)
{
super(message);
}
/**
* Encryption key ID
* @return key ID
*/
public int getKey()
{
return getMessage().getInt(KEY);
}
/**
* Encryption algorithm
* @return algorithm (0 - 7). 1 = ARC4
*/
public int getAlgorithmValue()
{
return getMessage().getInt(ALGORITHM);
}
public String getAlgorithm()
{
int algorithm = getAlgorithmValue();
if(algorithm == 1)
{
return "EP/ARC4";
}
return "ALGORITHM:" + algorithm;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("ENCRYPTION:").append(getAlgorithm());
sb.append(" KEY:").append(getKey());
return sb.toString();
}
}

View File

@@ -0,0 +1,90 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
/**
* Parameters that are embedded in a DMR voice super frame.
* @param algorithm identifier
* @param key identifier
* @param iv initialization vector
*/
public class EmbeddedParameters
{
private ShortBurst mShortBurst;
private String mIv;
/**
* Constructor
* @param shortBurst payload
* @param iv initialization vector extracted from DMR voice super-frame
*/
public EmbeddedParameters(ShortBurst shortBurst)
{
mShortBurst = shortBurst;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getShortBurst());
if(hasIv())
{
sb.append(" IV:").append(getIv());
}
return sb.toString();
}
/**
* Short Burst payload message
* @return short burst
*/
public ShortBurst getShortBurst()
{
return mShortBurst;
}
/**
* Optional initialization vector (IV) decoded from the voice super-frame
* @return iv
*/
public String getIv()
{
return mIv;
}
/**
* Sets the optional IV value.
* @param iv to set
*/
public void setIv(String iv)
{
mIv = iv;
}
/**
* Indicates if the optional IV is included.
* @return true if included
*/
public boolean hasIv()
{
return mIv != null;
}
}

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Short burst payload that doesn't pass the BPTC 16 fec check.
*/
public class NonStandardShortBurst extends ShortBurst
{
/**
* Constructor
*
* @param message containing the delinterleaved and error-corrected short burst payload.
*/
public NonStandardShortBurst(CorrectedBinaryMessage message)
{
super(message);
}
@Override
public String toString()
{
return "NON-STANDARD SHORT BURST:" + getMessage().toHexString();
}
}

View File

@@ -0,0 +1,45 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Null (empty) short burst
*/
public class NullShortBurst extends ShortBurst
{
/**
* Constructor
*
* @param message containing the delinterleaved and error-corrected short burst payload.
*/
public NullShortBurst(CorrectedBinaryMessage message)
{
super(message);
setValid(passesCRC3());
}
@Override
public String toString()
{
return "NULL SHORT BURST";
}
}

View File

@@ -0,0 +1,134 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Base class for DMR Voice Frame F Short Burst from the EMB payload.
*/
public abstract class ShortBurst
{
private static final int[] CRC3 = new int[]{0, 1, 2};
private static final int[] OPCODE = new int[]{8, 9, 10};
private static final int[] FULL_MESSAGE = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
private CorrectedBinaryMessage mMessage;
private boolean mValid = true;
/**
* Constructor
*
* @param message containing the delinterleaved and error-corrected short burst payload.
*/
public ShortBurst(CorrectedBinaryMessage message)
{
mMessage = message;
}
/**
* Access the underlying message.
*
* @return message
*/
public CorrectedBinaryMessage getMessage()
{
return mMessage;
}
/**
* Indicates if this message passes any CRC checks.
* @return
*/
public boolean isValid()
{
return mValid;
}
/**
* Sets the valid CRC flag for this message
* @param valid true or false
*/
protected void setValid(boolean valid)
{
mValid = valid;
}
/**
* Numeric opcode value.
*
* @return value.
*/
public int getOpcodeValue()
{
return getMessage().getInt(OPCODE);
}
/**
* Opcode for this message.
*
* @return opcode
*/
public ShortBurstOpcode getOpcode()
{
return getOpcode(getMessage());
}
/**
* Static utility method to lookup the opcode from a short burst message.
*
* @param message containing a short burst
* @return opcode
*/
public static ShortBurstOpcode getOpcode(BinaryMessage message)
{
return ShortBurstOpcode.fromValue(message.getInt(OPCODE));
}
/**
* Checks the message to determine if it passes for a CRC3. Not all Short Burst messages use the CRC3 check.
*
* @return true if it passes.
*/
public boolean passesCRC3()
{
int checksum = getMessage().getInt(FULL_MESSAGE);
int polynomial = 0xB << 7;
int checkBit = 0x1 << 10;
for(int x = 10; x >= 0; x--)
{
if(checksum == 0)
{
return true;
}
if((checksum & checkBit) == checkBit)
{
checksum ^= polynomial;
}
polynomial >>= 1;
checkBit >>= 1;
}
return checksum == 0;
}
}

View File

@@ -0,0 +1,71 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
/**
* Short Burst opcode
*
* See: https://patents.google.com/patent/US8271009B2 Page 12
*/
public enum ShortBurstOpcode
{
NULL(0),
ARC4_ENCRYPTION(1),
TXI_DELAY(3),
UNKNOWN(-1);
private int mValue;
/**
* Constructor
* @param value of the opcode
*/
ShortBurstOpcode(int value)
{
mValue = value;
}
/**
* Numeric value for the opcode
* @return value.
*/
private int getValue()
{
return mValue;
}
/**
* Lookup the enum entry from the specified value.
* @param value to lookup
* @return matching entry or UNKNOWN.
*/
public static ShortBurstOpcode fromValue(int value)
{
for(ShortBurstOpcode opcode: ShortBurstOpcode.values())
{
if(opcode.getValue() == value)
{
return opcode;
}
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,74 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Transmit Interrupt short burst
* <p>
* See: https://patents.google.com/patent/US8271009B2
*/
public class TransmitInterrupt extends ShortBurst
{
private static final int[] DELAY = new int[]{3, 4, 5, 6, 7};
/**
* Constructor
*
* @param message containing the delinterleaved and error-corrected short burst payload.
*/
public TransmitInterrupt(CorrectedBinaryMessage message)
{
super(message);
setValid(passesCRC3());
}
@Override
public String toString()
{
return "TRANSMIT INTERRUPT (TXI) AT " + getDelay();
}
/**
* Delay to when another radio can interrupt the current call.
* @return delay string.
*/
public String getDelay()
{
int value = getMessage().getInt(DELAY);
switch(value)
{
case 0:
return "ANY TIME";
case 2:
return "FRAME E";
case 4:
return "FRAME D";
case 6:
return "FRAME C";
case 8:
return "FRAME B";
default:
return "UNKNOWN(" + value + ")";
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* 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.dmr.message.voice.embedded;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Unknown or garbage short burst
*/
public class UnknownShortBurst extends ShortBurst
{
/**
* Constructor
*
* @param message containing the delinterleaved and error-corrected short burst payload.
*/
public UnknownShortBurst(CorrectedBinaryMessage message)
{
super(message);
}
@Override
public String toString()
{
return "UNKNOWN SHORT BURST:" + getMessage().toHexString();
}
}

View File

@@ -56,12 +56,21 @@ public abstract class ClearableHistoryModel<T> extends AbstractTableModel
*/
public void add(T item)
{
mItems.addFirst(item);
ClearableHistoryModel.this.fireTableRowsInserted(0, 0);
while(mItems.size() > mHistorySize)
if(mItems.contains(item))
{
mItems.removeLast();
super.fireTableRowsDeleted(mItems.size() - 1, mItems.size() - 1);
int itemRow = mItems.indexOf(item);
fireTableRowsUpdated(itemRow, itemRow);
}
else
{
mItems.addFirst(item);
fireTableRowsInserted(0, 0);
while(mItems.size() > mHistorySize)
{
mItems.removeLast();
fireTableRowsDeleted(mItems.size() - 1, mItems.size() - 1);
}
}
}
@@ -100,14 +109,6 @@ public abstract class ClearableHistoryModel<T> extends AbstractTableModel
return mHistorySize;
}
/**
* Resets the history size to the default value.
*/
public void resetHistorySize()
{
setHistorySize(DEFAULT_HISTORY_SIZE);
}
/**
* Sets the history size
* @param historySize

View File

@@ -87,7 +87,7 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
{
MyEventBus.getGlobalEventBus().register(this);
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][grow,fill]"));
mIconModel = iconModel;
mAliasModel = aliasModel;
mUserPreferences = userPreferences;

View File

@@ -0,0 +1,98 @@
/*
* *****************************************************************************
* 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;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import java.util.Collections;
import java.util.List;
/**
* Defined Short Data Packet
*/
public class DefinedShortDataPacket implements IPacket
{
private BinaryMessage mMessage;
private int mOffset;
/**
* Constructor
* @param message of the complete packet
* @param offset into the message
*/
public DefinedShortDataPacket(BinaryMessage message, int offset)
{
mMessage = message;
mOffset = offset;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("DEFINED SHORT DATA PACKET:");
if(getMessage().size() > getOffset())
{
sb.append(mMessage.getSubMessage(getOffset(), getMessage().size()).toHexString());
}
else
{
sb.append("(EMPTY)");
}
return sb.toString();
}
public BinaryMessage getMessage()
{
return mMessage;
}
public int getOffset()
{
return mOffset;
}
@Override
public IHeader getHeader()
{
return null;
}
@Override
public IPacket getPayload()
{
return null;
}
@Override
public boolean hasPayload()
{
return false;
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -324,7 +324,7 @@ public enum Opcode
public static List<Opcode> getUngrouped()
{
List<Opcode> ungrouped = new ArrayList<>();
for(Opcode opcode: ungrouped)
for(Opcode opcode: Opcode.values())
{
if(!opcode.isGrouped())
{

View File

@@ -45,6 +45,13 @@ import io.github.dsheirer.source.tuner.sdrplay.api.device.DeviceInfo;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.DiscoveredRspDuoTuner1;
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
import io.github.dsheirer.util.ThreadPool;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Context;
@@ -55,14 +62,6 @@ import org.usb4java.HotplugCallback;
import org.usb4java.HotplugCallbackHandle;
import org.usb4java.LibUsb;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Tuner manager provides access to tuners using USB, recording, sound-card and system-daemon accessible devices. This
* manager also supports hot-plug detection and black-listing of discovered tuners so that they can be used with other
@@ -187,10 +186,10 @@ public class TunerManager implements IDiscoveredTunerStatusListener
DeviceList deviceList = new DeviceList();
int deviceCount = LibUsb.getDeviceList(mLibUsbApplicationContext, deviceList);
mLog.info("LibUsb - discovered [" + deviceCount + "] potential usb devices");
if(deviceCount >= 0)
{
mLog.info("LibUsb - discovered [" + deviceCount + "] potential usb devices");
for(Device device: deviceList)
{
int bus = LibUsb.getBusNumber(device);
@@ -228,9 +227,40 @@ public class TunerManager implements IDiscoveredTunerStatusListener
//Unref the device - it will be rediscovered under the device context when it is started
LibUsb.unrefDevice(device);
}
}
LibUsb.freeDeviceList(deviceList, false);
LibUsb.freeDeviceList(deviceList, false);
}
//If the get device list operation generated error -99, turn on warning logging and try device list
// again to see what's causing it.
else if(deviceCount == LibUsb.ERROR_OTHER)
{
try
{
mLog.info("LibUsb reported error -99 during get device list - increasing logging level and retrying");
LibUsb.setOption(mLibUsbApplicationContext, LibUsb.OPTION_LOG_LEVEL, LibUsb.LOG_LEVEL_WARNING);
deviceCount = LibUsb.getDeviceList(mLibUsbApplicationContext, deviceList);
if(deviceCount >=0)
{
mLog.info("LibUsb - get device list count [" + deviceCount + "] during retry");
LibUsb.freeDeviceList(deviceList, false);
}
else
{
mLog.info("LibUsb - get device list reported error [" + deviceCount + "/" +
LibUsb.errorName(deviceCount) + "] during retry");
}
LibUsb.setOption(mLibUsbApplicationContext, LibUsb.OPTION_LOG_LEVEL, LibUsb.LOG_LEVEL_NONE);
mLog.info("LibUse - get device list reattempt complete - if additional logging was generated please notify developer");
}
catch(Exception e)
{
mLog.info("LibUsb error during get device list reattempt", e);
}
}
else
{
mLog.error("LibUsb - error [" + deviceCount + "/" + LibUsb.errorName(deviceCount) + "] during get device list");
}
}
catch(Exception e)
{