P25 Work-in-progress check-in.

This commit is contained in:
dennis.sheirer
2014-11-10 09:53:03 +00:00
parent 7b89c614e7
commit 46e3594c07
31 changed files with 2271 additions and 227 deletions

View File

@@ -0,0 +1,19 @@
package decode.p25;
public enum FrameSync
{
P25_PHASE1( 0x5575F5FF77FFl ),
P25_PHASE1_INVERTED( 0xFFDF5F55DD55l );
private long mSync;
private FrameSync( long sync )
{
mSync = sync;
}
public long getSync()
{
return mSync;
}
}

View File

@@ -20,8 +20,12 @@ package decode.p25;
import instrument.Instrumentable;
import instrument.tap.Tap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
@@ -29,19 +33,26 @@ import decode.Decoder;
import decode.DecoderType;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.FilterFactory;
import dsp.filter.FloatFIRFilter;
import dsp.filter.Window.WindowType;
import dsp.fsk.C4FMDecoder;
import dsp.fsk.P25MessageFramer;
import dsp.fsk.C4FMSlicer;
import dsp.fsk.C4FMSymbolFilter;
import dsp.nbfm.FMDiscriminator;
public class P25Decoder extends Decoder implements Instrumentable
{
private final static Logger mLog = LoggerFactory.getLogger( P25Decoder.class );
private ComplexFIRFilter mBasebandFilter = new ComplexFIRFilter(
FilterFactory.getLowPass( 48000, 6250, 73, WindowType.HAMMING ), 1.0 );
private FMDiscriminator mFMDiscriminator = new FMDiscriminator( 1 );
// private FloatFIRFilter mAudioFilter = new FloatFIRFilter( FilterFactory
// .getLowPass( 48000, 6000, 73, WindowType.HAMMING ), 1.0 );
private C4FMDecoder mC4FMDecoder = new C4FMDecoder();
FilterFactory.getLowPass( 48000, 5000, 31, WindowType.HAMMING ), 1.0 );
private FMDiscriminator mDemodulator = new FMDiscriminator( 1 );
private C4FMSymbolFilter mSymbolFilter = new C4FMSymbolFilter();
private C4FMSlicer mSlicer = new C4FMSlicer();
private P25MessageFramer mNormalFramer;
private P25MessageFramer mInvertedFramer;
private P25MessageProcessor mMessageProcessor;
private AliasList mAliasList;
public P25Decoder( SampleType sampleType, AliasList aliasList )
@@ -65,13 +76,25 @@ public class P25Decoder extends Decoder implements Instrumentable
* to process
*/
this.addComplexListener( mBasebandFilter );
mBasebandFilter.setListener( mFMDiscriminator );
mFMDiscriminator.setListener( getRealReceiver() );
// mFMDiscriminator.setListener( mAudioFilter );
// mAudioFilter.setListener( getRealReceiver() );
mBasebandFilter.setListener( mDemodulator );
mDemodulator.setListener( getRealReceiver() );
}
addRealSampleListener( mC4FMDecoder );
addRealSampleListener( mSymbolFilter );
mSymbolFilter.setListener( mSlicer );
mMessageProcessor = new P25MessageProcessor( mAliasList );
mNormalFramer = new P25MessageFramer(
FrameSync.P25_PHASE1.getSync(), 64, false );
mSlicer.addListener( mNormalFramer );
mNormalFramer.setListener( mMessageProcessor );
mInvertedFramer = new P25MessageFramer(
FrameSync.P25_PHASE1_INVERTED.getSync(), 64, true );
mSlicer.addListener( mInvertedFramer );
mInvertedFramer.setListener( mMessageProcessor );
}
@Override
@@ -83,24 +106,23 @@ public class P25Decoder extends Decoder implements Instrumentable
@Override
public List<Tap> getTaps()
{
return mC4FMDecoder.getTaps();
return new ArrayList<Tap>();
}
@Override
public void addTap( Tap tap )
{
mC4FMDecoder.addTap( tap );
}
@Override
public void removeTap( Tap tap )
{
mC4FMDecoder.removeTap( tap );
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
throw new IllegalArgumentException( "unfiltered real sample provider not implemented in P25 Decoder" );
throw new IllegalArgumentException( "unfiltered real sample provider "
+ "not implemented in P25 Decoder" );
}
}

View File

@@ -94,4 +94,30 @@ public class P25Interleave
return message;
}
public static void main( String[] args )
{
int start = 0;
int end = 196;
BitSetBuffer b = new BitSetBuffer( end );
for( int x = 0; x < end; x += 3 )
{
if( x > 20 )
{
b.set( x );
}
}
mLog.debug( "Origi:" + b.toString() );
P25Interleave.interleave( b, start, end );
mLog.debug( "Inter:" + b.toString() );
P25Interleave.deinterleave( b, start, end );
mLog.debug( "Donee:" + b.toString() );
}
}

View File

@@ -0,0 +1,28 @@
package decode.p25;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.AliasList;
public class P25MessageProcessor implements Listener<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageProcessor.class );
private AliasList mAliasList;
public P25MessageProcessor( AliasList aliasList )
{
mAliasList = aliasList;
}
@Override
public void receive( Message message )
{
mLog.debug( "P25: " + message.toString() + "\t" + message.getBinaryMessage() );
}
}

View File

@@ -0,0 +1,502 @@
package decode.p25;
import java.util.ArrayList;
import bits.BitSetBuffer;
public class TrellisHalfRate
{
private ArrayList<ConstellationNode> mConstellationNodes =
new ArrayList<ConstellationNode>();
private static final int[][] CONSTELLATION_COSTS =
{ { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 },
{ 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 },
{ 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 },
{ 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 },
{ 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 },
{ 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 },
{ 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 },
{ 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 },
{ 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 },
{ 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 },
{ 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 },
{ 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 },
{ 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 },
{ 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 },
{ 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 },
{ 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 } };
public TrellisHalfRate()
{
/* Create 49 x 4-bit constellation nodes to handle 196-bit blocks */
ConstellationNode previous = null;
for( int x = 0; x < 49; x++ )
{
ConstellationNode node = new ConstellationNode();
if( previous != null )
{
previous.connect( node );
}
previous = node;
mConstellationNodes.add( node );
}
}
public void dispose()
{
for( ConstellationNode node: mConstellationNodes )
{
node.dispose();
}
mConstellationNodes.clear();
}
public void decode( BitSetBuffer message, int start, int end )
{
/* load each of the nodes with deinterleaved constellations */
for( int index = 0; index < 49; index++ )
{
Constellation c = getConstellation( message, index * 4 );
mConstellationNodes.get( index ).setConstellation( c );
}
/* test to see if constellations are correct - otherwise correct them */
ConstellationNode firstNode = mConstellationNodes.get( 0 );
if( !firstNode.startsWith( Dibit.D0 ) || !firstNode.isCorrect() )
{
firstNode.correctTo( Dibit.D0 );
}
/* clear constellations from original message */
message.clear( start, end );
/* replace with decoded values from the nodes */
for( int index = 0; index < 49; index++ )
{
ConstellationNode node = mConstellationNodes.get( index );
if( node.firstBit() )
{
message.set( start + ( index * 2 ) );
}
if( node.secondBit() )
{
message.set( start + ( index * 2 ) + 1 );
}
}
}
private Constellation getConstellation( BitSetBuffer message, int index )
{
int constellation = 0;
for( int x = 0; x < 4; x++ )
{
if( message.get( index + x ) )
{
constellation += ( 1 << ( 3 - x ) );
}
}
return Constellation.fromValue( constellation );
}
public class ConstellationNode
{
private ConstellationNode mConnectedNode;
private Constellation mConstellation;
private boolean mCorrect;
public ConstellationNode()
{
}
public void dispose()
{
mConnectedNode = null;
}
public boolean startsWith( Dibit dibit )
{
return mConstellation.getLeft() == dibit;
}
public boolean firstBit()
{
return mConstellation.getRight().firstBit();
}
public boolean secondBit()
{
return mConstellation.getRight().secondBit();
}
/**
* Executes a correction down the line of connected nodes. Only nodes
* with the mCorrect flag set to false will be corrected.
*
* Note: Assumes that the starting node's value is 0. Initiate the
* corrective sequence by invoking this method with Dibit.D0 on the
* first node.
*
* @param dibit to use for the left side.
*/
public void correctTo( Dibit dibit )
{
if( mCorrect && mConstellation.getLeft() == dibit )
{
return;
}
if( isCurrentConnectionCorrect() )
{
mConstellation = Constellation.
fromDibits( dibit, mConstellation.getRight() );
mCorrect = true;
if( mConnectedNode != null )
{
mConnectedNode.correctTo( mConstellation.getRight() );
}
}
else
{
Constellation cheapest = mConstellation;
int cost = 100; //arbitrary
for( Dibit d: Dibit.values() )
{
Constellation test = Constellation.fromDibits( dibit, d );
int testCost = mConstellation.costTo( test ) +
mConnectedNode.costTo( d );
if( testCost < cost )
{
cost = testCost;
cheapest = test;
}
}
mConstellation = cheapest;
mConnectedNode.correctTo( mConstellation.getRight() );
mCorrect = true;
}
}
/**
* Calculates the cost (hamming distance) of using the argument as the
* left side dibit for the current node, and recursively finding the
* cheapest corresponding right dibit.
*
* @param leftTest
* @return
*/
public int costTo( Dibit leftTest )
{
if( isCurrentConnectionCorrect() )
{
Constellation c = Constellation.
fromDibits( leftTest, mConstellation.getRight() );
return mConstellation.costTo( c );
}
else
{
int cheapestCost = 100; //arbitrary
for( Dibit d: Dibit.values() )
{
Constellation c = Constellation.fromDibits( leftTest, d );
int cost = mConnectedNode.costTo( d ) +
mConstellation.costTo( c );
if( cost < cheapestCost )
{
cheapestCost = cost;
}
}
return cheapestCost;
}
}
/**
* Indicates if the immediate connection is correct
*/
public boolean isCurrentConnectionCorrect()
{
return ( mConnectedNode == null ||
mConstellation.getRight() == mConnectedNode.getLeft() );
}
/**
* Executes a recursive call to all nodes to the right, setting the
* mCorrect flag on all nodes to true, if the node's connection to the
* right node is correct and all nodes to the right are correct. Or,
* false if this node's connection, or any node's connection to the
* right is incorrect.
*
* @return - true - all node connections to the right are correct
* false - one or more nodes to the right are incorrect
*/
public boolean isCorrect()
{
if( mCorrect )
{
return mCorrect;
}
if( mConnectedNode == null )
{
mCorrect = true;
}
else
{
mCorrect = mConnectedNode.isCorrect() &&
mConstellation.getRight() == mConnectedNode.getLeft();
}
return mCorrect;
}
public Dibit getLeft()
{
return mConstellation.getLeft();
}
public void setConstellation( Constellation constellation )
{
mConstellation = constellation;
mCorrect = false;
}
public void connect( ConstellationNode node )
{
mConnectedNode = node;
}
}
public enum Constellation
{
C0( Dibit.D1, Dibit.D1, 0 ),
C1( Dibit.D0, Dibit.D2, 1 ),
C2( Dibit.D0, Dibit.D0, 2 ),
C3( Dibit.D1, Dibit.D3, 3 ),
C4( Dibit.D2, Dibit.D3, 4 ),
C5( Dibit.D3, Dibit.D0, 5 ),
C6( Dibit.D3, Dibit.D2, 6 ),
C7( Dibit.D2, Dibit.D1, 7 ),
C8( Dibit.D3, Dibit.D3, 8 ),
C9( Dibit.D2, Dibit.D0, 9 ),
CA( Dibit.D2, Dibit.D2, 10 ),
CB( Dibit.D3, Dibit.D1, 11 ),
CC( Dibit.D0, Dibit.D1, 12 ),
CD( Dibit.D1, Dibit.D2, 13 ),
CE( Dibit.D1, Dibit.D0, 14 ),
CF( Dibit.D0, Dibit.D3, 15 );
private Dibit mLeftDibit;
private Dibit mRightDibit;
private int mValue;
private Constellation( Dibit leftDibit, Dibit rightDibit, int value )
{
mLeftDibit = leftDibit;
mRightDibit = rightDibit;
mValue = value;
}
public Dibit getLeft()
{
return mLeftDibit;
}
public Dibit getRight()
{
return mRightDibit;
}
public int getValue()
{
return mValue;
}
public static Constellation fromValue( int value )
{
if( 0 <= value && value <= 15 )
{
return Constellation.values()[ value ];
}
return null;
}
/**
* Returns the cost or hamming distance to the other constellation using
* the values from the constellation costs table
*/
public int costTo( Constellation other )
{
return CONSTELLATION_COSTS[ getValue() ][ other.getValue() ];
}
public static Constellation fromDibits( Dibit left, Dibit right )
{
switch( left )
{
case D0:
switch( right )
{
case D0:
return C2;
case D1:
return CC;
case D2:
return C1;
case D3:
return CF;
default:
}
case D1:
switch( right )
{
case D0:
return CE;
case D1:
return C0;
case D2:
return CD;
case D3:
return C3;
default:
}
case D2:
switch( right )
{
case D0:
return C9;
case D1:
return C7;
case D2:
return CA;
case D3:
return C4;
default:
}
case D3:
switch( right )
{
case D0:
return C5;
case D1:
return CB;
case D2:
return C6;
case D3:
return C8;
default:
}
default:
}
/* we should never get to here */
return C0;
}
}
public enum Dibit
{
D0( false, false ),
D1( false, true ),
D2( true, false ),
D3( true, true );
private boolean mFirstBit;
private boolean mSecondBit;
private Dibit( boolean firstBit, boolean secondBit )
{
mFirstBit = firstBit;
mSecondBit = secondBit;
}
public boolean firstBit()
{
return mFirstBit;
}
public boolean secondBit()
{
return mSecondBit;
}
}
/**
* Test harness
*/
public static void main( String[] args )
{
String original = "0010101110101010001011001100111011001000100000101101001000100000110010111010100100100111100100100010000000001110001110010010110000010110001000101101101001110010001001011100001101011110110110011011";
// String original = "0010111110111110001000100010001011000000000000001101101010101001110011011001001000101100000011100010001000101100001101010010001000011010011100001101100111001110000101111101010001101010101001111110";
// String original = "0010001011000000110101111110001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010111110111110110000110101001011110101";
String witherrors = "0010001011000000110101100110001000100010001100100011001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010111110111110110000110101001011110101";
//Injected errors: xx x x
BitSetBuffer bufferOriginal = new BitSetBuffer( 196 );
BitSetBuffer bufferWithErrors = new BitSetBuffer( 196 );
try
{
for( int x = 0; x < 196; x++ )
{
if( witherrors.substring( x, x + 1 ).contentEquals( "0" ) )
{
bufferWithErrors.add( false );
}
else
{
bufferWithErrors.add( true );
}
if( original.substring( x, x + 1 ).contentEquals( "0" ) )
{
bufferOriginal.add( false );
}
else
{
bufferOriginal.add( true );
}
}
}
catch( Exception e )
{
e.printStackTrace();
}
System.out.println( " ORIGINAL: " + original );
System.out.println( " WITH ERRORS: " + witherrors );
TrellisHalfRate t = new TrellisHalfRate();
t.decode( bufferOriginal, 0, 196 );
System.out.println( "ORIG DECODED: " + bufferOriginal.toString() );
t.decode( bufferWithErrors, 0, 196 );
System.out.println( "ERRS DECODED: " + bufferWithErrors.toString() );
}
}

View File

@@ -0,0 +1,116 @@
package decode.p25.message;
import decode.p25.reference.DataUnitID;
import map.Plottable;
import message.Message;
import alias.Alias;
import bits.BitSetBuffer;
public class P25Message extends Message
{
public static final int[] NAC = { 0,1,2,3,4,5,6,7,8,9,10,11 };
public static final int[] DUID = { 12,13,14,15 };
public static final int[] BCH = { 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 };
protected BitSetBuffer mMessage;
protected DataUnitID mDUID;
public P25Message( BitSetBuffer message, DataUnitID duid )
{
super();
mMessage = message;
mDUID = duid;
}
public String getNAC()
{
return mMessage.getHex( NAC, 3 );
}
public DataUnitID getDUID()
{
return mDUID;
}
@Override
public String getErrorStatus()
{
return "UNK";
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().name() );
return sb.toString();
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
@Override
public String getProtocol()
{
return "P25 Phase 1";
}
@Override
public String getEventType()
{
return mDUID.name();
}
@Override
public String getFromID()
{
return null;
}
@Override
public Alias getFromIDAlias()
{
return null;
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public Plottable getPlottable()
{
return null;
}
@Override
public String toString()
{
return getMessage();
}
@Override
public boolean isValid()
{
return true;
}
}

View File

@@ -0,0 +1,157 @@
package decode.p25.message;
import decode.p25.reference.DataUnitID;
import decode.p25.reference.PDUFormat;
import decode.p25.reference.ServiceAccessPoint;
import decode.p25.reference.Vendor;
import bits.BitSetBuffer;
public class PDUMessage extends P25Message
{
public static final int PDU_HEADER_DUMMY1 = 66; //Always 0
public static final int PDU_HEADER_CONFIRMATION_REQUIRED = 67;
public static final int PDU_HEADER_DIRECTION_FLAG = 68;
public static final int[] PDU_HEADER_FORMAT = { 69,70,71,72,73 };
public static final int PDU_HEADER_DUMMY2 = 74; //Always 1
public static final int PDU_HEADER_DUMMY3 = 75; //Always 1
public static final int[] PDU_HEADER_SAP_ID = { 76,77,78,79,80 };
public static final int[] PDU_HEADER_VENDOR_ID = { 81,82,83,84,85,86,87,88 };
public static final int[] PDU_HEADER_LOGICAL_LINK_ID = { 89,90,91,92,93,94,
97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114 };
public static final int PDU_HEADER_FULL_MESSAGE_FLAG = 115;
public static final int[] PDU_HEADER_BLOCKS_TO_FOLLOW = { 116,117,118,119,
120,121,122 };
public static final int PDU_HEADER_DUMMY4 = 123; //Always 0
public static final int PDU_HEADER_DUMMY5 = 124; //Always 0
public static final int PDU_HEADER_DUMMY6 = 125; //Always 0
public static final int[] PDU_HEADER_PAD_BLOCKS = { 126,127,128,129,130 };
public static final int PDU_HEADER_RESYNCHRONIZE_FLAG = 131;
public static final int[] PDU_HEADER_SEQUENCE_NUMBER = { 132,133,134 };
public static final int[] PDU_HEADER_FRAGMENT_SEQUENCE_NUMBER = { 135,136,
137,138 };
public static final int PDU_HEADER_DUMMY7 = 139; //Always 0
public static final int PDU_HEADER_DUMMY8 = 140; //Always 0
public static final int[] PDU_HEADER_DATA_HEADER_OFFSET = { 141,142,143,
144,145,146 };
public static final int[] PDU_HEADER_CRC = { 147,148,149,150,151,152,153,154,
155,156,157,158,159,160,161,162 };
//status 166,167
public PDUMessage( BitSetBuffer message, DataUnitID duid )
{
super( message, duid );
}
public String getConfirmation()
{
return mMessage.get( PDU_HEADER_CONFIRMATION_REQUIRED ) ? "CON" : "UNC";
}
public String getDirection()
{
return mMessage.get( PDU_HEADER_DIRECTION_FLAG ) ? "OSP" : "ISP";
}
public PDUFormat getFormat()
{
return PDUFormat.fromValue( mMessage.getInt( PDU_HEADER_FORMAT ) );
}
public ServiceAccessPoint getServiceAccessPoint()
{
return ServiceAccessPoint.fromValue( mMessage.getInt( PDU_HEADER_SAP_ID ) );
}
public Vendor getVendor()
{
return Vendor.fromValue( mMessage.getInt( PDU_HEADER_VENDOR_ID ) );
}
public String getLogicalLinkID()
{
return mMessage.getHex( PDU_HEADER_LOGICAL_LINK_ID, 6 );
}
public boolean isFullMessage()
{
return mMessage.get( PDU_HEADER_FULL_MESSAGE_FLAG );
}
public int getBlockCount()
{
return mMessage.getInt( PDU_HEADER_BLOCKS_TO_FOLLOW );
}
public int getPadBlockCount()
{
return mMessage.getInt( PDU_HEADER_PAD_BLOCKS );
}
public boolean isResync()
{
return mMessage.get( PDU_HEADER_RESYNCHRONIZE_FLAG );
}
public int getSequenceNumber()
{
return mMessage.getInt( PDU_HEADER_SEQUENCE_NUMBER );
}
public int getFragmentSequenceNumber()
{
return mMessage.getInt( PDU_HEADER_FRAGMENT_SEQUENCE_NUMBER );
}
public int getDataHeaderOffset()
{
return mMessage.getInt( PDU_HEADER_DATA_HEADER_OFFSET );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().name() );
sb.append( " " );
sb.append( getConfirmation() );
sb.append( " " );
sb.append( getDirection() );
sb.append( " FMT:" );
sb.append( getFormat().getLabel() );
switch( getFormat() )
{
case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL:
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
sb.append( " SAP:" );
sb.append( getServiceAccessPoint().getLabel() );
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
sb.append( " LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " FMF:" );
sb.append( isFullMessage() ? "1" : "0" );
sb.append( " BLKS:" );
sb.append( getBlockCount() );
sb.append( " PAD:" );
sb.append( getPadBlockCount() );
sb.append( " RESYNC:" );
sb.append( isResync() ? "1" : "0" );
sb.append( " SEQ:" );
sb.append( getSequenceNumber() );
sb.append( " FRAG:" );
sb.append( getFragmentSequenceNumber() );
sb.append( " DHO:" );
sb.append( getDataHeaderOffset() );
break;
default:
}
return sb.toString();
}
}

View File

@@ -0,0 +1,67 @@
package decode.p25.message;
import decode.p25.reference.DataUnitID;
import decode.p25.reference.Opcode;
import decode.p25.reference.Vendor;
import bits.BitSetBuffer;
public class TSBKMessage extends P25Message
{
public static final int LAST_BLOCK_FLAG = 0;
public static final int ENCRYPTED_FLAG = 1;
public static final int[] OPCODE = { 2,3,4,5,6,7 };
public static final int[] VENDOR_ID = { 8,9,10,11,12,13,14,15 };
public TSBKMessage( int system, BitSetBuffer message )
{
super( message, DataUnitID.TSBK1 );
}
public boolean isLastBlock()
{
return mMessage.get( LAST_BLOCK_FLAG );
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTED_FLAG );
}
public Vendor getVendor()
{
return Vendor.fromValue( mMessage.getInt( VENDOR_ID ) );
}
public Opcode getOpcode()
{
return Opcode.fromValue( mMessage.getInt( OPCODE ) );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "SYSTEM:" );
sb.append( getNAC() ); /* NAC is the system id for TSBK messages */
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
if( isEncrypted() )
{
sb.append( " ENCRYPTED" );
}
else
{
Opcode opcode = getOpcode();
sb.append( " OPCODE:" );
sb.append( opcode.name() );
sb.append( " [" );
sb.append( mMessage.getHex( OPCODE, 2 ) );
sb.append( "]" );
}
return sb.toString();
}
}

View File

@@ -0,0 +1,31 @@
package decode.p25.message;
import bits.BitSetBuffer;
import decode.p25.reference.Opcode;
import decode.p25.reference.Vendor;
public class TSBKMessageFactory
{
public static TSBKMessage getMessage( int system, BitSetBuffer buffer )
{
int opcode = buffer.getInt( TSBKMessage.OPCODE );
Vendor vendor = Vendor.fromValue(
buffer.getInt( TSBKMessage.VENDOR_ID ) );
switch( vendor )
{
case STANDARD:
switch( Opcode.fromValue( opcode ) )
{
}
return new TSBKMessage( system, buffer );
case MOTOROLA:
default:
return new TSBKMessage( system, buffer );
}
}
}

View File

@@ -0,0 +1,78 @@
package decode.p25.reference;
/**
* The Data Unit ID (DUID) is part of the Network ID (NID) field and indicates
* the type of message.
*/
public enum DataUnitID
{
NID ( -1, 64, "Network and Data Unit ID", false ),
HDU ( 0, 722, "Header Data Unit", false ),
TDU ( 3, 64, "Simple Terminator Data Unit", false ),
LDU1 ( 5, 1630, "Logical Link Data Unit 1", true ),
TSBK1( 7, 260, "Trunking Signaling Block", false ), //Single Block
TSBK2( 7, 260, "Trunking Signaling Block", false ), //Single Block
TSBK3( 7, 260, "Trunking Signaling Block", false ), //Single Block
LDU2 ( 10, 1632, "Logical Link Data Unit 2", true ),
PDU1 ( 12, 456, "Packet Data Unit", false ),
PDU2 ( 12, 652, "Packet Data Unit", false ),
PDU3 ( 12, 848, "Packet Data Unit", false ),
TDULC( 15, 372, "Terminator Data Unit With Link Control", false ),
UNKN ( -1, 0, "Unknown", false );
private int mValue;
private int mMessageLength;
private String mLabel;
private boolean mParity;
private DataUnitID( int value, int length, String label, boolean parity )
{
mValue = value;
mMessageLength = length;
mLabel = label;
mParity = parity;
}
public int getValue()
{
return mValue;
}
public int getMessageLength()
{
return mMessageLength;
}
public String getLabel()
{
return mLabel;
}
public boolean getParity()
{
return mParity;
}
public static DataUnitID fromValue( int value )
{
switch( value )
{
case 0:
return HDU;
case 3:
return TDU;
case 5:
return LDU1;
case 7:
return TSBK1;
case 10:
return LDU2;
case 12:
return PDU1;
case 15:
return TDULC;
default:
return UNKN;
}
}
}

View File

@@ -0,0 +1,23 @@
package decode.p25.reference;
public enum Encryption
{
ACCORDION_1_3( 0x00 ),
BATON_AUTO_EVEN( 0x01 ),
FIREFLY_TYPE1( 0x02 ),
MAYFLY_TYPE1( 0x03 ),
SAVILLE( 0x04 ),
BATON_AUTO_ODD( 0x41 ),
UNENCRYPTED( 0x80 ),
DES_OFB( 0x81 ),
TRIPLE_DES_2_KEY( 0x82 ),
TRIPLE_DES_3_KEY( 0x83 ),
AES( 0x84 );
private int mValue;
private Encryption( int value )
{
mValue = value;
}
}

View File

@@ -0,0 +1,99 @@
package decode.p25.reference;
public enum Opcode
{
GROUP_VOICE_CHANNEL_GRANT( "Group Voice Channel Grant", 0 ),
RESERVED_01( "Reserved", 1 ),
GROUP_VOICE_CHANNLE_GRANT_UPDATE( "Group Voice Channel Grant Update", 2 ),
GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT( "Group Voice Channel Grant Update - Explicit", 3 ),
UNIT_TO_UNIT_VOICE_CHANNEL_GRANT( "Unit-to-Unit Voice Channel Grant", 4 ),
UNIT_TO_UNIT_ANSWER_REQUEST( "Unit-to-Unit Answer Request", 5 ),
UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE( "Unit-to-Unit Voice Channel Grant Update", 6 ),
RESERVED_07( "Reserved", 7 ),
TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT( "Telephone Interconnect Voice Channel Grant", 8 ),
RESERVED_09( "Reserved", 9 ),
TELEPHONE_INTERCONNECT_ANSWER_REQUEST( "Telephone Interconnect Answer Request", 10 ),
RESERVED_0B( "Reserved", 11 ),
RESERVED_0C( "Reserved", 12 ),
RESERVED_0D( "Reserved", 13 ),
RESERVED_0E( "Reserved", 14 ),
RESERVED_0F( "Reserved", 15 ),
INDIVIDUAL_DATA_CHANNEL_GRANT( "Individual Data Channel Grant", 16 ),
GROUP_DATA_CHANNEL_GRANT( "Group Data Channel Grant", 17 ),
GROUP_DATA_CHANNEL_ANNOUNCEMENT( "Group Data Channel Announcement", 18 ),
GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT( "Group Data Channel Announcement-Explicit", 19 ),
SNDCP_DATA_CHANNEL_GRANT( "SNDCP Data Channel Grant", 20 ),
SNDCP_DATA_PAGE_REQUEST( "SNDCP Data Page Request", 21 ),
SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT( "SNDCP Data Channel Announcement Explicit", 22 ),
RESERVED_17( "Reserved", 23 ),
STATUS_UPDATE( "Status Update", 24 ),
RESERVED_19( "Reserved", 25 ),
STATUS_QUERY( "Status Query", 26 ),
RESERVED_1B( "Reserved", 27 ),
MESSAGE_UPDATE( "Message Update", 28 ),
RADIO_UNIT_MONITOR_COMMAND( "Radio Unit Monitor Command", 29 ),
RESERVED_1E( "Reserved", 30 ),
CALL_ALERT( "Call Alert", 31 ),
ACKNOWLEDGE_RESPONSE_FNE( "Acknowledge Response - FNE", 32 ),
QUEUED_RESPONSE( "Queued Response", 33 ),
RESERVED_22( "Reserved", 34 ),
RESERVED_23( "Reserved", 35 ),
EXTENDED_FUNCTION_COMMAND( "Extended Function Command", 36 ),
RESERVED_25( "Reserved", 37 ),
RESERVED_26( "Reserved", 38 ),
DENY_RESPONSE( "Deny Response", 39 ),
GROUP_AFFILIATION_RESPONSE( "Group Affiliation Response", 40 ),
SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT( "Secondary Control Channel Broadcast-Explicit", 41 ),
GROUP_AFFILIATION_QUERY( "Group Affiliation Query", 42 ),
LOCATION_REGISTRATION_RESPONSE( "Location Registration Response", 43 ),
UNIT_REGISTRATION_RESPONSE( "Unit Registration Response", 44 ),
UNIT_REGISTRATION_COMMAND( "Unit Registration Command", 45 ),
AUTHENTICATION_COMMAND( "Authentication Command", 46 ),
UNIT_DEREGISTRATION_ACKNOWLEDGE( "De-Registration Acknowledge", 47 ),
RESERVED_30( "Reserved", 48 ),
RESERVED_31( "Reserved", 49 ),
RESERVED_32( "Reserved", 50 ),
RESERVED_33( "Reserved", 51 ),
IDENTIFIER_UPDATE_VHF_UHF_BANDS( "Identifier Update for VHF/UHF Bands", 52 ),
TIME_DATE_ANNOUNCEMENT( "Time and Date Announcement", 53 ),
ROAMING_ADDRESS_COMMAND( "Roaming Address Command", 54 ),
ROAMING_ADDRESS_UPDATE( "Roaming Address Update", 55 ),
SYSTEM_SERVICE_BROADCAST( "System Service Broadcast", 56 ),
SECONDARY_CONTROL_CHANNEL_BROADCAST( "Secondary Control Channel Broadcast", 57 ),
RFSS_STATUS_BROADCAST( "RFSS Status Broadcast", 58 ),
NETWORK_STATUS_BROADCAST( "Network Status Broadcast", 59 ),
ADJACENT_STATUS_BROADCAST( "Adjacent Status Broadcast", 60 ),
IDENTIFIER_UPDATE_NON_VHF_UHF( "Identifier Update for non-VHF/UHF Bands", 61 ),
PROTECTION_PARAMETER_BROADCAST( "Protection Parameter Broadcast", 62 ),
PROTECTION_PARAMETER_UPDATE( "Protection Parameter Update", 63 ),
UNKNOWN( "Unknown", -1 );
private String mDescription;
private int mCode;
private Opcode( String description, int code )
{
mDescription = description;
mCode = code;
}
public String getDescription()
{
return mDescription;
}
public int getCode()
{
return mCode;
}
public static Opcode fromValue( int value )
{
if( 0 <= value && value <= 63 )
{
return values()[ value ];
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,67 @@
package decode.p25.reference;
public enum PDUFormat
{
F00( "F00", 0 ),
F01( "F01", 1 ),
F02( "F02", 2 ),
F03( "F03", 3 ),
F04( "F04", 4 ),
F05( "F05", 5 ),
F06( "F06", 6 ),
F07( "F07", 7 ),
F08( "F08", 8 ),
F09( "F09", 9 ),
F10( "F10", 10 ),
F11( "F11", 11 ),
F12( "F12", 12 ),
F13( "F13", 13 ),
F14( "F14", 14 ),
F15( "F15", 15 ),
F16( "F16", 16 ),
F17( "F17", 17 ),
F18( "F18", 18 ),
F19( "F19", 19 ),
F20( "F20", 20 ),
UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL( "UMBTC", 21 ),
ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL( "AMBTC", 22 ),
F23( "F23", 23 ),
F24( "F24", 24 ),
F25( "F25", 25 ),
F26( "F26", 26 ),
F27( "F27", 27 ),
F28( "F28", 28 ),
F29( "F29", 29 ),
F30( "F30", 30 ),
F31( "F31", 31 ),
UNKNOWN( "UNKN", -1 );
private String mLabel;
private int mValue;
private PDUFormat( String label, int value )
{
mLabel = label;
mValue = value;
}
public String getLabel()
{
return mLabel;
}
public int getValue()
{
return mValue;
}
public static PDUFormat fromValue( int value )
{
if( 0 <= value && value <= 31 )
{
return values()[ value ];
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,99 @@
package decode.p25.reference;
public enum ServiceAccessPoint
{
SAP_0( "0", 0 ),
SAP_1( "1", 1 ),
SAP_2( "2", 2 ),
SAP_3( "3", 3 ),
SAP_4( "4", 4 ),
SAP_5( "5", 5 ),
SAP_6( "6", 6 ),
SAP_7( "7", 7 ),
SAP_8( "8", 8 ),
SAP_9( "9", 9 ),
SAP_10( "10", 10 ),
SAP_11( "11", 11 ),
SAP_12( "12", 12 ),
SAP_13( "13", 13 ),
SAP_14( "14", 14 ),
SAP_15( "15", 15 ),
SAP_16( "16", 16 ),
SAP_17( "17", 17 ),
SAP_18( "18", 18 ),
SAP_19( "19", 19 ),
SAP_20( "20", 20 ),
SAP_21( "21", 21 ),
SAP_22( "22", 22 ),
SAP_23( "23", 23 ),
SAP_24( "24", 24 ),
SAP_25( "25", 25 ),
SAP_26( "26", 26 ),
SAP_27( "27", 27 ),
SAP_28( "28", 28 ),
SAP_29( "29", 29 ),
SAP_30( "30", 30 ),
SAP_31( "31", 31 ),
SAP_32( "32", 32 ),
SAP_33( "33", 33 ),
SAP_34( "34", 34 ),
SAP_35( "35", 35 ),
SAP_36( "36", 36 ),
SAP_37( "37", 37 ),
SAP_38( "38", 38 ),
SAP_39( "39", 39 ),
SAP_40( "40", 40 ),
SAP_41( "41", 41 ),
SAP_42( "42", 42 ),
SAP_43( "43", 43 ),
SAP_44( "44", 44 ),
SAP_45( "45", 45 ),
SAP_46( "46", 46 ),
SAP_47( "47", 47 ),
SAP_48( "48", 48 ),
SAP_49( "49", 49 ),
SAP_50( "50", 50 ),
SAP_51( "51", 51 ),
SAP_52( "52", 52 ),
SAP_53( "53", 53 ),
SAP_54( "54", 54 ),
SAP_55( "55", 55 ),
SAP_56( "56", 56 ),
SAP_57( "57", 57 ),
SAP_58( "58", 58 ),
SAP_59( "59", 59 ),
SAP_60( "60", 60 ),
UNENCRYPTED_TRUNKING_CONTROL( "OPEN TRUNK", 61 ),
SAP_62( "62", 62 ),
ENCRYPTED_TRUNKING_CONTROL( "ENC TRUNK", 63 ),
UKNOWN( "UNKN", -1 );
private String mLabel;
private int mValue;
private ServiceAccessPoint( String label, int value )
{
mLabel = label;
mValue = value;
}
public String getLabel()
{
return mLabel;
}
public int getValue()
{
return mValue;
}
public static ServiceAccessPoint fromValue( int value )
{
if( 0 <= value && value <= 63 )
{
return values()[ value ];
}
return UKNOWN;
}
}

View File

@@ -0,0 +1,21 @@
package decode.p25.reference;
public enum Status
{
TALKAROUND,
BUSY,
REPEATER,
IDLE,
UNKNOWN;
public Status fromValue( int value )
{
if( 0 <= value && value <= 3 )
{
return values()[ value ];
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,292 @@
package decode.p25.reference;
public enum Vendor
{
STANDARD( "STANDARD", 0),
V1( "V_01", 1),
V2( "V_02", 2),
V3( "V_03", 3),
V4( "V_04", 4),
V5( "V_05", 5),
V6( "V_06", 6),
V7( "V_07", 7),
V8( "V_08", 8),
V9( "V_09", 9),
V10( "V_0A", 10),
V11( "V_0B", 11),
V12( "V_0C", 12),
V13( "V_0D", 13),
V14( "V_0E", 14),
V15( "V_0F", 15),
RELM( "RELM/BK RADIO", 16),
V17( "V_11", 17),
V18( "V_12", 18),
V19( "V_13", 19),
V20( "V_14", 20),
V21( "V_15", 21),
V22( "V_16", 22),
V23( "V_17", 23),
V24( "V_18", 24),
V25( "V_19", 25),
V26( "V_1A", 26),
V27( "V_1B", 27),
V28( "V_1C", 28),
V29( "V_1D", 29),
V30( "V_1E", 30),
V31( "V_1F", 31),
CYCOMM( "CYCOMM", 32),
V33( "V_21", 33),
V34( "V_22", 34),
V35( "V_23", 35),
V36( "V_24", 36),
V37( "V_25", 37),
V38( "V_26", 38),
V39( "V_27", 39),
EFRATOM( "EFRATOM", 40),
V41( "V_29", 41),
V42( "V_2A", 42),
V43( "V_2B", 43),
V44( "V_2C", 44),
V45( "V_2D", 45),
V46( "V_2E", 46),
V47( "V_2F", 47),
ERICSSON( "ERICSSON", 48),
V49( "V_31", 49),
V50( "V_32", 50),
V51( "V_33", 51),
V52( "V_34", 52),
V53( "V_35", 53),
V54( "V_36", 54),
V55( "V_37", 55),
DATRON( "DATRON", 56),
V57( "V_39", 57),
V58( "V_3A", 58),
V59( "V_3B", 59),
V60( "V_3C", 60),
V61( "V_3D", 61),
V62( "V_3E", 62),
V63( "V_3F", 63),
ICOM( "ICOM", 64),
V65( "V_41", 65),
V66( "V_42", 66),
V67( "V_43", 67),
V68( "V_44", 68),
V69( "V_45", 69),
V70( "V_46", 70),
V71( "V_47", 71),
GARMIN( "GARMIN", 72),
V73( "V_49", 73),
V74( "V_4A", 74),
V75( "V_4B", 75),
V76( "V_4C", 76),
V77( "V_4D", 77),
V78( "V_4E", 78),
V79( "V_4F", 79),
GTE( "GTE", 80),
V81( "V_51", 81),
V82( "V_52", 82),
V83( "V_53", 83),
V84( "V_54", 84),
IFR_SYSTEMS( "IFR SYSTEMS", 85),
V86( "V_56", 86),
V87( "V_57", 87),
V88( "V_58", 88),
V89( "V_59", 89),
V90( "V_5A", 90),
V91( "V_5B", 91),
V92( "V_5C", 92),
V93( "V_5D", 93),
V94( "V_5E", 94),
V95( "V_5F", 95),
GEC_MARCONI( "GEC-MARCONI", 96),
V97( "V_61", 97),
V98( "V_62", 98),
V99( "V_63", 99),
V100( "V_64", 100),
V101( "V_65", 101),
V102( "V_66", 102),
V103( "V_67", 103),
KENWOOD( "KENWOOD", 104),
V105( "V_69", 105),
V106( "V_6A", 106),
V107( "V_6B", 107),
V108( "V_6C", 108),
V109( "V_6D", 109),
V110( "V_6E", 110),
V111( "V_6F", 111),
GLENAYRE( "GLENAYRE ELECTRONICS", 112),
V113( "V_71", 113),
V114( "V_72", 114),
V115( "V_73", 115),
JAPAN_RADIO( "JAPAN RADIO CO", 116),
V117( "V_75", 117),
V118( "V_76", 118),
V119( "V_77", 119),
KOKUSAI( "KOKUSAI", 120),
V121( "V_79", 121),
V122( "V_7A", 122),
V123( "V_7B", 123),
MAXON( "MAXON", 124),
V125( "V_7D", 125),
V126( "V_7E", 126),
V127( "V_7F", 127),
MIDLAND( "MIDLAND", 128),
V129( "V_81", 129),
V130( "V_82", 130),
V131( "V_83", 131),
V132( "V_84", 132),
V133( "V_85", 133),
DANIELS( "DANIELS ELECTRONICS", 134),
V135( "V_87", 135),
V136( "V_88", 136),
V137( "V_89", 137),
V138( "V_8A", 138),
V139( "V_8B", 139),
V140( "V_8C", 140),
V141( "V_8D", 141),
V142( "V_8E", 142),
V143( "V_8F", 143),
MOTOROLA( "MOTOROLA", 144),
V145( "V_91", 145),
V146( "V_92", 146),
V147( "V_93", 147),
V148( "V_94", 148),
V149( "V_95", 149),
V150( "V_96", 150),
V151( "V_97", 151),
V152( "V_98", 152),
V153( "V_99", 153),
V154( "V_9A", 154),
V155( "V_9B", 155),
V156( "V_9C", 156),
V157( "V_9D", 157),
V158( "V_9E", 158),
V159( "V_9F", 159),
THALES( "THALES", 160),
V161( "V_A1", 161),
V162( "V_A2", 162),
V163( "V_A3", 163),
MACOM( "M/A-COM", 164),
V165( "V_A5", 165),
V166( "V_A6", 166),
V167( "V_A7", 167),
V168( "V_A8", 168),
V169( "V_A9", 169),
V170( "V_AA", 170),
V171( "V_AB", 171),
V172( "V_AC", 172),
V173( "V_AD", 173),
V174( "V_AE", 174),
V175( "V_AF", 175),
RATHEON( "RATHEON", 176),
V177( "V_B1", 177),
V178( "V_B2", 178),
V179( "V_B3", 179),
V180( "V_B4", 180),
V181( "V_B5", 181),
V182( "V_B6", 182),
V183( "V_B7", 183),
V184( "V_B8", 184),
V185( "V_B9", 185),
V186( "V_BA", 186),
V187( "V_BB", 187),
V188( "V_BC", 188),
V189( "V_BD", 189),
V190( "V_BE", 190),
V191( "V_BF", 191),
SEA( "SEA", 192),
V193( "V_C1", 193),
V194( "V_C2", 194),
V195( "V_C3", 195),
V196( "V_C4", 196),
V197( "V_C5", 197),
V198( "V_C6", 198),
V199( "V_C7", 199),
SECURICOR( "SECURICOR", 200),
V201( "V_C9", 201),
V202( "V_CA", 202),
V203( "V_CB", 203),
V204( "V_CC", 204),
V205( "V_CD", 205),
V206( "V_CE", 206),
V207( "V_CF", 207),
ADI( "ADI", 208),
V209( "V_D1", 209),
V210( "V_D2", 210),
V211( "V_D3", 211),
V212( "V_D4", 212),
V213( "V_D5", 213),
V214( "V_D6", 214),
V215( "V_D7", 215),
TAIT( "TAIT", 216),
V217( "V_D9", 217),
V218( "V_DA", 218),
V219( "V_DB", 219),
V220( "V_DC", 220),
V221( "V_DD", 221),
V222( "V_DE", 222),
V223( "V_DF", 223),
TELETEC( "TELETEC", 224),
V225( "V_E1", 225),
V226( "V_E2", 226),
V227( "V_E3", 227),
V228( "V_E4", 228),
V229( "V_E5", 229),
V230( "V_E6", 230),
V231( "V_E7", 231),
V232( "V_E8", 232),
V233( "V_E9", 233),
V234( "V_EA", 234),
V235( "V_EB", 235),
V236( "V_EC", 236),
V237( "V_ED", 237),
V238( "V_EE", 238),
V239( "V_EF", 239),
TRANSCRYPT( "TRANSCRYPT", 240),
V241( "V_F1", 241),
V242( "V_F2", 242),
V243( "V_F3", 243),
V244( "V_F4", 244),
V245( "V_F5", 245),
V246( "V_F6", 246),
V247( "V_F7", 247),
V248( "V_F8", 248),
V249( "V_F9", 249),
V250( "V_FA", 250),
V251( "V_FB", 251),
V252( "V_FC", 252),
V253( "V_FD", 253),
V254( "V_FE", 254),
V255( "V_FF", 255),
VUNK( "UNKN", -1 );
private String mLabel;
private int mValue;
private Vendor( String label, int value )
{
mLabel = label;
mValue = value;
}
public String getLabel()
{
return mLabel;
}
public int getValue()
{
return mValue;
}
public static Vendor fromValue( int value )
{
if( 0 <= value && value <= 255 )
{
return values()[ value ];
}
return VUNK;
}
}