Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

View File

@@ -0,0 +1,15 @@
package module.decode.p25;
import bits.BinaryMessage;
public interface C4FMFrameListener
{
/**
* Listener interface to receive the output of the C4FMMessageFramer.
*
* @param buffer - framed message without the sync pattern
* @param inverted - flag indicating if the message was received inverted
*/
public void receive( BinaryMessage buffer, boolean inverted );
}

View File

@@ -0,0 +1,70 @@
package module.decode.p25;
import dsp.symbol.Dibit;
import sample.Broadcaster;
import sample.Listener;
import sample.real.RealSampleListener;
/**
* C4FM slicer to convert the output stream of the C4FMSymbolFilter into a
* stream of C4FM symbols.
*
* Supports registering listener(s) to receive normal and/or inverted symbol
* output streams.
*/
public class C4FMSlicer implements RealSampleListener
{
private static final float THRESHOLD = 2.0f;
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
/**
* Primary method for receiving output from the C4FMSymbolFilter. Slices
* (converts) the filtered sample value into a C4FMSymbol decision.
*/
@Override
public void receive( float sample )
{
if( sample > THRESHOLD )
{
dispatch( Dibit.D01_PLUS_3 );
}
else if( sample > 0 )
{
dispatch( Dibit.D00_PLUS_1 );
}
else if( sample > -THRESHOLD )
{
dispatch( Dibit.D10_MINUS_1 );
}
else
{
dispatch( Dibit.D11_MINUS_3 );
}
}
/**
* Dispatches the symbol decision to any registered listeners
*/
private void dispatch( Dibit symbol )
{
mBroadcaster.receive( symbol );
}
/**
* Registers the listener to receive the normal (non-inverted) C4FM symbol
* stream.
*/
public void addListener( Listener<Dibit> listener )
{
mBroadcaster.addListener( listener );
}
/**
* Removes the listener
*/
public void removeListener( Listener<Dibit> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,431 @@
package module.decode.p25;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.AdditiveFloatTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
import sample.real.RealSampleProvider;
import source.tuner.frequency.FrequencyCorrectionControl;
import source.tuner.frequency.FrequencyCorrectionControl.FrequencyCorrectionResetListener;
import dsp.gain.GainController;
public class C4FMSymbolFilter implements FrequencyCorrectionResetListener,
RealSampleListener,
RealSampleProvider,
Instrumentable
{
private final static Logger mLog =
LoggerFactory.getLogger( C4FMSymbolFilter.class );
private static final float TAPS[][] =
{
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
{ -3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f }, // 2/128
{ -4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f }, // 3/128
{ -6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f }, // 4/128
{ -7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f }, // 5/128
{ -9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f }, // 6/128
{ -1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f }, // 7/128
{ -1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f }, // 8/128
{ -1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f }, // 9/128
{ -1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f }, // 10/128
{ -1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f }, // 11/128
{ -1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f }, // 12/128
{ -1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f }, // 13/128
{ -2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f }, // 14/128
{ -2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f }, // 15/128
{ -2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f }, // 16/128
{ -2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f }, // 17/128
{ -2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f }, // 18/128
{ -2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f }, // 19/128
{ -3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f }, // 20/128
{ -3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f }, // 21/128
{ -3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f }, // 22/128
{ -3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f }, // 23/128
{ -3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f }, // 24/128
{ -3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f }, // 25/128
{ -3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f }, // 26/128
{ -3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f }, // 27/128
{ -4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f }, // 28/128
{ -4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f }, // 29/128
{ -4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f }, // 30/128
{ -4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f }, // 31/128
{ -4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f }, // 32/128
{ -4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f }, // 33/128
{ -4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f }, // 34/128
{ -4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f }, // 35/128
{ -5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f }, // 36/128
{ -5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f }, // 37/128
{ -5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f }, // 38/128
{ -5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f }, // 39/128
{ -5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f }, // 40/128
{ -5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f }, // 41/128
{ -5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f }, // 42/128
{ -5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f }, // 43/128
{ -5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f }, // 44/128
{ -5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f }, // 45/128
{ -5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f }, // 46/128
{ -6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f }, // 47/128
{ -6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f }, // 48/128
{ -6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f }, // 49/128
{ -6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f }, // 50/128
{ -6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f }, // 51/128
{ -6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f }, // 52/128
{ -6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f }, // 53/128
{ -6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f }, // 54/128
{ -6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f }, // 55/128
{ -6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f }, // 56/128
{ -6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f }, // 57/128
{ -6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f }, // 58/128
{ -6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f }, // 59/128
{ -6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f }, // 60/128
{ -6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f }, // 61/128
{ -6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f }, // 62/128
{ -6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f }, // 63/128
{ -6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f }, // 64/128
{ -6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f }, // 65/128
{ -6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f }, // 66/128
{ -6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f }, // 67/128
{ -6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f }, // 68/128
{ -6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f }, // 69/128
{ -6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f }, // 70/128
{ -6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f }, // 71/128
{ -6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f }, // 72/128
{ -6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f }, // 73/128
{ -6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f }, // 74/128
{ -6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f }, // 75/128
{ -6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f }, // 76/128
{ -6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f }, // 77/128
{ -6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f }, // 78/128
{ -6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f }, // 79/128
{ -6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f }, // 80/128
{ -6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f }, // 81/128
{ -6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f }, // 82/128
{ -6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f }, // 83/128
{ -6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f }, // 84/128
{ -6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f }, // 85/128
{ -6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f }, // 86/128
{ -6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f }, // 87/128
{ -5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f }, // 88/128
{ -5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f }, // 89/128
{ -5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f }, // 90/128
{ -5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f }, // 91/128
{ -5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f }, // 92/128
{ -5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f }, // 93/128
{ -5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f }, // 94/128
{ -5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f }, // 95/128
{ -5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f }, // 96/128
{ -5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f }, // 97/128
{ -4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f }, // 98/128
{ -4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f }, // 99/128
{ -4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f }, // 100/128
{ -4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f }, // 101/128
{ -4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f }, // 102/128
{ -4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f }, // 103/128
{ -4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f }, // 104/128
{ -4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f }, // 105/128
{ -3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f }, // 106/128
{ -3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f }, // 107/128
{ -3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f }, // 108/128
{ -3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f }, // 109/128
{ -3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f }, // 110/128
{ -3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f }, // 111/128
{ -2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f }, // 112/128
{ -2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f }, // 113/128
{ -2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f }, // 114/128
{ -2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f }, // 115/128
{ -2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f }, // 116/128
{ -2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f }, // 117/128
{ -1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f }, // 118/128
{ -1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f }, // 119/128
{ -1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f }, // 120/128
{ -1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f }, // 121/128
{ -1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f }, // 122/128
{ -9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f }, // 123/128
{ -7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f }, // 124/128
{ -5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f }, // 125/128
{ -3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f }, // 126/128
{ -1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f }, // 127/128
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128
};
/* Instrumentation Taps */
private static final String INSTRUMENT_SYMBOL_SPREAD = "Tap Point: Symbol Spread (Goal=2.0)";
private FloatTap mSymbolSpreadTap;
private List<Tap> mAvailableTaps;
private static final int NUMBER_FILTER_TAPS = 8;
private static final int NUMBER_FILTER_STEPS = 128;
private static final int SAMPLE_RATE = 48000;
private static final int SYMBOL_RATE = 4800;
/* Tracking loop gain constant */
private static final double K_SYMBOL_SPREAD = 0.0100;
/* Constraints on symbol spreading */
private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20%
private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20%
/* Symbol clock tracking loop gain */
private static final double K_SYMBOL_TIMING = 0.025;
/* Coarse and fine frequency tracking constants */
private static final double K_COARSE_FREQUENCY = 0.00125;
private static final double K_FINE_FREQUENCY = 0.125;
/* Frequency correction broadcast threshold */
// private static final double COARSE_FREQUENCY_DEADBAND = 1.66;
private static final double COARSE_FREQUENCY_DEADBAND = 1.20;
/* 2.0 symbol spread gives -3, -1, 1, 3 */
private float mSymbolSpread = 2.0f;
private float mSymbolClock = 0.0f;
private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
private float mFineFrequencyCorrection = 0.0f;
private float mCoarseFrequencyCorrection = 0.0f;
private float mHistory[] = new float[ NUMBER_FILTER_TAPS ];
private int mHistoryLast = 0;
private boolean mReset = false;
private RealSampleListener mListener;
private GainController mGainController;
private FrequencyCorrectionControl mFrequencyCorrectionControl;
/**
* C4FM Symbol Filter
*
* Sample Gain values - the gain of incoming sample values will critically
* impact the performance of this filter. Gain should be adjusted to
* optimize the value of mSymbolSpread toward a value of 2.0. If the value
* is less than 2.0, then increase gain. If the value is over 2.0, then
* decrease gain.
*
* When preceded by the RealAutomaticGainFilter class, an optimal gain
* setting for this filter is 15.4 and that yields a symbol spread that
* is centered on 2.0, ranging 1.92 to 2.08.
*/
public C4FMSymbolFilter( GainController gainController,
FrequencyCorrectionControl frequencyControl )
{
mGainController = gainController;
mFrequencyCorrectionControl = frequencyControl;
/* Register for reset events, so we can reset internal frequency tracking */
mFrequencyCorrectionControl.setListener( this );
}
@Override
public void receive( float sample )
{
//TODO: limit frequency corrections to once per buffer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11
if( mReset )
{
mGainController.reset();
mCoarseFrequencyCorrection = 0.0f;
mFineFrequencyCorrection = 0.0f;
mSymbolSpread = 2.0f;
mSymbolClock = 0.0f;
mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
mReset = false;
}
mSymbolClock += mSymbolTime;
mHistory[ mHistoryLast++ ] = sample;
mHistoryLast %= NUMBER_FILTER_TAPS;
if( mSymbolClock > 1.0f )
{
mSymbolClock -= 1.0f;
int imu = (int)Math.floor( 0.5 +
( (float)NUMBER_FILTER_STEPS * ( mSymbolClock / mSymbolTime ) ) );
if( imu >= NUMBER_FILTER_STEPS )
{
imu = NUMBER_FILTER_STEPS - 1;
}
int imu_p1 = imu + 1;
int j = mHistoryLast;
double interp = 0.0;
double interp_p1 = 0.0;
for( int i = 0; i < NUMBER_FILTER_TAPS; i++ )
{
interp += TAPS[ imu][ i ] * mHistory[ j ];
interp_p1 += TAPS[ imu_p1 ][ i ] * mHistory[ j ];
j = ( j + 1 ) % NUMBER_FILTER_TAPS;
}
/* Output symbol will be interpolated value corrected for symbol
* spread and frequency offset */
interp -= mFineFrequencyCorrection;
interp_p1 -= mFineFrequencyCorrection;
/* Correct output for symbol deviation (spread) */
float output = (float)( 2.0 * interp / mSymbolSpread );
/* Detect received symbol error: basically use a hard decision and
* subtract off expected position nominal symbol level which will be
* +/- 0.5 * symbol spread and +/- 1.5 symbol spread. Remember that
* nominal symbol spread will be 2.0 */
double symbolError;
if( interp < -mSymbolSpread )
{
/* symbol is -3: Expected at -1.5 * symbol spread */
symbolError = interp + ( 1.5 * mSymbolSpread );
mSymbolSpread -= ( symbolError * 0.5 * K_SYMBOL_SPREAD );
}
else if( interp < 0.0 )
{
/* symbol is -1: Expected at -0.5 * symbol_spread */
symbolError = interp + (0.5 * mSymbolSpread );
mSymbolSpread -= ( symbolError * K_SYMBOL_SPREAD );
}
else if( interp < mSymbolSpread )
{
/* symbol is +1: Expected at +0.5 * symbol_spread */
symbolError = interp - ( 0.5 * mSymbolSpread );
mSymbolSpread += ( symbolError * K_SYMBOL_SPREAD );
}
else
{
/* symbol is +3: Expected at +1.5 * symbol_spread */
symbolError = interp - ( 1.5 * mSymbolSpread );
mSymbolSpread += ( symbolError * 0.5 * K_SYMBOL_SPREAD );
}
/* Symbol clock tracking loop adjustment */
if( interp_p1 < interp )
{
mSymbolClock += symbolError * K_SYMBOL_TIMING;
}
else
{
mSymbolClock -= symbolError * K_SYMBOL_TIMING;
}
if( mSymbolSpread < SYMBOL_SPREAD_MIN )
{
mGainController.increase();
mSymbolSpread = SYMBOL_SPREAD_MIN;
}
else if( mSymbolSpread > SYMBOL_SPREAD_MAX )
{
mGainController.decrease();
mSymbolSpread = SYMBOL_SPREAD_MAX;
}
mCoarseFrequencyCorrection += ( ( mFineFrequencyCorrection -
mCoarseFrequencyCorrection ) * K_COARSE_FREQUENCY );
mFineFrequencyCorrection += ( symbolError * K_FINE_FREQUENCY );
/* send frequency correction */
if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_DEADBAND )
{
int adjustment = 500 * ( mCoarseFrequencyCorrection > 0 ? 1 : -1 );
mFrequencyCorrectionControl.adjust( adjustment );
/* reset internal frequency error tracker */
mCoarseFrequencyCorrection = 0;
}
if( mSymbolSpreadTap != null )
{
mSymbolSpreadTap.receive( mSymbolSpread );
}
/* dispatch the interpolated value to the listener */
if( mListener != null )
{
mListener.receive( output );
}
}
}
/**
* Listener interface for frequency correction reset events. We reset the
* coarse and fine frequency control values when the tuner channel source
* has changed frequency or frequency offset.
*/
@Override
public void resetFrequencyCorrection()
{
mReset = true;
}
@Override
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
@Override
public void removeListener( RealSampleListener listener )
{
mListener = null;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
/*
* Since the target is 2.0, we subtract 2.0 from the value so that
* a good value is plotted right on the zero center line
*/
mAvailableTaps.add(
new AdditiveFloatTap( INSTRUMENT_SYMBOL_SPREAD, 0, 0.1f, -2.0f ) );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = (FloatTap)tap;
}
}
@Override
public void removeTap( Tap tap )
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = null;
}
}
}

View File

@@ -0,0 +1,97 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package module.decode.p25;
import javax.xml.bind.annotation.XmlAttribute;
import module.decode.DecoderType;
import module.decode.config.DecodeConfiguration;
import module.decode.p25.P25Decoder.Modulation;
public class DecodeConfigP25Phase1 extends DecodeConfiguration
{
private P25_LSMDecoder.Modulation mModulation = Modulation.C4FM;
private int mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
public DecodeConfigP25Phase1()
{
super( DecoderType.P25_PHASE1 );
setAFC( false );
}
public boolean supportsAFC()
{
return false;
}
@XmlAttribute( name = "modulation" )
public P25_LSMDecoder.Modulation getModulation()
{
return mModulation;
}
public void setModulation( P25_LSMDecoder.Modulation modulation )
{
mModulation = modulation;
}
@XmlAttribute( name="call_timeout" )
public int getCallTimeout()
{
return mCallTimeout;
}
/**
* Sets the call timeout value in seconds ( 10 - 600 );
* @param timeout
*/
public void setCallTimeout( int timeout )
{
if( CALL_TIMEOUT_MINIMUM <= timeout && timeout <= CALL_TIMEOUT_MAXIMUM )
{
mCallTimeout = timeout;
}
else
{
mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
}
}
@XmlAttribute( name="traffic_channel_pool_size" )
public int getTrafficChannelPoolSize()
{
return mTrafficChannelPoolSize;
}
/**
* Sets the traffic channel pool size which is the maximum number of
* simultaneous traffic channels that can be allocated.
*
* This limits the maximum calls so that busy systems won't cause more
* traffic channels to be allocated than the decoder/software/host computer
* can support.
*/
public void setTrafficChannelPoolSize( int size )
{
mTrafficChannelPoolSize = size;
}
}

View File

@@ -0,0 +1,154 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package module.decode.p25;
import module.decode.DecoderType;
import module.decode.event.CallEvent;
import alias.Alias;
import alias.AliasList;
public class P25CallEvent extends CallEvent
{
private String mChannel;
private long mFrequency;
public P25CallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String channel,
long frequency,
String details )
{
super( DecoderType.P25_PHASE1, callEventType,
aliasList, fromID, toID, details );
mChannel = channel;
mFrequency = frequency;
}
private Alias getAlias( String talkgroup )
{
if( hasAliasList() )
{
return getAliasList().getTalkgroupAlias( talkgroup );
}
return null;
}
@Override
public Alias getFromIDAlias()
{
return getAlias( getFromID() );
}
@Override
public Alias getToIDAlias()
{
return getAlias( getToID() );
}
@Override
public String getChannel()
{
return mChannel;
}
@Override
public long getFrequency()
{
return mFrequency;
}
public static class Builder
{
/* Required parameters */
private CallEventType mCallEventType;
/* Optional parameters */
private AliasList mAliasList;
private String mFromID;
private String mToID;
private String mDetails;
private String mChannel;
private long mFrequency;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder from( String val )
{
mFromID = val;
return this;
}
public Builder channel( String channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( long frequency )
{
mFrequency = frequency;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public P25CallEvent build()
{
return new P25CallEvent( this );
}
}
/**
* Private constructor for the builder
*/
private P25CallEvent( Builder builder )
{
this( builder.mCallEventType,
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mChannel,
builder.mFrequency,
builder.mDetails );
}
}

View File

@@ -0,0 +1,65 @@
package module.decode.p25;
import instrument.Instrumentable;
import module.decode.Decoder;
import module.decode.DecoderType;
import source.tuner.frequency.IFrequencyCorrectionController;
import alias.AliasList;
import controller.channel.Channel.ChannelType;
public abstract class P25Decoder extends Decoder
implements IFrequencyCorrectionController, Instrumentable
{
private P25MessageProcessor mMessageProcessor;
public P25Decoder( AliasList aliasList, ChannelType channelType )
{
super( new P25DecoderState( aliasList, channelType ) );
mMessageProcessor = new P25MessageProcessor( aliasList );
mMessageProcessor.setMessageListener( mMessageBroadcaster );
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.P25_PHASE1;
}
protected P25MessageProcessor getMessageProcessor()
{
return mMessageProcessor;
}
public abstract Modulation getModulation();
public enum Modulation
{
CQPSK( "Simulcast (LSM)", "LSM" ),
C4FM( "Normal (C4FM)", "C4FM" );
private String mLabel;
private String mShortLabel;
private Modulation( String label, String shortLabel )
{
mLabel = label;
mShortLabel = shortLabel;
}
public String getLabel()
{
return mLabel;
}
public String getShortLabel()
{
return mShortLabel;
}
public String toString()
{
return getLabel();
}
}
}

View File

@@ -0,0 +1,200 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package module.decode.p25;
import java.awt.EventQueue;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import module.decode.state.State;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
public class P25DecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol;
private JLabel mFrom = new JLabel( " " );
private JLabel mFromAlias = new JLabel( " " );
private JLabel mNAC = new JLabel( "NAC:" );
private JLabel mTo = new JLabel( " " );
private JLabel mToAlias = new JLabel( " " );
private JLabel mSystem = new JLabel( "SYS:" );
private JLabel mSite = new JLabel( "Site:" );
private JLabel mSiteAlias = new JLabel( "" );
public P25DecoderPanel( SettingsManager settingsManager, P25Decoder decoder )
{
super( settingsManager, decoder );
mProtocol = new JLabel( "P25-1 " + decoder.getModulation().getShortLabel() );
init();
}
public void init()
{
super.init();
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mFrom.setFont( mFontDecoder );
mFrom.setForeground( mColorLabelDecoder );
mFromAlias.setFont( mFontDecoder );
mFromAlias.setForeground( mColorLabelDecoder );
mNAC.setFont( mFontDetails );
mNAC.setForeground( mColorLabelDecoder );
mTo.setFont( mFontDecoder );
mTo.setForeground( mColorLabelDecoder );
mToAlias.setFont( mFontDecoder );
mToAlias.setForeground( mColorLabelDecoder );
mSystem.setFont( mFontDetails );
mSystem.setForeground( mColorLabelDecoder );
mSiteAlias.setFont( mFontDecoder );
mSiteAlias.setForeground( mColorLabelDecoder );
mSite.setFont( mFontDetails );
mSite.setForeground( mColorLabelDecoder );
add( mProtocol );
add( mFrom );
add( mFromAlias, "wrap" );
add( mNAC );
add( mTo );
add( mToAlias, "wrap" );
add( mSystem );
add( mSite );
add( mSiteAlias, "wrap" );
}
private P25DecoderState getDecoderState()
{
return (P25DecoderState)getDecoder().getDecoderState();
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
final P25DecoderState state = getDecoderState();
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case NAC:
mNAC.setText( "NAC:" + state.getNAC() );
break;
case SYSTEM:
mSystem.setText( "SYS:" + state.getSystem() );
break;
case SITE:
mSite.setText( "SITE:" + state.getSite() );
break;
case SITE_ALIAS:
mSiteAlias.setText( state.getSiteAlias() );
break;
case FROM_TALKGROUP:
mFrom.setText( state.getFromTalkgroup() );
break;
case FROM_TALKGROUP_ALIAS:
Alias from = state.getFromAlias();
if( from != null )
{
mFromAlias.setText( from.getName() );
mFromAlias.setIcon( mSettingsManager
.getImageIcon( from.getIconName(), 12 ) );
}
else
{
mFromAlias.setText( null );
mFromAlias.setIcon( null );
}
break;
case TO_TALKGROUP:
mTo.setText( state.getToTalkgroup() );
break;
case TO_TALKGROUP_ALIAS:
Alias to = state.getToAlias();
if( to != null )
{
mToAlias.setText( to.getName() );
mToAlias.setIcon( mSettingsManager
.getImageIcon( to.getIconName(), 12 ) );
}
else
{
mToAlias.setText( null );
mToAlias.setIcon( null );
}
break;
}
repaint();
}
});
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
break;
default:
break;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package module.decode.p25;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import module.decode.DecodeEditor;
import module.decode.config.DecodeConfiguration;
import module.decode.p25.P25Decoder.Modulation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.SourceEditor;
import source.tuner.TunerEditor;
import controller.Editor;
import controller.channel.ChannelValidationException;
public class P25Editor extends DecodeEditor
{
private final static Logger mLog = LoggerFactory.getLogger( P25Editor.class );
private static final long serialVersionUID = 1L;
private JComboBox<P25_LSMDecoder.Modulation> mComboModulation;
public P25Editor( DecodeConfiguration config )
{
super( config );
initGUI();
}
private void initGUI()
{
mComboModulation = new JComboBox<P25_LSMDecoder.Modulation>();
mComboModulation.setModel( new DefaultComboBoxModel<P25_LSMDecoder.Modulation>(
P25_LSMDecoder.Modulation.values() ) );
mComboModulation.setSelectedItem( ((DecodeConfigP25Phase1)mConfig).getModulation() );
mComboModulation.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
P25_LSMDecoder.Modulation selected = mComboModulation
.getItemAt( mComboModulation.getSelectedIndex() );
if( selected != null )
{
((DecodeConfigP25Phase1)mConfig).setModulation( selected );
}
}
} );
add( new JLabel( "Modulation:" ) );
add( mComboModulation, "wrap" );
}
/**
* Enforce source=tuner for CQPSK modulation
*/
@Override
public void validate( Editor editor ) throws ChannelValidationException
{
if( editor instanceof SourceEditor &&
((DecodeConfigP25Phase1)mConfig).getModulation() == Modulation.CQPSK )
{
if( !( editor instanceof TunerEditor ) )
{
throw new ChannelValidationException(
"<html><body width='175'><h1>LSM Simulcast</h1>"
+ "<p>P25 LSM Simulcast decoder can only be used with "
+ "a tuner source. Please change the Source to use a tuner"
+ " or change the P25 Decoder to C4FM modulation" );
}
}
}
}

View File

@@ -0,0 +1,161 @@
package module.decode.p25;
import java.util.BitSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import bits.BinaryMessage;
/**
* Utility class to process interleave of P25 Voice and Data messages.
*/
public class P25Interleave
{
private final static Logger mLog =
LoggerFactory.getLogger( P25Interleave.class );
private static int[] DATA_INTERLEAVE = new int[] { 0,1,2,3,52,53,54,55,100,
101,102,103,148,149,150,151,4,5,6,7,56,57,58,59,104,105,106,107,152,153,
154,155,8,9,10,11,60,61,62,63,108,109,110,111,156,157,158,159,12,13,14,
15,64,65,66,67,112,113,114,115,160,161,162,163,16,17,18,19,68,69,70,71,
116,117,118,119,164,165,166,167,20,21,22,23,72,73,74,75,120,121,122,123,
168,169,170,171,24,25,26,27,76,77,78,79,124,125,126,127,172,173,174,175,
28,29,30,31,80,81,82,83,128,129,130,131,176,177,178,179,32,33,34,35,84,
85,86,87,132,133,134,135,180,181,182,183,36,37,38,39,88,89,90,91,136,
137,138,139,184,185,186,187,40,41,42,43,92,93,94,95,140,141,142,143,188,
189,190,191,44,45,46,47,96,97,98,99,144,145,146,147,192,193,194,195,48,
49,50,51 };
private static int[] DATA_DEINTERLEAVE = new int[] { 0,1,2,3,16,17,18,19,32,
33,34,35,48,49,50,51,64,65,66,67,80,81,82,83,96,97,98,99,112,113,114,115,
128,129,130,131,144,145,146,147,160,161,162,163,176,177,178,179,192,193,
194,195,4,5,6,7,20,21,22,23,36,37,38,39,52,53,54,55,68,69,70,71,84,85,
86,87,100,101,102,103,116,117,118,119,132,133,134,135,148,149,150,151,
164,165,166,167,180,181,182,183,8,9,10,11,24,25,26,27,40,41,42,43,56,57,
58,59,72,73,74,75,88,89,90,91,104,105,106,107,120,121,122,123,136,137,
138,139,152,153,154,155,168,169,170,171,184,185,186,187,12,13,14,15,28,
29,30,31,44,45,46,47,60,61,62,63,76,77,78,79,92,93,94,95,108,109,110,
111,124,125,126,127,140,141,142,143,156,157,158,159,172,173,174,175,188,
189,190,191 };
private static int[] VOICE_INTERLEAVE = new int[] { 0,24,48,72,96,120,25,
1,73,49,121,97,2,26,50,74,98,122,27,3,75,51,123,99,4,28,52,76,100,124,
29,5,77,53,125,101,6,30,54,78,102,126,31,7,79,55,127,103,8,32,56,80,104,
128,33,9,81,57,129,105,10,34,58,82,106,130,35,11,83,59,131,107,12,36,60,
84,108,132,37,13,85,61,133,109,14,38,62,86,110,134,39,87,15,63,135,111,
16,40,64,88,112,136,41,17,89,65,137,113,18,42,66,90,114,138,43,19,91,
67,139,115,20,44,68,92,116,140,45,21,93,69,141,117,22,46,70,94,119,142,
47,23,95,71,143,118 };
private static int[] VOICE_DEINTERLEAVE = new int[] { 0,7,12,19,24,31,36,43,
48,55,60,67,72,79,84,91,96,103,108,115,120,127,132,139,1,6,13,18,25,30,
37,42,49,54,61,66,73,78,85,90,97,102,109,114,121,126,133,138,2,9,14,21,
26,33,38,45,50,57,62,69,74,81,86,93,98,105,110,117,122,129,134,141,3,8,
15,20,27,32,39,44,51,56,63,68,75,80,87,92,99,104,111,116,123,128,135,
140,4,11,16,23,28,35,40,47,52,59,64,71,76,83,88,95,100,107,112,119,124,
131,136,143,5,10,17,22,29,34,41,46,53,58,65,70,77,82,89,94,101,106,113,
118,125,130,137,142 };
/**
* Deinterleaves the 196-bit block in message, identified by start and end
* bit positions. Note: end index (exclusive) should be one more than the
* last bit in the block.
*
* @param message - source message to deinterleave
* @param start - starting bit index for the block
* @param end - ending bit index for the block, plus 1
*/
public static BinaryMessage deinterleaveData( BinaryMessage message,
int start, int end )
{
return deinterleave( DATA_DEINTERLEAVE, message, start, end );
}
public static BinaryMessage deinterleaveVoice( BinaryMessage message,
int start, int end )
{
return deinterleave( VOICE_DEINTERLEAVE, message, start, end );
}
public static BinaryMessage deinterleave( int[] pattern, BinaryMessage message,
int start, int end )
{
BitSet original = message.get( start, end );
/* Clear block bits in source message */
message.clear( start, end );
/* Iterate only the set bits in the original message and apply
* the deinterleave -- we don't have to evaluate the 0 bits */
for (int i = original.nextSetBit( 0 );
i >= 0 && i < pattern.length;
i = original.nextSetBit( i + 1 ) )
{
message.set( start + pattern[ i ] );
}
return message;
}
/**
* Interleaves the 196-bit block in message, identified by start and end
* bit positions. Note: end index (exclusive) should be one more than the
* last bit in the block.
*
* @param message - source message to interleave
* @param start - starting bit index for the block
* @param end - ending bit index for the block, plus 1
*/
public static BinaryMessage interleaveData( BinaryMessage message,
int start, int end )
{
return interleave( DATA_INTERLEAVE, message, start, end );
}
public static BinaryMessage interleaveVoice( BinaryMessage message,
int start, int end )
{
return interleave( VOICE_INTERLEAVE, message, start, end );
}
public static BinaryMessage interleave( int[] pattern, BinaryMessage message,
int start, int end )
{
BitSet original = message.get( start, end );
/* Clear block bits in source message */
message.clear( start, end );
/* Iterate only the set bits in the original message and apply
* the deinterleave -- we don't have to evaluate the 0 bits */
for (int i = original.nextSetBit( 0 );
i >= 0 && i < pattern.length;
i = original.nextSetBit( i + 1 ) )
{
message.set( start + pattern[ i ] );
}
return message;
}
public static void main( String[] args )
{
int start = 0;
int end = 144;
int set = 50;
String interleaved = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
BinaryMessage b = BinaryMessage.load( interleaved );
b.set( set );
mLog.debug( "INT:" + b.toString() );
P25Interleave.deinterleaveVoice( b, start, end );
mLog.debug( "DEI:" + b.toString() );
mLog.debug( "In: " + set + " Out:" + b.nextSetBit( 0 ) );
}
}

View File

@@ -0,0 +1,692 @@
package module.decode.p25;
import java.util.ArrayList;
import message.Message;
import module.decode.p25.message.P25Message;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.ldu.LDU2Message;
import module.decode.p25.message.ldu.lc.LDULCMessageFactory;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.message.pdu.PDUMessageFactory;
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessageFactory;
import module.decode.p25.message.tdu.TDUMessage;
import module.decode.p25.message.tdu.lc.TDULCMessageFactory;
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
import module.decode.p25.message.tsbk.TSBKMessage;
import module.decode.p25.message.tsbk.TSBKMessageFactory;
import module.decode.p25.reference.DataUnitID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
import bits.BitSetFullException;
import bits.ISyncDetectListener;
import bits.MultiSyncPatternMatcher;
import bits.SoftSyncDetector;
import bits.SyncDetector;
import dsp.psk.CQPSKDemodulator;
import dsp.psk.CQPSKDemodulator2;
import dsp.symbol.Dibit;
import dsp.symbol.FrameSync;
import edac.BCH_63_16_11;
import edac.CRC;
import edac.CRCP25;
public class P25MessageFramer implements Listener<Dibit>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageFramer.class );
/* Determines the threshold for sync pattern soft matching */
private static final int SYNC_MATCH_THRESHOLD = 2;
private static final int SYNC_IN_CALL_THRESHOLD = 4;
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD );
/* Costas Loop phase lock error correction values. A phase lock error of
* 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An
* error of 180 degrees requires a correction of 1/2 of the symbol rate */
public static final double SYMBOL_FREQUENCY = 2.0d * Math.PI * 4800.0d / 48000.0d;
public static final double PHASE_CORRECTION_90_DEGREES = SYMBOL_FREQUENCY / 4.0d;
public static final double PHASE_CORRECTION_180_DEGREES = SYMBOL_FREQUENCY / 2.0d;
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher( 48 );
private ArrayList<P25MessageAssembler> mAssemblers =
new ArrayList<P25MessageAssembler>();
public static final int TSBK_BEGIN = 64;
public static final int TSBK_CRC_START = 144;
public static final int TSBK_END = 260;
public static final int TSBK_DECODED_END = 160;
public static final int PDU0_BEGIN = 64;
public static final int PDU0_CRC_BEGIN = 144;
public static final int PDU0_END = 260;
public static final int PDU1_BEGIN = 160;
public static final int PDU1_END = 356;
public static final int PDU2_BEGIN = 256;
public static final int PDU2_END = 452;
public static final int PDU3_BEGIN = 352;
public static final int PDU3_END = 548;
public static final int PDU3_DECODED_END = 448;
private Listener<Message> mListener;
private AliasList mAliasList;
private Trellis_1_2_Rate mHalfRate = new Trellis_1_2_Rate();
private Trellis_3_4_Rate mThreeQuarterRate = new Trellis_3_4_Rate();
private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11();
/**
* Constructs a P25 message framer to receive a stream of symbols and
* detect the sync pattern then capture the following stream of symbols up
* to the message length, and then broadcast that bit buffer to the registered
* listener.
*/
public P25MessageFramer( AliasList aliasList )
{
mAliasList = aliasList;
mPrimarySyncDetector.setListener( new ISyncDetectListener()
{
@Override
public void syncDetected()
{
for( P25MessageAssembler assembler: mAssemblers )
{
if( !assembler.isActive() )
{
assembler.setActive( true );
break;
}
}
}
} );
mMatcher.add( mPrimarySyncDetector );
/**
* We use two message assemblers to catch any sync detections, so that
* if we have a false trigger then we still have a chance to catch the
* valid message. If we have two false triggers before the arrival of
* the actual message sync, then the third or subsequent real or false
* sync pattern will produce a debug message indicating no assemblers
* are available..
*/
mAssemblers.add( new P25MessageAssembler() );
mAssemblers.add( new P25MessageAssembler() );
}
public P25MessageFramer( AliasList aliasList, CQPSKDemodulator demodulator )
{
this( aliasList );
if( demodulator != null )
{
/* For CQPSK, we include 3 additional sync detectors to watch for and
* correction +/-90 and 180 degree costas loop phase lock errors */
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CCW,
demodulator, PHASE_CORRECTION_90_DEGREES ) );
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CW,
demodulator, -PHASE_CORRECTION_90_DEGREES ) );
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_180,
demodulator, PHASE_CORRECTION_180_DEGREES ) );
}
}
private void dispatch( Message message )
{
if( mListener != null )
{
mListener.receive( message );
}
}
@Override
public void receive( Dibit symbol )
{
for( P25MessageAssembler assembler: mAssemblers )
{
if( assembler.isActive() )
{
assembler.receive( symbol );
if( assembler.complete() )
{
assembler.reset();
}
}
}
mMatcher.receive( symbol.getBit1(), symbol.getBit2() );
}
public void setListener( Listener<Message> listener )
{
mListener = listener;
}
public void removeListener( Listener<Message> listener )
{
mListener = null;
}
private class P25MessageAssembler
{
/* Starting position of the status symbol counter is 24 symbols to
* account for the 48-bit sync pattern which is not included in message */
private int mStatusSymbolPointer = 24;
private BinaryMessage mMessage;
private int mMessageLength;
private boolean mComplete = false;
private boolean mActive = false;
private DataUnitID mDUID = DataUnitID.NID;
public P25MessageAssembler()
{
mMessageLength = mDUID.getMessageLength();
mMessage = new BinaryMessage( mMessageLength );
reset();
}
public void receive( Dibit dibit )
{
if( mActive )
{
if( mStatusSymbolPointer == 35 )
{
mStatusSymbolPointer = 0;
}
else
{
mStatusSymbolPointer++;
try
{
mMessage.add( dibit.getBit1() );
mMessage.add( dibit.getBit2() );
}
catch( BitSetFullException e )
{
mComplete = true;
}
/* Check the message for complete */
if( mMessage.isFull() )
{
checkComplete();
}
}
}
}
public void reset()
{
mDUID = DataUnitID.NID;
mMessage.setSize( mDUID.getMessageLength() );
mMessage.clear();
mStatusSymbolPointer = 24;
mComplete = false;
mActive = false;
}
public void setActive( boolean active )
{
mActive = active;
}
private void setDUID( DataUnitID id )
{
mDUID = id;
mMessageLength = id.getMessageLength();
mMessage.setSize( mMessageLength );
}
private void checkComplete()
{
switch( mDUID )
{
case NID:
mMessage = mNIDDecoder.correctNID( mMessage );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
int value = mMessage.getInt( P25Message.DUID );
DataUnitID duid = DataUnitID.fromValue( value );
if( duid != DataUnitID.UNKN )
{
setDUID( duid );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
break;
case HDU:
mComplete = true;
dispatch( new HDUMessage( mMessage.copy(), mDUID, mAliasList ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case LDU1:
mComplete = true;
LDU1Message ldu1 = new LDU1Message( mMessage.copy(),
mDUID, mAliasList );
/* Convert the LDU1 message into a link control LDU1 message */
dispatch( LDULCMessageFactory.getMessage( ldu1 ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case LDU2:
mComplete = true;
dispatch( new LDU2Message( mMessage.copy(), mDUID, mAliasList ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case PDU0:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU0_BEGIN, PDU0_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU0_BEGIN, PDU0_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
PDU0_BEGIN, PDU0_CRC_BEGIN );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
boolean confirmed = mMessage.get(
PDUMessage.CONFIRMATION_REQUIRED_INDICATOR );
if( confirmed )
{
setDUID( DataUnitID.PDUC );
}
else
{
setDUID( DataUnitID.PDU1 );
}
mMessage.setPointer( PDU1_BEGIN );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case PDU1:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU1_BEGIN, PDU1_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU1_BEGIN, PDU1_END ) )
{
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 1 )
{
mMessage.setSize( PDU2_BEGIN );
PDUMessage pduMessage1 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU1, mAliasList );
dispatch( pduMessage1 );
mComplete = true;
}
else
{
setDUID( DataUnitID.PDU2 );
mMessage.setPointer( PDU2_BEGIN );
}
}
else
{
mComplete = true;
}
break;
case PDU2:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU2_BEGIN, PDU2_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU2_BEGIN, PDU2_END ) )
{
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 2 )
{
mMessage.setSize( PDU3_BEGIN );
PDUMessage pduMessage2 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU2, mAliasList );
dispatch( pduMessage2 );
mComplete = true;
}
else
{
setDUID( DataUnitID.PDU3 );
mMessage.setPointer( PDU3_BEGIN );
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case PDU3:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU3_BEGIN, PDU3_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU3_BEGIN, PDU3_END ) )
{
mMessage.setSize( PDU3_DECODED_END );
PDUMessage pduMessage3 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU3, mAliasList );
dispatch( pduMessage3 );
}
mComplete = true;
break;
case PDUC:
/* De-interleave the latest block*/
P25Interleave.deinterleaveData( mMessage,
mMessage.size() - 196, mMessage.size() );
/* Decode 3/4 rate convolutional encoding from latest block */
if( mThreeQuarterRate.decode( mMessage,
mMessage.size() - 196, mMessage.size() ) )
{
/* Resize the message and adjust the message pointer
* to account for removing 48 + 4 parity bits */
mMessage.setSize( mMessage.size() - 52 );
mMessage.adjustPointer( -52 );
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );
int current = ( mMessage.size() - 160 ) / 144;
if( current < blocks )
{
mMessage.setSize( mMessage.size() + 196 );
mMessageLength = mMessage.size();
}
else
{
PDUConfirmedMessage pducm =
new PDUConfirmedMessage( mMessage.copy(),
mAliasList );
/* Translate into correct subclass */
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
mComplete = true;
}
break;
}
else
{
PDUConfirmedMessage pducm =
new PDUConfirmedMessage( mMessage.copy(),
mAliasList );
/* Translate into correct subclass */
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TDU:
dispatch( new TDUMessage( mMessage.copy(), mDUID, mAliasList ) );
mComplete = true;
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TDULC:
TDULinkControlMessage tdulc = new TDULinkControlMessage(
mMessage.copy(), mDUID, mAliasList );
/* Convert to an appropriate link control message */
tdulc = TDULCMessageFactory.getMessage( tdulc );
dispatch( tdulc );
mComplete = true;
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TSBK1:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer1 = mMessage.copy();
tsbkBuffer1.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage(
tsbkBuffer1, DataUnitID.TSBK1, mAliasList );
if( tsbkMessage1.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK2 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage1 );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TSBK2:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer2 = mMessage.copy();
tsbkBuffer2.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage(
tsbkBuffer2, DataUnitID.TSBK2, mAliasList );
if( tsbkMessage2.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK3 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage2 );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
break;
case TSBK3:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer3 = mMessage.copy();
tsbkBuffer3.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage(
tsbkBuffer3, DataUnitID.TSBK3, mAliasList );
dispatch( tsbkMessage3 );
}
}
mComplete = true;
break;
case UNKN:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) );
break;
default:
mComplete = true;
break;
}
}
public void dispose()
{
mMessage = null;
mHalfRate.dispose();
}
/**
* Flag to indicate when this assembler has received all of the bits it
* is looking for (ie message length), and should then be removed from
* receiving any more bits
*/
public boolean complete()
{
return mComplete;
}
public boolean isActive()
{
return mActive;
}
}
/**
* Sync pattern detector to listen for costas loop phase lock errors and
* apply a phase correction to the costas loop so that we don't miss any
* messages.
*
* When the costas loop locks with a +/- 90 degree or 180 degree phase
* error, the slicer will incorrectly apply the symbol pattern rotated left
* or right by the phase error. However, we can detect these rotated sync
* patterns and apply immediate phase correction so that message processing
* can continue.
*/
public class CostasPhaseErrorDetector extends SyncDetector
{
private CQPSKDemodulator mDemodulator;
private double mCorrection;
public CostasPhaseErrorDetector( FrameSync frameSync,
CQPSKDemodulator demodulator, double correction )
{
super( frameSync.getSync() );
mDemodulator = demodulator;
mCorrection = correction;
setListener( new ISyncDetectListener()
{
@Override
public void syncDetected()
{
mDemodulator.correctPhaseError( mCorrection );
/* Since we detected a sync pattern, start a message assembler */
for( P25MessageAssembler assembler: mAssemblers )
{
if( !assembler.isActive() )
{
assembler.setActive( true );
break;
}
}
}
} );
}
}
}

View File

@@ -0,0 +1,98 @@
package module.decode.p25;
import java.util.HashMap;
import message.Message;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDUMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
public class P25MessageProcessor implements Listener<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageProcessor.class );
private Listener<Message> mMessageListener;
/* Map of up to 16 band identifiers per RFSS. These identifier update
* messages are inserted into any message that conveys channel information
* so that the uplink/downlink frequencies can be calculated */
private HashMap<Integer,IBandIdentifier> mBandIdentifierMap =
new HashMap<Integer,IBandIdentifier>();
private AliasList mAliasList;
public P25MessageProcessor( AliasList aliasList )
{
mAliasList = aliasList;
}
@Override
public void receive( Message message )
{
/**
* Capture frequency band identifier messages and inject them into any
* messages that require band information in order to calculate the
* up-link and down-link frequencies for any numeric channel references
* contained within the message.
*/
if( message.isValid() )
{
/* Insert band identifier update messages into channel-type messages */
if( message instanceof IdentifierReceiver )
{
IdentifierReceiver receiver = (IdentifierReceiver)message;
int[] identifiers = receiver.getIdentifiers();
for( int identifier: identifiers )
{
receiver.setIdentifierMessage( identifier,
mBandIdentifierMap.get( identifier ) );
}
}
/* Store band identifiers so that they can be injected into channel
* type messages */
if( message instanceof IBandIdentifier )
{
IBandIdentifier bandIdentifier = (IBandIdentifier)message;
mBandIdentifierMap.put( bandIdentifier.getIdentifier(),
bandIdentifier );
}
}
/**
* Broadcast all valid messages and any LDU voice messages regardless if
* they are valid or not, so that we don't miss any voice frames
*/
if( mMessageListener != null && message.isValid() || message instanceof LDUMessage )
{
mMessageListener.receive( message );
}
}
public void dispose()
{
mBandIdentifierMap.clear();
mMessageListener = null;
}
public void setMessageListener( Listener<Message> listener )
{
mMessageListener = listener;
}
public void removeMessageListener()
{
mMessageListener = null;
}
}

View File

@@ -0,0 +1,239 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 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 module.decode.p25;
import instrument.tap.Tap;
import instrument.tap.stream.DibitTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import sample.real.RealBufferToStreamConverter;
import source.tuner.frequency.FrequencyCorrectionControl;
import alias.AliasList;
import controller.channel.Channel.ChannelType;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
import dsp.gain.DirectGainControl;
public class P25_C4FMDecoder extends P25Decoder implements IRealBufferListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
/* Instrumentation Taps */
private static final String INSTRUMENT_FILTER_OUTPUT = "Tap Point: Pre-Filter Output";
private static final String INSTRUMENT_DGC_OUTPUT = "Tap Point: DGC Output";
private static final String INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT = "Tap Point: Symbol Filter Output";
private static final String INSTRUMENT_C4FM_SLICER_OUTPUT = "Tap Point: C4FM Slicer Output";
private final static int MAXIMUM_FREQUENCY_CORRECTION = 3000; //Hertz, +/-
private List<Tap> mAvailableTaps;
private RealFIRFilter_RB_RB mC4FMPreFilter;
private DirectGainControl mDGC;
private RealBufferToStreamConverter mStreamConverter =
new RealBufferToStreamConverter();
private FrequencyCorrectionControl mFrequencyCorrectionControl;
private C4FMSymbolFilter mSymbolFilter;
private C4FMSlicer mC4FMSlicer;
private P25MessageFramer mMessageFramer;
/**
* P25 Phase 1 C4FM Decoder processes real buffers of un-filtered,
* demodulated audio and produces decoded P25 Phase 1 Messages.
*
* Provides Frequency Control to steer external tuner source and incorporates
* internal gain control.
*
* Note: use the P25AudioModule to convert the decoded messages into audio.
*
* @param aliasList - optional (can be null) list of alias values for network
* infrastructure and subscriber identities that will be included in each
* decoded message
*/
public P25_C4FMDecoder( AliasList aliasList, ChannelType channelType )
{
super( aliasList, channelType );
/* Filter demodulated sample buffers */
mC4FMPreFilter = new RealFIRFilter_RB_RB( FilterFactory.getLowPass(
48000, 6750, 7500, 60, WindowType.HANNING, true ), 1.0f );
/* Convert buffers to a sample stream so that the symbol filter can
* provide fine-grained gain control over individual samples */
mC4FMPreFilter.setListener( mStreamConverter );
/* Apply gain remotely controlled by the downstream symbol filter */
mDGC = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
mStreamConverter.setListener( mDGC );
/* Issue tuned frequency correction commands, remotely controlled by the
* downstream symbol filter */
mFrequencyCorrectionControl = new FrequencyCorrectionControl(
MAXIMUM_FREQUENCY_CORRECTION );
/* Shape gain and frequency offsets to optimize sample stream */
mSymbolFilter = new C4FMSymbolFilter( mDGC, mFrequencyCorrectionControl );
mDGC.setListener( mSymbolFilter );
/* Convert samples to symbols */
mC4FMSlicer = new C4FMSlicer();
mSymbolFilter.setListener( mC4FMSlicer );
/* Sync pattern detection and message construction */
mMessageFramer = new P25MessageFramer( aliasList );
mC4FMSlicer.addListener( mMessageFramer );
/* Process and broadcast messages */
mMessageFramer.setListener( getMessageProcessor() );
}
public Modulation getModulation()
{
return Modulation.C4FM;
}
@Override
public boolean hasFrequencyCorrectionControl()
{
return mFrequencyCorrectionControl != null;
}
@Override
public FrequencyCorrectionControl getFrequencyCorrectionControl()
{
return mFrequencyCorrectionControl;
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
return mC4FMPreFilter;
}
/**
* Instrumentation taps for monitoring internal processing
*/
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add( new FloatTap( INSTRUMENT_FILTER_OUTPUT, 0, 1.0f ) );
mAvailableTaps.add( new FloatTap( INSTRUMENT_DGC_OUTPUT, 0, 1.0f ) );
if( mSymbolFilter != null )
{
mAvailableTaps.addAll( mSymbolFilter.getTaps() );
}
mAvailableTaps.add( new FloatTap( INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT, 0, 0.1f ) );
mAvailableTaps.add( new DibitTap( INSTRUMENT_C4FM_SLICER_OUTPUT, 0, 0.1f ) );
}
return mAvailableTaps;
}
/**
* Adds an instrumentation tap to monitor internal processing.
*/
@Override
public void addTap( Tap tap )
{
if( mSymbolFilter != null )
{
mSymbolFilter.addTap( tap );
}
switch( tap.getName() )
{
case INSTRUMENT_FILTER_OUTPUT:
FloatTap filterOutput = (FloatTap)tap;
mStreamConverter.setListener( filterOutput );
filterOutput.setListener( mDGC );
break;
case INSTRUMENT_DGC_OUTPUT:
FloatTap dgcTap = (FloatTap)tap;
mDGC.setListener( dgcTap );
dgcTap.setListener( mSymbolFilter );
break;
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
FloatTap symbolTap = (FloatTap)tap;
mSymbolFilter.setListener( symbolTap );
symbolTap.setListener( mC4FMSlicer );
break;
case INSTRUMENT_C4FM_SLICER_OUTPUT:
DibitTap slicerTap = (DibitTap)tap;
if( mC4FMSlicer != null )
{
mC4FMSlicer.addListener( slicerTap );
}
break;
default:
throw new IllegalArgumentException( "Unrecognized tap: " +
tap.getName() );
}
}
/**
* Removes the instrumentation tap.
*/
@Override
public void removeTap( Tap tap )
{
if( mSymbolFilter != null )
{
mSymbolFilter.removeTap( tap );
}
switch( tap.getName() )
{
case INSTRUMENT_FILTER_OUTPUT:
mStreamConverter.setListener( mDGC );
break;
case INSTRUMENT_DGC_OUTPUT:
mDGC.setListener( mSymbolFilter );
break;
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
mSymbolFilter.setListener( mC4FMSlicer );
break;
case INSTRUMENT_C4FM_SLICER_OUTPUT:
DibitTap slicerTap = (DibitTap)tap;
if( mC4FMSlicer != null )
{
mC4FMSlicer.removeListener( slicerTap );
}
break;
default:
throw new IllegalArgumentException( "Unrecognized tap: " +
tap.getName() );
}
}
}

View File

@@ -0,0 +1,205 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 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 module.decode.p25;
import instrument.tap.Tap;
import instrument.tap.stream.ComplexSampleTap;
import instrument.tap.stream.ComplexTap;
import instrument.tap.stream.DibitTap;
import instrument.tap.stream.FloatTap;
import instrument.tap.stream.QPSKTap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import controller.channel.Channel.ChannelType;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexBufferToStreamConverter;
import sample.complex.IComplexBufferListener;
import source.tuner.frequency.FrequencyCorrectionControl;
import alias.AliasList;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.gain.ComplexFeedForwardGainControl;
import dsp.psk.CQPSKDemodulator;
import dsp.psk.QPSKPolarSlicer;
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25_LSMDecoder.class );
/* Instrumentation Taps */
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
private static final String INSTRUMENT_QPSK_DEMODULATOR_OUTPUT = "Tap Point: CQPSK Demodulator Output";
private static final String INSTRUMENT_CQPSK_SLICER_OUTPUT = "Tap Point: CQPSK Slicer Output";
private List<Tap> mAvailableTaps;
private ComplexFIRFilter_CB_CB mBasebandFilter;
private ComplexBufferToStreamConverter mStreamConverter =
new ComplexBufferToStreamConverter();
private ComplexFeedForwardGainControl mAGC =
new ComplexFeedForwardGainControl( 32 );
private CQPSKDemodulator mCQPSKDemodulator = new CQPSKDemodulator();
private QPSKPolarSlicer mCQPSKSlicer = new QPSKPolarSlicer();
private P25MessageFramer mMessageFramer;
public P25_LSMDecoder( AliasList aliasList, ChannelType channelType )
{
super( aliasList, channelType );
mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass(
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0f );
mBasebandFilter.setListener( mStreamConverter );
mStreamConverter.setListener( mAGC );
mAGC.setListener( mCQPSKDemodulator );
mCQPSKDemodulator.setSymbolListener( mCQPSKSlicer );
mCQPSKSlicer.addListener( mMessageFramer );
mMessageFramer = new P25MessageFramer( aliasList, mCQPSKDemodulator );
mCQPSKSlicer.addListener( mMessageFramer );
mMessageFramer.setListener( getMessageProcessor() );
}
@Override
public Listener<ComplexBuffer> getComplexBufferListener()
{
return mBasebandFilter;
}
public Modulation getModulation()
{
return Modulation.CQPSK;
}
@Override
public boolean hasFrequencyCorrectionControl()
{
return false;
}
@Override
public FrequencyCorrectionControl getFrequencyCorrectionControl()
{
return null;
}
/**
* Provides a list of instrumentation taps for monitoring internal processing
*/
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add( new ComplexTap( INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f ) );
mAvailableTaps.add( new ComplexTap( INSTRUMENT_AGC_OUTPUT, 0, 1.0f ) );
mAvailableTaps.add( new QPSKTap( INSTRUMENT_QPSK_DEMODULATOR_OUTPUT, 0, 1.0f ) );
mAvailableTaps.add( new DibitTap( INSTRUMENT_CQPSK_SLICER_OUTPUT, 0, 0.1f ) );
if( mCQPSKDemodulator != null )
{
mAvailableTaps.addAll( mCQPSKDemodulator.getTaps() );
}
}
return mAvailableTaps;
}
/**
* Adds the instrumentation tap
*/
@Override
public void addTap( Tap tap )
{
if( mCQPSKDemodulator != null )
{
mCQPSKDemodulator.addTap( tap );
}
switch( tap.getName() )
{
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
ComplexSampleTap baseband = (ComplexSampleTap)tap;
mStreamConverter.setListener( baseband );
baseband.setListener( mAGC );
break;
case INSTRUMENT_AGC_OUTPUT:
ComplexSampleTap agcSymbol = (ComplexSampleTap)tap;
mAGC.setListener( agcSymbol );
agcSymbol.setListener( mCQPSKDemodulator );
break;
case INSTRUMENT_QPSK_DEMODULATOR_OUTPUT:
QPSKTap qpsk = (QPSKTap)tap;
mCQPSKDemodulator.setSymbolListener( qpsk );
qpsk.setListener( mCQPSKSlicer );
break;
case INSTRUMENT_CQPSK_SLICER_OUTPUT:
mCQPSKSlicer.addListener( (DibitTap)tap );
break;
default:
throw new IllegalArgumentException( "Unrecognized tap: " +
tap.getName() );
}
}
/**
* Removes the instrumentation tap
*/
@Override
public void removeTap( Tap tap )
{
if( mCQPSKDemodulator != null )
{
mCQPSKDemodulator.removeTap( tap );
}
switch( tap.getName() )
{
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
mStreamConverter.setListener( mAGC );
break;
case INSTRUMENT_AGC_OUTPUT:
mAGC.setListener( mCQPSKDemodulator );
break;
case INSTRUMENT_QPSK_DEMODULATOR_OUTPUT:
mCQPSKDemodulator.setSymbolListener( mCQPSKSlicer );
break;
case INSTRUMENT_CQPSK_SLICER_OUTPUT:
mCQPSKSlicer.removeListener( (DibitTap)tap );
break;
default:
throw new IllegalArgumentException( "Unrecognized tap: " +
tap.getName() );
}
}
}

View File

@@ -0,0 +1,534 @@
package module.decode.p25;
import java.util.ArrayList;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import bits.BinaryMessage;
import dsp.symbol.Dibit;
public class Trellis_1_2_Rate
{
public final static int MAX_ERROR_THRESHOLD = 7;
private final static Logger mLog =
LoggerFactory.getLogger( Trellis_1_2_Rate.class );
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 Trellis_1_2_Rate()
{
/* 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();
}
private String constellationsToString( String label )
{
StringBuilder sb = new StringBuilder();
sb.append( label );
for( ConstellationNode node: mConstellationNodes )
{
sb.append( " " + node.getConstellation().name() );
}
return sb.toString();
}
private String inputsToString()
{
StringBuilder sb = new StringBuilder();
sb.append( " INPUTS: " );
for( ConstellationNode node: mConstellationNodes )
{
sb.append( " " );
sb.append( ( node.getConstellation().getInput().getBit1() ? "1" : "0" ) );
sb.append( ( node.getConstellation().getInput().getBit2() ? "1" : "0" ) );
}
return sb.toString();
}
public boolean decode( BinaryMessage message, int start, int end )
{
/* load each of the nodes with de-interleaved constellations */
for( int index = 0; index < 49; index++ )
{
Constellation c = getConstellation( message, start + index * 4 );
mConstellationNodes.get( index ).setConstellation( c );
}
/* test to see if constellations are correct - otherwise correct them */
ConstellationNode firstNode = mConstellationNodes.get( 0 );
int errorCount = firstNode.getErrorCount();
if( errorCount > 0 )
{
if( errorCount < MAX_ERROR_THRESHOLD )
{
/* repair the errors */
firstNode.correctTo( Dibit.D00_PLUS_1 );
}
else
{
return false;
}
}
/* clear constellations from original message */
message.clear( start, end );
/* replace with decoded values from the constellation nodes */
for( int index = 0; index < 49; index++ )
{
ConstellationNode node = mConstellationNodes.get( index );
Dibit input = node.getInputDibit();
if( input.getBit1() )
{
message.set( start + ( index * 2 ) );
}
if( input.getBit2() )
{
message.set( start + ( index * 2 ) + 1 );
}
}
return true;
}
private Constellation getConstellation( BinaryMessage message, int index )
{
int transmittedValue = 0;
for( int x = 0; x < 4; x++ )
{
if( message.get( index + x ) )
{
transmittedValue += ( 1 << ( 3 - x ) );
}
}
return Constellation.fromTransmittedValue( transmittedValue );
}
public class ConstellationNode
{
private ConstellationNode mConnectedNode;
private Constellation mConstellation;
private boolean mCorrect;
public ConstellationNode()
{
}
public void dispose()
{
mConnectedNode = null;
}
public Constellation getConstellation()
{
return mConstellation;
}
public Dibit getInputDibit()
{
return mConstellation.getInput();
}
public boolean hasStateDibit( Dibit dibit )
{
return mConstellation.getState() == dibit;
}
/**
* 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 stateDibit to use for the left side.
*/
public void correctTo( Dibit stateDibit )
{
if( mCorrect && mConstellation.getState() == stateDibit )
{
return;
}
if( isCurrentConnectionCorrect() )
{
mConstellation = Constellation.fromStateAndInputDibits( stateDibit,
mConstellation.getInput() );
mCorrect = true;
if( mConnectedNode != null )
{
/* the next node's state was this node's input */
mConnectedNode.correctTo( mConstellation.getInput() );
}
}
else
{
Constellation cheapestConstellation = mConstellation;
int cheapestCost = 100; //arbitrary
for( Dibit testInput: Dibit.values() )
{
Constellation testConstellation = Constellation
.fromStateAndInputDibits( stateDibit, testInput );
int testCost = mConstellation.costTo( testConstellation ) +
mConnectedNode.costTo( testInput );
if( testCost < cheapestCost )
{
cheapestCost = testCost;
cheapestConstellation = testConstellation;
}
}
mConstellation = cheapestConstellation;
mConnectedNode.correctTo( mConstellation.getInput() );
mCorrect = true;
}
}
/**
* Calculates the cost (hamming distance) of using the argument as the
* state dibit for the current node, and recursively finding the
* cheapest corresponding input dibit.
*
* @param stateTest
* @return
*/
public int costTo( Dibit stateTest )
{
if( isCurrentConnectionCorrect() )
{
Constellation c = Constellation.fromStateAndInputDibits(
stateTest, mConstellation.getInput() );
return mConstellation.costTo( c );
}
else
{
int cheapestCost = 100; //arbitrary
for( Dibit inputTest: Dibit.values() )
{
Constellation constellationTest =
Constellation.fromStateAndInputDibits( stateTest, inputTest );
int cost = mConnectedNode.costTo( inputTest ) +
mConstellation.costTo( constellationTest );
if( cost < cheapestCost )
{
cheapestCost = cost;
}
}
return cheapestCost;
}
}
/**
* Indicates if the immediate connection to the right is correct
*/
public boolean isCurrentConnectionCorrect()
{
return ( mConnectedNode == null ||
mConstellation.getInput() == mConnectedNode.getStateDibit() );
}
/**
* 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 int getErrorCount()
{
if( mConnectedNode == null )
{
mCorrect = true;
return 0;
}
mCorrect = mConstellation.getInput() == mConnectedNode.getStateDibit();
return mConnectedNode.getErrorCount() + ( mCorrect ? 0 : 1 );
}
public Dibit getStateDibit()
{
return mConstellation.getState();
}
public void setConstellation( Constellation constellation )
{
mConstellation = constellation;
mCorrect = false;
}
public void connect( ConstellationNode node )
{
mConnectedNode = node;
}
}
public enum Constellation
{
/* State (previous), Input (next), Transmit 1, Transmit 2 */
CB( Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, 0 ),
CC( Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, 1 ),
C0( Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, 2 ),
C7( Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, 3 ),
CE( Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, 4 ),
C9( Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, 5 ),
C5( Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, 6 ),
C2( Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, 7 ),
CA( Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, 8 ),
CD( Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, 9 ),
C1( Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, 10 ),
C6( Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, 11 ),
CF( Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, 12 ),
C8( Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, 13 ),
C4( Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, 14 ),
C3( Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, 15 );
private Dibit mStateDibit;
private Dibit mInputDibit;
private Dibit mTransmitDibit1;
private Dibit mTransmitDibit2;
private int mTransmittedValue;
private Constellation( Dibit stateDibit,
Dibit inputDibit,
Dibit transmitDibit1,
Dibit transmitDibit2,
int transmittedValue )
{
mStateDibit = stateDibit;
mInputDibit = inputDibit;
mTransmitDibit1 = transmitDibit1;
mTransmitDibit2 = transmitDibit2;
mTransmittedValue = transmittedValue;
}
public Dibit getState()
{
return mStateDibit;
}
public Dibit getInput()
{
return mInputDibit;
}
public Dibit getTransmitDibit1()
{
return mTransmitDibit1;
}
public Dibit getTransmitDibit2()
{
return mTransmitDibit2;
}
public int getTransmittedValue()
{
return mTransmittedValue;
}
public static Constellation fromTransmittedValue( int value )
{
if( 0 <= value && value <= 15 )
{
return values()[ value ];
}
return null;
}
public static Constellation fromTransmittedDibits( Dibit left, Dibit right )
{
return fromTransmittedValue( left.getHighValue() + right.getLowValue() );
}
public static Constellation fromStateAndInputDibits( Dibit state, Dibit input )
{
switch( state )
{
case D00_PLUS_1:
switch( input )
{
case D00_PLUS_1:
return C0;
case D01_PLUS_3:
return CF;
case D10_MINUS_1:
return CC;
case D11_MINUS_3:
return C3;
}
break;
case D01_PLUS_3:
switch( input )
{
case D00_PLUS_1:
return C4;
case D01_PLUS_3:
return CB;
case D10_MINUS_1:
return C8;
case D11_MINUS_3:
return C7;
}
break;
case D10_MINUS_1:
switch( input )
{
case D00_PLUS_1:
return CD;
case D01_PLUS_3:
return C2;
case D10_MINUS_1:
return C1;
case D11_MINUS_3:
return CE;
}
break;
case D11_MINUS_3:
switch( input )
{
case D00_PLUS_1:
return C9;
case D01_PLUS_3:
return C6;
case D10_MINUS_1:
return C5;
case D11_MINUS_3:
return CA;
}
break;
}
/* Should never get to here */
return C0;
}
/**
* 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[ getTransmittedValue() ][ other.getTransmittedValue() ];
}
}
/**
* Test harness
*/
public static void main( String[] args )
{
Random random = new Random();
BinaryMessage buffer = new BinaryMessage( 196 );
try
{
for( int x = 0; x < 196; x++ )
{
int number = random.nextInt( 2 );
if( number == 1 )
{
buffer.add( false );
}
else
{
buffer.add( true );
}
}
}
catch( Exception e )
{
e.printStackTrace();
}
mLog.debug( " BUFFER: " + buffer.toString() );
Trellis_1_2_Rate t = new Trellis_1_2_Rate();
t.decode( buffer, 0, 196 );
mLog.debug( " DECODED: " + buffer.toString() );
mLog.debug( "Finished!" );
}
}

View File

@@ -0,0 +1,658 @@
package module.decode.p25;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import bits.BinaryMessage;
import dsp.symbol.Dibit;
public class Trellis_3_4_Rate
{
private final static Logger mLog =
LoggerFactory.getLogger( Trellis_3_4_Rate.class );
/* Hamming distance (bit match count) between constellation pairs */
private static final int[][] CONSTELLATION_METRICS =
{ { 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 },
{ 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 },
{ 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 },
{ 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 },
{ 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 },
{ 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 },
{ 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 },
{ 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 },
{ 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 },
{ 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 },
{ 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 },
{ 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 },
{ 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 },
{ 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 },
{ 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 },
{ 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 } };
/* Even valued state tribits can only produce even valued constellations
* and odd valued state tribits can only produce odd constellations. */
public static Con[] EVEN_CONSTELLATIONS = new Con[] { Con.C0,Con.C2,Con.C4,
Con.C6,Con.C8,Con.CA,Con.CC,Con.CE };
public static Con[] ODD_CONSTELLATIONS = new Con[] { Con.C1,Con.C3,Con.C5,
Con.C7,Con.C9,Con.CB,Con.CD,Con.CF };
/* Constellation and state tribit lookup map to find the input tribit */
public static HashMap<Con,Tribit[]> INPUT_FROM_CONSTELLATION_MAP;
private ArrayList<Con> mTransmittedConstellations = new ArrayList<Con>();
private ArrayList<Path> mSurvivorPaths = new ArrayList<Path>();
private ArrayList<Path> mNewPaths = new ArrayList<Path>();
private PathMetrics mPathMetrics = new PathMetrics();
/**
* Implements the Viterbi algorithm to decode 3/4 rate trellis encoded 196-bit
* packet data messages.
*/
public Trellis_3_4_Rate()
{
createConstellationToTribitMap();
}
/**
* Creates a lookup map for state to input tribit values for a given
* constellation. Input tribits are contained in the array using the state
* tribit's value as the lookup index. Null values indicate illegal state
* and input combinations for the specified constellation.
*/
private void createConstellationToTribitMap()
{
INPUT_FROM_CONSTELLATION_MAP = new HashMap<Con,Tribit[]>();
INPUT_FROM_CONSTELLATION_MAP.put( Con.CB, new Tribit[] { null, null, Tribit.T5, Tribit.T3, Tribit.T1, Tribit.T7, null, null } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.CC, new Tribit[] { Tribit.T3, Tribit.T1, null, null, null, null, Tribit.T7, Tribit.T5 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C0, new Tribit[] { Tribit.T0, Tribit.T6, null, null, null, null, Tribit.T4, Tribit.T2 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C7, new Tribit[] { null, null, Tribit.T6, Tribit.T4, Tribit.T2, Tribit.T0, null, null } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.CE, new Tribit[] { Tribit.T7, Tribit.T5, null, null, null, null, Tribit.T3, Tribit.T1 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C9, new Tribit[] { null, null, Tribit.T1, Tribit.T7, Tribit.T5, Tribit.T3, null, null } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C5, new Tribit[] { null, null, Tribit.T2, Tribit.T0, Tribit.T6, Tribit.T4, null, null } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C2, new Tribit[] { Tribit.T4, Tribit.T2, null, null, null, null, Tribit.T0, Tribit.T6 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.CA, new Tribit[] { Tribit.T5, Tribit.T3, null, null, null, null, Tribit.T1, Tribit.T7 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.CD, new Tribit[] { null, null, Tribit.T3, Tribit.T1, Tribit.T7, Tribit.T5, null, null } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C1, new Tribit[] { null, null, Tribit.T0, Tribit.T6, Tribit.T4, Tribit.T2, null, null } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C6, new Tribit[] { Tribit.T6, Tribit.T4, null, null, null, null, Tribit.T2, Tribit.T0 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.CF, new Tribit[] { null, null, Tribit.T7, Tribit.T5, Tribit.T3, Tribit.T1, null, null } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C8, new Tribit[] { Tribit.T1, Tribit.T7, null, null, null, null, Tribit.T5, Tribit.T3 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C4, new Tribit[] { Tribit.T2, Tribit.T0, null, null, null, null, Tribit.T6, Tribit.T4 } );
INPUT_FROM_CONSTELLATION_MAP.put( Con.C3, new Tribit[] { null, null, Tribit.T4, Tribit.T2, Tribit.T0, Tribit.T6, null, null } );;
}
/**
* Member object cleanup prior to deleting
*/
public void dispose()
{
reset();
}
/**
* Decodes a 196-bit 3/4 rate convolutional encoded message located between
* start and end indexes and returns the decoded 144-bit message overlayed
* upon the original message with the remaining 52 bits cleared to zero.
*
* @return - original message with decoded message bits..
*/
public boolean decode( BinaryMessage message, int start, int end )
{
reset();
/* Load and decode each of the transmitted constellations */
for( int index = 0; index < 49; index++ )
{
Con c = getConstellation( message, start + index * 4 );
add( c );
}
/* The final decoded survivor path should be at path metrics tribit 0 */
Path path = mPathMetrics.getPath( Tribit.T0 );
/* We should have a path with 50 nodes, counting the starting 000 node
* and the flushing 000 node, otherwise there was an error */
if( path != null && path.getNodes().size() == 50 )
{
/* Clear the original message bits */
message.clear( start, end );
List<Node> nodes = path.getNodes();
/* Load each of the nodes' state tribit values into the original message */
for( int x = 1; x < 50; x++ )
{
message.load( start + ( ( x - 1 ) * 3 ), 3,
nodes.get( x ).getState().getValue() );
}
return true;
}
return false;
}
/**
* Resets the decoder before decoding a new data packet.
*/
private void reset()
{
mTransmittedConstellations.clear();
mNewPaths.clear();
mPathMetrics.reset();
mSurvivorPaths.clear();
/* Tribit 000 is the only legal start point */
mSurvivorPaths.add( new Path( new Node( 0, Tribit.T0, Con.C0 ) ) );
}
private void add( Con con )
{
mTransmittedConstellations.add( con );
/* Add in any newly created survivor paths */
mSurvivorPaths.addAll( mNewPaths );
mNewPaths.clear();
/* Set a culling threshold for any path metrics 3 below the best path */
int survivorThreshold = mPathMetrics.getSurvivorThreshold();
/* Reset path metrics */
mPathMetrics.reset();
/* Add constellation to each survivor path. If a survivor path metric
* falls below the culling threshold, remove it */
Iterator<Path> it = mSurvivorPaths.iterator();
Path path;
while( it.hasNext() )
{
path = it.next();
if( path.isDead() || path.getPathMetric() < survivorThreshold )
{
it.remove();
}
else
{
path.add( con );
}
}
}
private Con getConstellation( BinaryMessage message, int index )
{
int value = message.getInt( index, index + 3 );
return Con.fromTransmittedValue( value );
}
/**
* Path metrics maintains a map of the current best path metrics that end
* at each of the 8 valid tribit values. Each new path is evaluated against
* the current best path for retention (survivor) or disposal (dead). A
* best metric is maintained to evaluate the Walking Dead for disposal.
* Walking dead is a path lagging the best path metric by more than 3.
*/
public class PathMetrics
{
private HashMap<Tribit,Path> mMetrics = new HashMap<Tribit,Path>();
private int mBestMetric = 0;
public PathMetrics()
{
}
public int getSurvivorThreshold()
{
return mBestMetric - 3;
}
/**
* Removes all path metrics
*/
public void reset()
{
mMetrics.clear();
mBestMetric = 0;
}
public int getMetric( Tribit tribit )
{
if( mMetrics.containsKey( tribit ) )
{
return mMetrics.get( tribit ).getPathMetric();
}
return 0;
}
public Path getPath( Tribit tribit )
{
return mMetrics.get( tribit );
}
/**
* Evaluates the path's metric against the current best path metric for
* the final state tribit of the path. If the path's metric is higher
* than the current best path metric, the current path is marked for
* termination and the new path is promoted to best path. If the new
* path's metric is less than the best path metric, then the new path
* metric is marked for termination. If the metrics are equal, the new
* path is allowed to survive.
*
* @param path/metric to evaluate
*/
public void evalute( Path path )
{
Node lastNode = path.getLastNode();
Path bestPath = mMetrics.get( lastNode.getState() );
if( bestPath == null )
{
mMetrics.put( lastNode.getState(), path );
}
else
{
if( path.getPathMetric() < bestPath.getPathMetric() )
{
path.setSurvivor( false );
}
else if( path.getPathMetric() > bestPath.getPathMetric() )
{
mMetrics.put( lastNode.getState(), path );
bestPath.setSurvivor( false );
}
/* Equal metric paths are allowed to survive */
}
/* Capture the best path metric */
if( path.getPathMetric() > mBestMetric )
{
mBestMetric = path.getPathMetric();
}
}
}
/**
* Path is a sequence of nodes where the starting node always contains
* tribit 0 and constellation 0. Path maintains a running path metric
* derived from the sum of each of the node's branch metrics.
*/
public class Path
{
private boolean mSurvivor = true;
private int mPathMetric = 0;
private ArrayList<Node> mNodes = new ArrayList<Node>();
public Path( Node first )
{
mNodes.add( first );
mPathMetric = first.getBranchMetric();
mPathMetrics.evalute( this );
}
public Path( ArrayList<Node> nodes, int metric )
{
mNodes = nodes;
mPathMetric = metric;
}
public List<Node> getNodes()
{
return mNodes;
}
public int getBitErrorCount()
{
return ( getLastNode().getIndex() * 4 ) - mPathMetric;
}
public void setSurvivor( boolean survivor )
{
mSurvivor = survivor;
}
public boolean isSurvivor()
{
return mSurvivor;
}
public boolean isDead()
{
return !mSurvivor;
}
@SuppressWarnings( "unchecked" )
public Path copyOf()
{
return new Path( (ArrayList<Node>)mNodes.clone(), mPathMetric );
}
/**
* Adds the constellation and returns any new paths created as a result
* of the constellation being detected as an errant constellation.
*/
public void add( Con con )
{
Node current = getLastNode();
Tribit input = INPUT_FROM_CONSTELLATION_MAP
.get( con )[ current.getState().getValue() ];
/* A non-null input tribit from the lookup table is valid, otherwise
* create 8 alternate branches from the original path to explore
* where the error occurred. */
if( input != null )
{
add( new Node( mNodes.size(), input, con ) );
}
else
{
/* Create new paths for each of the valid constellations that
* correspond to the parity of the current state tribit */
Con[] constellations = current.getState().getType() ==
Type.EVEN ? EVEN_CONSTELLATIONS : ODD_CONSTELLATIONS;
for( int x = 0; x < 8; x++ )
{
/* Get the input tribit corresponding to the state tribit
* and constellation combination */
Tribit tribit = INPUT_FROM_CONSTELLATION_MAP
.get( constellations[ x ] )[ current.getState().getValue() ];
Node candidate = new Node( mNodes.size(), tribit, constellations[ x ] );
/* If the current path metric plus the candidate node's
* branch metric is equal or better than the current best
* metric for the given tribit, add it as a potential path,
* otherwise discard it */
if( this.getPathMetric() + candidate.getBranchMetric() >=
mPathMetrics.getMetric( tribit ) )
{
/* If this is the final constellation of the set, simply
* add it to the existing path ... */
if( x == 7 )
{
add( candidate );
}
/* ... otherwise create a copy of the current path */
else
{
Path path = this.copyOf();
path.add( candidate );
mNewPaths.add( path );
}
}
}
}
}
public void add( Node node )
{
mNodes.add( node );
mPathMetric += node.getBranchMetric();
mPathMetrics.evalute( this );
}
public int getPathMetric()
{
return mPathMetric;
}
public Node getLastNode()
{
return mNodes.get( mNodes.size() - 1 );
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "PATH [" );
sb.append( mNodes.size() );
sb.append( "-" );
sb.append( mPathMetric );
sb.append( "] " );
for( Node node: mNodes )
{
sb.append( node.getState().name() );
sb.append( "/" );
sb.append( node.getConstellation().name() );
sb.append( " " );
}
sb.append( mSurvivor ? "SURVIVOR" : "DEAD" );
return sb.toString();
}
}
/**
* Trellis node containing the constellation used to produce the node and
* the input tribit for the constellation.
*/
public class Node
{
private int mIndex;
private int mBranchMetric;
private Tribit mTribit;
private Con mConstellation;
public Node( int index, Tribit tribit, Con constellation )
{
mIndex = index;
mTribit = tribit;
mConstellation = constellation;
if( mIndex > 0 )
{
Con transmitted = mTransmittedConstellations.get( index - 1 );
if( transmitted == null )
{
throw new IllegalArgumentException( "Cannot calculate "
+ "metric - transmitted constellation symbol is null" );
}
mBranchMetric = transmitted.getMetricTo( constellation );
}
}
public int getIndex()
{
return mIndex;
}
public Tribit getState()
{
return mTribit;
}
public Con getConstellation()
{
return mConstellation;
}
public int getBranchMetric()
{
return mBranchMetric;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Node " );
sb.append( mIndex );
sb.append( " Metric:" );
sb.append( mBranchMetric );
sb.append( " Con:" );
sb.append( mConstellation.name() );
sb.append( " State:" );
if( mTribit == null )
{
sb.append( "null" );
}
else
{
sb.append( mTribit.name() );
}
return sb.toString();
}
}
/**
* Constellation Type. Constellations are classified as even or add based
* on the the composition/comparison of the transmitted (representative)
* dibits:
*
* EVEN: - first dibit differs from second dibit in exactly one bit position
*
* ODD: - first dibit differs from second dibit in zero or two bit positions
* following three patterns:
* 1) repeat (10 10), mirror (01 10), or invert(00 11)
*/
public enum Type { EVEN, ODD };
/**
* Constellations, ordered by transmitted value. Transmitted value is the
* value of the dibit pair that represents the constellation when transmitted.
*/
public enum Con
{
CB( 0 ),
CC( 1 ),
C0( 2 ),
C7( 3 ),
CE( 4 ),
C9( 5 ),
C5( 6 ),
C2( 7 ),
CA( 8 ),
CD( 9 ),
C1( 10 ),
C6( 11 ),
CF( 12 ),
C8( 13 ),
C4( 14 ),
C3( 15 );
private int mTransmittedValue;
private Con( int transmittedValue )
{
mTransmittedValue = transmittedValue;
}
public int getTransmittedValue()
{
return mTransmittedValue;
}
public static Con fromTransmittedValue( int value )
{
if( 0 <= value && value <= 15 )
{
return values()[ value ];
}
return null;
}
public static Con fromTransmittedDibits( Dibit left, Dibit right )
{
return fromTransmittedValue( left.getHighValue() + right.getLowValue() );
}
/**
* Returns the metric or hamming distance to the other constellation
* using the values from the constellation costs table. A perfect match
* has a metric of 4 and a complete miss has a value of 0.
*/
public int getMetricTo( Con other )
{
return CONSTELLATION_METRICS[ getTransmittedValue() ][ other.getTransmittedValue() ];
}
}
/**
* Tribit. Used to define both the state and the input bit sequences that
* produce the transmitted constellation bit sequences. Each tribit is
* either even or odd valued producing a direct correlation to the types
* of valid constellations containing the tribit as the state tribit.
*/
public enum Tribit
{
T0( 0, Type.EVEN ),
T1( 1, Type.EVEN ),
T2( 2, Type.ODD ),
T3( 3, Type.ODD ),
T4( 4, Type.ODD ),
T5( 5, Type.ODD ),
T6( 6, Type.EVEN ),
T7( 7, Type.EVEN );
private int mValue;
private Type mType;
private Tribit( int value, Type type )
{
mValue = value;
mType = type;
}
public Type getType()
{
return mType;
}
public int getValue()
{
return mValue;
}
}
/**
* Test harness
*/
public static void main( String[] args )
{
String raw = "12:58:51.635 DEBUG dsp.fsk.P25MessageFramer - AFTER DEINTERLEAVE: 00100110000011001010010101111101000101010110100111111100101011000111011011000110000000000000000000000111111101011000001000001111000110000000000010101011110110010000000001011011000000010100011110110001000010100100011111000000000100000000000000000000010000000000001100000000000000011010101010101010101010100010001010000110010111111101101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000010111001001000011100101011010101010101001010011";
BinaryMessage message = BinaryMessage.load( raw );
mLog.debug( "MSG: " + message.toString() );
Trellis_3_4_Rate t = new Trellis_3_4_Rate();
t.decode( message, 0, 196 );
mLog.debug( "DEC: " + message.toString() );
mLog.debug( "Finished!" );
}
}

View File

@@ -0,0 +1,220 @@
package module.decode.p25.audio;
import jmbe.iface.AudioConversionLibrary;
import jmbe.iface.AudioConverter;
import message.IMessageListener;
import message.Message;
import module.Module;
import module.decode.p25.message.ldu.LDUMessage;
import module.decode.p25.message.tdu.TDUMessage;
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
import sample.Listener;
import alias.Metadata;
import audio.AudioFormats;
import audio.AudioPacket;
import audio.IAudioPacketProvider;
import audio.metadata.AudioMetadata;
import audio.metadata.IMetadataListener;
public class P25AudioModule extends Module implements Listener<Message>,
IAudioPacketProvider, IMessageListener, IChannelEventListener, IMetadataListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25AudioModule.class );
private static final String IMBE_CODEC = "IMBE";
/* Provides a unique identifier for this audio module instance to use as a
* source identifier for all audio packets */
private static int UNIQUE_ID = 0;
private int mSourceID;
private static boolean mLibraryLoadStatusLogged = false;
private boolean mCanConvertAudio = false;
private boolean mEncryptedAudio = false;
private AudioConverter mAudioConverter;
private Listener<AudioPacket> mAudioPacketListener;
private AudioMetadata mAudioMetadata;
private ChannelEventListener mChannelEventListener = new ChannelEventListener();
public P25AudioModule()
{
mSourceID = ++UNIQUE_ID;
mAudioMetadata = new AudioMetadata( mSourceID );
}
@Override
public Listener<Message> getMessageListener()
{
return this;
}
@Override
public void dispose()
{
mAudioConverter = null;
}
@Override
public void init()
{
loadConverter();
}
/**
* Primary inject point for p25 imbe audio frame messages. Each LDU audio
* message contains 9 imbe voice frames. If the audio is not encrypted,
* we insert each audio frame asynchronously into the audio converter.
*/
public void receive( Message message )
{
if( mCanConvertAudio && mAudioPacketListener != null )
{
if( message instanceof LDUMessage )
{
LDUMessage ldu = (LDUMessage)message;
/* If we detect a valid LDU message with the encryption flag
* set to true, we toggle the encrypted audio state until we
* receive the first TDU or TDULC message, then reset it. */
if( ldu.isValid() && ldu.isEncrypted() )
{
mEncryptedAudio = true;
}
if( !mEncryptedAudio && mAudioConverter != null )
{
for( byte[] frame: ((LDUMessage)message).getIMBEFrames() )
{
float[] audio = mAudioConverter.convert( frame );
mAudioPacketListener.receive(
new AudioPacket( audio, mAudioMetadata ) );
}
}
}
else if( message instanceof TDUMessage || message instanceof TDULinkControlMessage )
{
mEncryptedAudio = false;
}
}
}
/**
* Loads audio frame processing chain. Constructs an imbe targetdataline
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
*/
private void loadConverter()
{
AudioConversionLibrary library = null;
try
{
@SuppressWarnings( "rawtypes" )
Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
library = (AudioConversionLibrary)temp.newInstance();
mAudioConverter = library.getAudioConverter( IMBE_CODEC,
AudioFormats.PCM_SIGNED_48KHZ_16BITS );
if( mAudioConverter != null )
{
mCanConvertAudio = true;
if( !mLibraryLoadStatusLogged )
{
mLog.info( "JMBE audio conversion library successfully loaded"
+ " - P25 audio will be available" );
mLibraryLoadStatusLogged = true;
}
}
else
{
if( !mLibraryLoadStatusLogged )
{
mLog.info( "JMBE audio conversion library NOT FOUND" );
mLibraryLoadStatusLogged = true;
}
}
}
catch ( ClassNotFoundException e1 )
{
if( !mLibraryLoadStatusLogged )
{
mLog.error( "Couldn't find/load JMBE audio conversion library" );
mLibraryLoadStatusLogged = true;
}
}
catch ( InstantiationException e1 )
{
if( !mLibraryLoadStatusLogged )
{
mLog.error( "Couldn't instantiate JMBE audio conversion library class" );
mLibraryLoadStatusLogged = true;
}
}
catch ( IllegalAccessException e1 )
{
if( !mLibraryLoadStatusLogged )
{
mLog.error( "Couldn't load JMBE audio conversion library due to "
+ "security restrictions" );
mLibraryLoadStatusLogged = true;
}
}
}
@Override
public void setAudioPacketListener( Listener<AudioPacket> listener )
{
mAudioPacketListener = listener;
}
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public Listener<Metadata> getMetadataListener()
{
return mAudioMetadata;
}
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventListener;
}
/**
* Wrapper for channel event listener. Responds to channel state reset
* events to remove/cleanup current audio metadata
*/
public class ChannelEventListener implements Listener<ChannelEvent>
{
@Override
public void receive( ChannelEvent event )
{
switch( event.getEvent() )
{
case CHANNEL_STATE_RESET:
mAudioMetadata.reset();
break;
case CHANGE_SELECTED:
mAudioMetadata.setSelected( event.getChannel().isSelected() );
break;
default:
break;
}
}
}
}

View File

@@ -0,0 +1,32 @@
package module.decode.p25.message;
public interface IBandIdentifier
{
/**
* (Band) Identifier
*/
public abstract int getIdentifier();
/**
* Channel spacing in hertz
*/
public abstract long getChannelSpacing();
/**
* Base frequency in hertz
*/
public abstract long getBaseFrequency();
/**
* Channel bandwidth in hertz
*/
public abstract int getBandwidth();
/**
* Transmit offset in hertz
*/
public abstract long getTransmitOffset();
}

View File

@@ -0,0 +1,14 @@
package module.decode.p25.message;
/**
* Interface to allow messages to be augmented with IdentiferUpdateXXX type
* messages that provide the channel information necessary to calculate the
* uplink and downlink frequency for the channel.
*/
public interface IdentifierReceiver
{
public void setIdentifierMessage( int identifier, IBandIdentifier message );
public int[] getIdentifiers();
}

View File

@@ -0,0 +1,213 @@
package module.decode.p25.message;
import java.text.SimpleDateFormat;
import map.Plottable;
import message.Message;
import module.decode.p25.reference.DataUnitID;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
public class P25Message extends Message
{
public enum DuplexMode { HALF, FULL };
public enum SessionMode { PACKET, CIRCUIT };
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 SimpleDateFormat mTimeDurationFormat =
new SimpleDateFormat( "HH:mm:ss.SSS");
protected BinaryMessage mMessage;
protected DataUnitID mDUID;
protected AliasList mAliasList;
protected CRC[] mCRC;
public P25Message( BinaryMessage message,
DataUnitID duid,
AliasList aliasList )
{
super();
mMessage = message;
mDUID = duid;
mAliasList = aliasList;
}
public CRC[] getCRCResults()
{
return mCRC;
}
public BinaryMessage getSourceMessage()
{
return mMessage;
}
public AliasList getAliasList()
{
return mAliasList;
}
public String getNAC()
{
return mMessage.getHex( NAC, 3 );
}
public DataUnitID getDUID()
{
return mDUID;
}
@Override
public String getErrorStatus()
{
if( mCRC == null )
{
return "UNKNOWN";
}
return CRC.format( mCRC );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
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()
{
if( mAliasList != null )
{
return mAliasList.getTalkgroupAlias( getFromID() );
}
return null;
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getTalkgroupAlias( getToID() );
}
return null;
}
@Override
public Plottable getPlottable()
{
return null;
}
@Override
public String toString()
{
return getMessage();
}
@Override
public boolean isValid()
{
if( mCRC != null )
{
for( CRC crc: mCRC )
{
if( crc == CRC.FAILED_CRC )
{
return false;
}
}
}
return true;
}
/**
* Calculates the frequency of the uplink channel using the channel number
* and the IdentifierUpdate message.
*
* @param iden - Identifier Update message
* @param channel - channel number
* @return - frequency in Hertz
*/
protected static long calculateUplink( IBandIdentifier iden, int channel )
{
long downlink = calculateDownlink( iden, channel );
if( downlink > 0 && iden != null )
{
return downlink + iden.getTransmitOffset();
}
return 0;
}
/**
* Calculates the frequency of the downlink channel using the channel number
* and the IdentifierUpdate message.
*
* @param iden - Identifier Update message
* @param channel - channel number
* @return - frequency in Hertz
*/
protected static long calculateDownlink( IBandIdentifier iden, int channel )
{
if( iden != null )
{
return iden.getBaseFrequency() +
( channel * iden.getChannelSpacing() );
}
return 0;
}
}

View File

@@ -0,0 +1,36 @@
package module.decode.p25.message.filter;
import java.util.Collections;
import java.util.List;
import message.Message;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
import filter.Filter;
import filter.FilterElement;
public class HDUMessageFilter extends Filter<Message>
{
public HDUMessageFilter()
{
super( "Header Data Unit" );
}
@Override
public boolean passes( Message message )
{
return mEnabled && canProcess( message );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof HDUMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -0,0 +1,35 @@
package module.decode.p25.message.filter;
import java.util.Collections;
import java.util.List;
import message.Message;
import module.decode.p25.message.ldu.LDUMessage;
import filter.Filter;
import filter.FilterElement;
public class LDUMessageFilter extends Filter<Message>
{
public LDUMessageFilter()
{
super( "Link Data Unit" );
}
@Override
public boolean passes( Message message )
{
return mEnabled && canProcess( message );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof LDUMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -0,0 +1,26 @@
package module.decode.p25.message.filter;
import message.Message;
import module.decode.p25.message.P25Message;
import filter.FilterSet;
public class P25MessageFilterSet extends FilterSet<Message>
{
public P25MessageFilterSet()
{
super( "P25 Message Filter" );
addFilter( new HDUMessageFilter() );
addFilter( new LDUMessageFilter() );
addFilter( new PDUMessageFilter() );
addFilter( new TDUMessageFilter() );
addFilter( new TDULCMessageFilter() );
addFilter( new TSBKMessageFilterSet() );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof P25Message;
}
}

View File

@@ -0,0 +1,54 @@
package module.decode.p25.message.filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.Opcode;
import filter.Filter;
import filter.FilterElement;
public class PDUMessageFilter extends Filter<Message>
{
private HashMap<Opcode,FilterElement<Opcode>> mOpcodeFilterElements =
new HashMap<Opcode,FilterElement<Opcode>>();
public PDUMessageFilter()
{
super( "PDU - Packet Data Unit" );
for( Opcode opcode: Opcode.values() )
{
mOpcodeFilterElements.put( opcode, new FilterElement<Opcode>( opcode ) );
}
}
@Override
public boolean passes( Message message )
{
if( mEnabled && message instanceof PDUMessage )
{
PDUMessage pdu = (PDUMessage)message;
Opcode opcode = pdu.getOpcode();
return mOpcodeFilterElements.get( opcode ).isEnabled();
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return message instanceof PDUMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mOpcodeFilterElements.values() );
}
}

View File

@@ -0,0 +1,36 @@
package module.decode.p25.message.filter;
import java.util.Collections;
import java.util.List;
import message.Message;
import module.decode.p25.message.tdu.TDUMessage;
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
import filter.Filter;
import filter.FilterElement;
public class TDULCMessageFilter extends Filter<Message>
{
public TDULCMessageFilter()
{
super( "TDU Terminator Data Unit with Link Control" );
}
@Override
public boolean passes( Message message )
{
return mEnabled && canProcess( message );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof TDULinkControlMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -0,0 +1,35 @@
package module.decode.p25.message.filter;
import java.util.Collections;
import java.util.List;
import message.Message;
import module.decode.p25.message.tdu.TDUMessage;
import filter.Filter;
import filter.FilterElement;
public class TDUMessageFilter extends Filter<Message>
{
public TDUMessageFilter()
{
super( "TDU Terminator Data Unit" );
}
@Override
public boolean passes( Message message )
{
return mEnabled && canProcess( message );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof TDUMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -0,0 +1,200 @@
package module.decode.p25.message.filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import module.decode.p25.message.tsbk.TSBKMessage;
import module.decode.p25.message.tsbk.motorola.MotorolaOpcode;
import module.decode.p25.message.tsbk.vendor.VendorOpcode;
import module.decode.p25.reference.Opcode;
import module.decode.p25.reference.Vendor;
import filter.Filter;
import filter.FilterElement;
import filter.FilterSet;
public class TSBKMessageFilterSet extends FilterSet<Message>
{
public TSBKMessageFilterSet()
{
super( "TSBK Trunking Signalling Block" );
addFilter( new StandardOpcodeFilter() );
addFilter( new MotorolaOpcodeFilter() );
addFilter( new VendorOpcodeFilter() );
}
// @Override
// public boolean passes( Message message )
// {
// if( mEnabled && canProcess( message ) )
// {
// TSBKMessage tsbk = (TSBKMessage)message;
//
// switch( tsbk.getVendor() )
// {
// case STANDARD:
// return
// }
// Opcode opcode = tsbk.getOpcode();
//
// return mFilterElements.get( opcode ).isEnabled();
// }
//
// return false;
// }
@Override
public boolean canProcess( Message message )
{
return message instanceof TSBKMessage;
}
public class StandardOpcodeFilter extends Filter<Message>
{
private HashMap<Opcode,FilterElement<Opcode>> mStandardElements =
new HashMap<Opcode,FilterElement<Opcode>>();
public StandardOpcodeFilter()
{
super( "Standard Opcode Filter" );
for( Opcode opcode: Opcode.values() )
{
if( opcode != Opcode.UNKNOWN )
{
mStandardElements.put( opcode,
new FilterElement<Opcode>( opcode ) );
}
}
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
Opcode opcode = ((TSBKMessage)message).getOpcode();
if( mStandardElements.containsKey( opcode ) )
{
return mStandardElements.get( opcode ).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return ((TSBKMessage)message).getVendor() == Vendor.STANDARD;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mStandardElements.values() );
}
}
public class MotorolaOpcodeFilter extends Filter<Message>
{
private HashMap<MotorolaOpcode,FilterElement<MotorolaOpcode>> mMotorolaElements =
new HashMap<MotorolaOpcode,FilterElement<MotorolaOpcode>>();
public MotorolaOpcodeFilter()
{
super( "Motorola Opcode Filter" );
for( MotorolaOpcode opcode: MotorolaOpcode.values() )
{
if( opcode != MotorolaOpcode.UNKNOWN )
{
mMotorolaElements.put( opcode,
new FilterElement<MotorolaOpcode>( opcode ) );
}
}
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
MotorolaOpcode opcode = ((TSBKMessage)message).getMotorolaOpcode();
if( mMotorolaElements.containsKey( opcode ) )
{
return mMotorolaElements.get( opcode ).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return ((TSBKMessage)message).getVendor() == Vendor.MOTOROLA;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mMotorolaElements.values() );
}
}
public class VendorOpcodeFilter extends Filter<Message>
{
private HashMap<VendorOpcode,FilterElement<VendorOpcode>> mVendorElements =
new HashMap<VendorOpcode,FilterElement<VendorOpcode>>();
public VendorOpcodeFilter()
{
super( "Vendor Opcode Filter" );
for( VendorOpcode opcode: VendorOpcode.values() )
{
if( opcode != VendorOpcode.UNKNOWN )
{
mVendorElements.put( opcode,
new FilterElement<VendorOpcode>( opcode ) );
}
}
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
MotorolaOpcode opcode = ((TSBKMessage)message).getMotorolaOpcode();
if( mVendorElements.containsKey( opcode ) )
{
return mVendorElements.get( opcode ).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
Vendor vendor = ((TSBKMessage)message).getVendor();
return vendor != Vendor.STANDARD && vendor != Vendor.MOTOROLA;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mVendorElements.values() );
}
}
}

View File

@@ -0,0 +1,273 @@
package module.decode.p25.message.hdu;
import module.decode.p25.message.P25Message;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Encryption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.Golay18;
import edac.ReedSolomon_63_47_17;
public class HDUMessage extends P25Message
{
private final static Logger mLog = LoggerFactory.getLogger( HDUMessage.class );
public static final int[] MESSAGE_INDICATOR_A = { 64,65,66,67,68,69,82,83,
84,85,86,87,100,101,102,103,104,105,118,119,120,121,122,123,136,137,138,
139,140,141,154,155,156,157,158,159 };
public static final int[] MESSAGE_INDICATOR_B = { 172,173,174,175,176,177,
190,191,192,193,194,195,208,209,210,211,212,213,226,227,228,229,230,231,
244,245,246,247,248,249,262,263,264,265,266,267 };
public static final int[] VENDOR_ID = { 280,281,282,283,284,285,298,299 };
public static final int[] ALGORITHM_ID = { 300,301,302,303,316,317,318,319 };
public static final int[] KEY_ID = { 320,321,334,335,336,337,338,339,352,
353,354,355,356,357,370,371 };
public static final int[] TALKGROUP_ID = { 372,373,374,375,388,389,390,391,
392,393,406,407,408,409,410,411 };
public static final int[] GOLAY_WORD_STARTS = { 64,82,100,118,136,154,172,
190,208,226,244,262,280,298,316,334,352,370,388,406,424,442,460,478,
496,514,532,550,568,586,604,622,640,658,676,694 };
public static final int[] CW_HEX_0 = { 64,65,66,67,68,69 };
public static final int[] CW_HEX_1 = { 82,83,84,85,86,87 };
public static final int[] CW_HEX_2 = { 100,101,102,103,104,105 };
public static final int[] CW_HEX_3 = { 118,119,120,121,122,123 };
public static final int[] CW_HEX_4 = { 136,137,138,139,140,141 };
public static final int[] CW_HEX_5 = { 154,155,156,157,158,159 };
public static final int[] CW_HEX_6 = { 172,173,174,175,176,177 };
public static final int[] CW_HEX_7 = { 190,191,192,193,194,195 };
public static final int[] CW_HEX_8 = { 208,209,210,211,212,213 };
public static final int[] CW_HEX_9 = { 226,227,228,229,230,231 };
public static final int[] CW_HEX_10 = { 244,245,246,247,248,249 };
public static final int[] CW_HEX_11 = { 262,263,264,265,266,267 };
public static final int[] CW_HEX_12 = { 280,281,282,283,284,285 };
public static final int[] CW_HEX_13 = { 298,299,300,301,302,303 };
public static final int[] CW_HEX_14 = { 316,317,318,319,320,321 };
public static final int[] CW_HEX_15 = { 334,335,336,337,338,339 };
public static final int[] CW_HEX_16 = { 352,353,354,355,356,357 };
public static final int[] CW_HEX_17 = { 370,371,372,373,374,375 };
public static final int[] CW_HEX_18 = { 388,389,390,391,392,393 };
public static final int[] CW_HEX_19 = { 406,407,408,409,410,411 };
public static final int[] RS_HEX_0 = { 424,425,426,427,428,429 };
public static final int[] RS_HEX_1 = { 442,443,444,445,446,447 };
public static final int[] RS_HEX_2 = { 460,461,462,463,464,465 };
public static final int[] RS_HEX_3 = { 478,479,480,481,482,483 };
public static final int[] RS_HEX_4 = { 496,497,498,499,500,501 };
public static final int[] RS_HEX_5 = { 514,515,516,517,518,519 };
public static final int[] RS_HEX_6 = { 532,533,534,535,536,537 };
public static final int[] RS_HEX_7 = { 550,551,552,553,554,555 };
public static final int[] RS_HEX_8 = { 568,569,570,571,572,573 };
public static final int[] RS_HEX_9 = { 586,587,588,589,590,591 };
public static final int[] RS_HEX_10 = { 604,605,606,607,608,609 };
public static final int[] RS_HEX_11 = { 622,623,624,625,626,627 };
public static final int[] RS_HEX_12 = { 640,641,642,643,644,645 };
public static final int[] RS_HEX_13 = { 658,659,660,661,662,663 };
public static final int[] RS_HEX_14 = { 676,677,678,679,680,681 };
public static final int[] RS_HEX_15 = { 694,695,696,697,698,699 };
/* Reed-Solomon(36,20,17) code protects the header word. Maximum
* correctable errors are: Hamming Distance(17) / 2 = 8 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17( 8 );
public HDUMessage( BinaryMessage message, DataUnitID duid,
AliasList aliasList )
{
super( message, duid, aliasList );
/* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */
mCRC = new CRC[ 3 ];
mCRC[ 0 ] = CRC.PASSED;
checkCRC();
}
private void checkCRC()
{
/* Golay( 18,6,18 ) error detection and correction */
for( int index: GOLAY_WORD_STARTS )
{
Golay18.checkAndCorrect( mMessage, index );
}
mCRC[ 1 ] = CRC.PASSED;
/* Reed-Solomon( 36,20,17 ) error detection and correction
* Check the Reed-Solomon parity bits. The RS decoder expects the link
* control data and reed solomon parity hex codewords in reverse order.
* The RS(24,12,13) code used by P25 removes the left-hand 47 data hex
* words, so we replace them with zeros. */
int[] input = new int [63];
int[] output = new int [63];
input[ 0 ] = mMessage.getInt( RS_HEX_15 );
input[ 1 ] = mMessage.getInt( RS_HEX_14 );
input[ 2 ] = mMessage.getInt( RS_HEX_13 );
input[ 3 ] = mMessage.getInt( RS_HEX_12 );
input[ 4 ] = mMessage.getInt( RS_HEX_11 );
input[ 5 ] = mMessage.getInt( RS_HEX_10 );
input[ 6 ] = mMessage.getInt( RS_HEX_9 );
input[ 7 ] = mMessage.getInt( RS_HEX_8 );
input[ 8 ] = mMessage.getInt( RS_HEX_7 );
input[ 9 ] = mMessage.getInt( RS_HEX_6 );
input[ 10 ] = mMessage.getInt( RS_HEX_5 );
input[ 11 ] = mMessage.getInt( RS_HEX_4 );
input[ 12 ] = mMessage.getInt( RS_HEX_3 );
input[ 13 ] = mMessage.getInt( RS_HEX_2 );
input[ 14 ] = mMessage.getInt( RS_HEX_1 );
input[ 15 ] = mMessage.getInt( RS_HEX_0 );
input[ 16 ] = mMessage.getInt( CW_HEX_19 );
input[ 17 ] = mMessage.getInt( CW_HEX_18 );
input[ 18 ] = mMessage.getInt( CW_HEX_17 );
input[ 19 ] = mMessage.getInt( CW_HEX_16 );
input[ 20 ] = mMessage.getInt( CW_HEX_15 );
input[ 21 ] = mMessage.getInt( CW_HEX_14 );
input[ 22 ] = mMessage.getInt( CW_HEX_13 );
input[ 23 ] = mMessage.getInt( CW_HEX_12 );
input[ 24 ] = mMessage.getInt( CW_HEX_11 );
input[ 25 ] = mMessage.getInt( CW_HEX_10 );
input[ 26 ] = mMessage.getInt( CW_HEX_9 );
input[ 27 ] = mMessage.getInt( CW_HEX_8 );
input[ 28 ] = mMessage.getInt( CW_HEX_7 );
input[ 29 ] = mMessage.getInt( CW_HEX_6 );
input[ 30 ] = mMessage.getInt( CW_HEX_5 );
input[ 31 ] = mMessage.getInt( CW_HEX_4 );
input[ 32 ] = mMessage.getInt( CW_HEX_3 );
input[ 33 ] = mMessage.getInt( CW_HEX_2 );
input[ 34 ] = mMessage.getInt( CW_HEX_1 );
input[ 35 ] = mMessage.getInt( CW_HEX_0 );
/* indexes 36 - 62 are defaulted to zero */
boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output );
if( irrecoverableErrors )
{
mCRC[ 2 ] = CRC.FAILED_CRC;
}
else
{
mCRC[ 2 ] = CRC.PASSED;
/* Only fix the codeword data hex codewords */
repairHexCodeword( input, output, 16, CW_HEX_19 );
repairHexCodeword( input, output, 17, CW_HEX_18 );
repairHexCodeword( input, output, 18, CW_HEX_17 );
repairHexCodeword( input, output, 19, CW_HEX_16 );
repairHexCodeword( input, output, 20, CW_HEX_15 );
repairHexCodeword( input, output, 21, CW_HEX_14 );
repairHexCodeword( input, output, 22, CW_HEX_13 );
repairHexCodeword( input, output, 23, CW_HEX_12 );
repairHexCodeword( input, output, 24, CW_HEX_11 );
repairHexCodeword( input, output, 25, CW_HEX_10 );
repairHexCodeword( input, output, 26, CW_HEX_9 );
repairHexCodeword( input, output, 27, CW_HEX_8 );
repairHexCodeword( input, output, 28, CW_HEX_7 );
repairHexCodeword( input, output, 29, CW_HEX_6 );
repairHexCodeword( input, output, 30, CW_HEX_5 );
repairHexCodeword( input, output, 31, CW_HEX_4 );
repairHexCodeword( input, output, 32, CW_HEX_3 );
repairHexCodeword( input, output, 33, CW_HEX_2 );
repairHexCodeword( input, output, 34, CW_HEX_1 );
repairHexCodeword( input, output, 35, CW_HEX_0 );
}
}
private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet )
{
if( input[ index ] != output[ index ] )
{
mMessage.load( indexSet[ 0 ], 6, output[ index ] );
mCRC[ 2 ] = CRC.CORRECTED;
}
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
sb.append( " TALKGROUP:" + getTalkgroupID() );
Encryption encryption = getEncryption();
if( encryption != Encryption.UNENCRYPTED )
{
sb.append( " ENCRYPTION:" );
if( encryption == Encryption.UNKNOWN )
{
sb.append( "UNK ALGO ID:" );
sb.append( mMessage.getInt( ALGORITHM_ID ) );
}
else
{
sb.append( encryption.name() );
}
sb.append( " KEY ID:" + getKeyID() );
sb.append( " MSG INDICATOR:" + getMessageIndicator() );
}
else
{
sb.append( " UNENCRYPTED" );
}
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
public String getMessageIndicator()
{
StringBuilder sb = new StringBuilder();
sb.append( mMessage.getHex( MESSAGE_INDICATOR_A, 9 ) );
sb.append( mMessage.getHex( MESSAGE_INDICATOR_B, 9 ) );
return sb.toString();
}
public Encryption getEncryption()
{
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
}
public boolean isEncrypted()
{
return getEncryption() != Encryption.UNENCRYPTED;
}
public int getKeyID()
{
return mMessage.getInt( KEY_ID );
}
public String getTalkgroupID()
{
return mMessage.getHex( TALKGROUP_ID, 4 );
}
@Override
public String getToID()
{
return getTalkgroupID();
}
public boolean isValid()
{
return mCRC[ 2 ] != null && mCRC[ 2 ] != CRC.FAILED_CRC;
}
}

View File

@@ -0,0 +1,244 @@
package module.decode.p25.message.ldu;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.Hamming10;
import edac.ReedSolomon_63_47_17;
public class LDU1Message extends LDUMessage
{
private final static Logger mLog = LoggerFactory.getLogger( LDU1Message.class );
public static final int ENCRYPTION_FLAG = 352;
public static final int IMPLICIT_VENDOR_FLAG = 353;
public static final int[] OPCODE = { 354,355,356,357,362,363 };
public static final int[] VENDOR = { 364,365,366,367,372,373,374,375 };
public static final int[] GOLAY_WORD_STARTS = {
352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088,
1098,1108,1118,1272,1282,1292,1302 };
public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 };
public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 };
public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 };
public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 };
public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 };
public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 };
public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 };
public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 };
public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 };
public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 };
public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 };
public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 };
public static final int[] RS_HEX_0 = { 904,905,906,907,908,909 };
public static final int[] RS_HEX_1 = { 914,915,916,917,918,919 };
public static final int[] RS_HEX_2 = { 924,925,926,927,928,929 };
public static final int[] RS_HEX_3 = { 934,935,936,937,938,939 };
public static final int[] RS_HEX_4 = { 1088,1089,1090,1091,1092,1093 };
public static final int[] RS_HEX_5 = { 1098,1099,1100,1101,1102,1103 };
public static final int[] RS_HEX_6 = { 1108,1109,1110,1111,1112,1113 };
public static final int[] RS_HEX_7 = { 1118,1119,1120,1121,1122,1123 };
public static final int[] RS_HEX_8 = { 1272,1273,1274,1275,1276,1277 };
public static final int[] RS_HEX_9 = { 1282,1283,1284,1285,1286,1287 };
public static final int[] RS_HEX_10 = { 1292,1293,1294,1295,1296,1297 };
public static final int[] RS_HEX_11 = { 1302,1303,1304,1305,1306,1307 };
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum
* correctable errors are: Hamming Distance(13) / 2 = 6 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17( 6 );
public LDU1Message( BinaryMessage message, DataUnitID duid,
AliasList aliasList )
{
super( message, duid, aliasList );
/* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */
mCRC = new CRC[ 3 ];
mCRC[ 0 ] = CRC.PASSED;
checkCRC();
}
/**
* Subclass constructor from an existing LDU1Message
*/
protected LDU1Message( LDU1Message message )
{
super( message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList() );
mCRC = message.mCRC;
}
@Override
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTION_FLAG );
}
private void checkCRC()
{
mCRC[ 1 ] = CRC.PASSED;
/* Hamming( 10,6,3 ) error detection and correction */
for( int index: GOLAY_WORD_STARTS )
{
int errors = Hamming10.checkAndCorrect( mMessage, index );
if( errors > 1 )
{
mCRC[ 1 ] = CRC.FAILED_CRC;
}
}
/* Reed-Solomon( 24,16,9 ) error detection and correction
* Check the Reed-Solomon parity bits. The RS decoder expects the code
* words and reed solomon parity hex codewords in reverse order.
*
* Since this is a truncated RS(63) codes, we pad the code with zeros */
int[] input = new int [63];
int[] output = new int [63];
input[ 0 ] = mMessage.getInt( RS_HEX_11 );
input[ 1 ] = mMessage.getInt( RS_HEX_10 );
input[ 2 ] = mMessage.getInt( RS_HEX_9 );
input[ 3 ] = mMessage.getInt( RS_HEX_8 );
input[ 4 ] = mMessage.getInt( RS_HEX_7 );
input[ 5 ] = mMessage.getInt( RS_HEX_6 );
input[ 6 ] = mMessage.getInt( RS_HEX_5 );
input[ 7 ] = mMessage.getInt( RS_HEX_4 );
input[ 8 ] = mMessage.getInt( RS_HEX_3 );
input[ 9 ] = mMessage.getInt( RS_HEX_2 );
input[ 10 ] = mMessage.getInt( RS_HEX_1 );
input[ 11 ] = mMessage.getInt( RS_HEX_0 );
input[ 12 ] = mMessage.getInt( CW_HEX_11 );
input[ 13 ] = mMessage.getInt( CW_HEX_10 );
input[ 14 ] = mMessage.getInt( CW_HEX_9 );
input[ 15 ] = mMessage.getInt( CW_HEX_8 );
input[ 16 ] = mMessage.getInt( CW_HEX_7 );
input[ 17 ] = mMessage.getInt( CW_HEX_6 );
input[ 18 ] = mMessage.getInt( CW_HEX_5 );
input[ 19 ] = mMessage.getInt( CW_HEX_4 );
input[ 20 ] = mMessage.getInt( CW_HEX_3 );
input[ 21 ] = mMessage.getInt( CW_HEX_2 );
input[ 22 ] = mMessage.getInt( CW_HEX_1 );
input[ 23 ] = mMessage.getInt( CW_HEX_0 );
/* indexes 24 - 62 are defaulted to zero */
boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output );
if( irrecoverableErrors )
{
mCRC[ 2 ] = CRC.FAILED_CRC;
}
else
{
/* Since we've detected that we can correct the RS words, mark the
* hamming crc as corrected as well, if it is currently failed */
if( mCRC[ 1 ] == CRC.FAILED_CRC )
{
mCRC[ 1 ] = CRC.CORRECTED;
}
mCRC[ 2 ] = CRC.PASSED;
/* Only fix the codeword data hex codewords */
repairHexCodeword( input, output, 12, CW_HEX_11 );
repairHexCodeword( input, output, 13, CW_HEX_10 );
repairHexCodeword( input, output, 14, CW_HEX_9 );
repairHexCodeword( input, output, 15, CW_HEX_8 );
repairHexCodeword( input, output, 16, CW_HEX_7 );
repairHexCodeword( input, output, 17, CW_HEX_6 );
repairHexCodeword( input, output, 18, CW_HEX_5 );
repairHexCodeword( input, output, 19, CW_HEX_4 );
repairHexCodeword( input, output, 20, CW_HEX_3 );
repairHexCodeword( input, output, 21, CW_HEX_2 );
repairHexCodeword( input, output, 22, CW_HEX_1 );
repairHexCodeword( input, output, 23, CW_HEX_0 );
}
}
private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet )
{
if( input[ index ] != output[ index ] )
{
mMessage.load( indexSet[ 0 ], 6, output[ index ] );
mCRC[ 2 ] = CRC.CORRECTED;
}
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
public String getMessageStub()
{
StringBuilder sb = new StringBuilder();
sb.append( super.getMessageStub() );
if( isEncrypted() )
{
sb.append( "ENCRYPTED" );
}
else
{
if( isImplicitFormat() || getVendor() == Vendor.STANDARD )
{
sb.append( getOpcode().getLabel() );
}
else
{
sb.append( "VENDOR:" + getVendor().getLabel() + " " );
sb.append( getVendorOpcode().getLabel() );
}
}
return sb.toString();
}
public boolean isImplicitFormat()
{
return mMessage.get( IMPLICIT_VENDOR_FLAG );
}
public LinkControlOpcode getOpcode()
{
return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
public VendorLinkControlOpcode getVendorOpcode()
{
return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
public Vendor getVendor()
{
if( isImplicitFormat() )
{
return Vendor.STANDARD;
}
return Vendor.fromValue( mMessage.getInt( VENDOR ) );
}
}

View File

@@ -0,0 +1,241 @@
package module.decode.p25.message.ldu;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Encryption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.Hamming10;
import edac.ReedSolomon_63_47_17;
public class LDU2Message extends LDUMessage
{
private final static Logger mLog = LoggerFactory.getLogger( LDU2Message.class );
public static final int[] MESSAGE_INDICATOR_A = { 352,353,354,355,356,357,
362,363,364,365,366,367,372,373,374,375,376,377,382,383,384,385,386,387,
536,537,538,539,540,541,546,547,548,549,550,551 };
public static final int[] MESSAGE_INDICATOR_B = { 556,557,558,559,560,561,
566,567,568,569,570,571,720,721,722,723,724,725,730,731,732,733,734,735,
740,741,742,743,744,745,750,751,752,753,754,755 };
public static final int[] ALGORITHM_ID = { 904,905,906,907,908,909,914,915 };
public static final int[] KEY_ID = { 916,917,918,919,924,925,926,927,928,
929,934,935,936,937,938,939 };
public static final int[] GOLAY_WORD_STARTS = {
352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088,
1098,1108,1118,1272,1282,1292,1302 };
public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 };
public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 };
public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 };
public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 };
public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 };
public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 };
public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 };
public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 };
public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 };
public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 };
public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 };
public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 };
public static final int[] CW_HEX_12 = { 904,905,906,907,908,909 };
public static final int[] CW_HEX_13 = { 914,915,916,917,918,919 };
public static final int[] CW_HEX_14 = { 924,925,926,927,928,929 };
public static final int[] CW_HEX_15 = { 934,935,936,937,938,939 };
public static final int[] RS_HEX_0 = { 1088,1089,1090,1091,1092,1093 };
public static final int[] RS_HEX_1 = { 1098,1099,1100,1101,1102,1103 };
public static final int[] RS_HEX_2 = { 1108,1109,1110,1111,1112,1113 };
public static final int[] RS_HEX_3 = { 1118,1119,1120,1121,1122,1123 };
public static final int[] RS_HEX_4 = { 1272,1273,1274,1275,1276,1277 };
public static final int[] RS_HEX_5 = { 1282,1283,1284,1285,1286,1287 };
public static final int[] RS_HEX_6 = { 1292,1293,1294,1295,1296,1297 };
public static final int[] RS_HEX_7 = { 1302,1303,1304,1305,1306,1307 };
/* Reed-Solomon(24,16,9) code protects the encryption sync word. Maximum
* correctable errors are: Hamming Distance(9) / 2 = 4 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17( 4 );
public LDU2Message( BinaryMessage message, DataUnitID duid,
AliasList aliasList )
{
super( message, duid, aliasList );
/* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */
mCRC = new CRC[ 3 ];
mCRC[ 0 ] = CRC.PASSED;
checkCRC();
}
@Override
public boolean isEncrypted()
{
return getEncryption() != Encryption.UNENCRYPTED;
}
private void checkCRC()
{
mCRC[ 1 ] = CRC.PASSED;
/* Hamming( 10,6,3 ) error detection and correction */
for( int index: GOLAY_WORD_STARTS )
{
int errors = Hamming10.checkAndCorrect( mMessage, index );
if( errors > 1 )
{
mCRC[ 1 ] = CRC.FAILED_CRC;
}
}
/* Reed-Solomon( 24,16,9 ) error detection and correction
* Check the Reed-Solomon parity bits. The RS decoder expects the code
* words and reed solomon parity hex codewords in reverse order.
*
* Since this is a truncated RS(63) codes, we pad the code with zeros */
int[] input = new int [63];
int[] output = new int [63];
input[ 0 ] = mMessage.getInt( RS_HEX_7 );
input[ 1 ] = mMessage.getInt( RS_HEX_6 );
input[ 2 ] = mMessage.getInt( RS_HEX_5 );
input[ 3 ] = mMessage.getInt( RS_HEX_4 );
input[ 4 ] = mMessage.getInt( RS_HEX_3 );
input[ 5 ] = mMessage.getInt( RS_HEX_2 );
input[ 6 ] = mMessage.getInt( RS_HEX_1 );
input[ 7 ] = mMessage.getInt( RS_HEX_0 );
input[ 8 ] = mMessage.getInt( CW_HEX_15 );
input[ 9 ] = mMessage.getInt( CW_HEX_14 );
input[ 10 ] = mMessage.getInt( CW_HEX_13 );
input[ 11 ] = mMessage.getInt( CW_HEX_12 );
input[ 12 ] = mMessage.getInt( CW_HEX_11 );
input[ 13 ] = mMessage.getInt( CW_HEX_10 );
input[ 14 ] = mMessage.getInt( CW_HEX_9 );
input[ 15 ] = mMessage.getInt( CW_HEX_8 );
input[ 16 ] = mMessage.getInt( CW_HEX_7 );
input[ 17 ] = mMessage.getInt( CW_HEX_6 );
input[ 18 ] = mMessage.getInt( CW_HEX_5 );
input[ 19 ] = mMessage.getInt( CW_HEX_4 );
input[ 20 ] = mMessage.getInt( CW_HEX_3 );
input[ 21 ] = mMessage.getInt( CW_HEX_2 );
input[ 22 ] = mMessage.getInt( CW_HEX_1 );
input[ 23 ] = mMessage.getInt( CW_HEX_0 );
/* indexes 24 - 62 are defaulted to zero */
boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output );
if( irrecoverableErrors )
{
mCRC[ 2 ] = CRC.FAILED_CRC;
}
else
{
/* Since we've detected that we can correct the RS words, mark the
* hamming crc as corrected as well, if it is currently failed */
if( mCRC[ 1 ] == CRC.FAILED_CRC )
{
mCRC[ 1 ] = CRC.CORRECTED;
}
mCRC[ 2 ] = CRC.PASSED;
/* Only fix the codeword data hex codewords */
repairHexCodeword( input, output, 8, CW_HEX_15 );
repairHexCodeword( input, output, 9, CW_HEX_14 );
repairHexCodeword( input, output, 10, CW_HEX_13 );
repairHexCodeword( input, output, 11, CW_HEX_12 );
repairHexCodeword( input, output, 12, CW_HEX_11 );
repairHexCodeword( input, output, 13, CW_HEX_10 );
repairHexCodeword( input, output, 14, CW_HEX_9 );
repairHexCodeword( input, output, 15, CW_HEX_8 );
repairHexCodeword( input, output, 16, CW_HEX_7 );
repairHexCodeword( input, output, 17, CW_HEX_6 );
repairHexCodeword( input, output, 18, CW_HEX_5 );
repairHexCodeword( input, output, 19, CW_HEX_4 );
repairHexCodeword( input, output, 20, CW_HEX_3 );
repairHexCodeword( input, output, 21, CW_HEX_2 );
repairHexCodeword( input, output, 22, CW_HEX_1 );
repairHexCodeword( input, output, 23, CW_HEX_0 );
}
}
private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet )
{
if( input[ index ] != output[ index ] )
{
mMessage.load( indexSet[ 0 ], 6, output[ index ] );
mCRC[ 2 ] = CRC.CORRECTED;
}
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
Encryption encryption = getEncryption();
if( encryption != Encryption.UNENCRYPTED )
{
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
sb.append( " ENCRYPTED VOICE:" );
sb.append( encryption.name() );
sb.append( " KEY ID:" + getKeyID() );
sb.append( " MSG INDICATOR:" + getMessageIndicator() );
}
else
{
sb.append( super.getMessageStub() );
sb.append( "UNENCRYPTED" );
}
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
public String getMessageIndicator()
{
StringBuilder sb = new StringBuilder();
sb.append( mMessage.getHex( MESSAGE_INDICATOR_A, 9 ) );
sb.append( mMessage.getHex( MESSAGE_INDICATOR_B, 9 ) );
return sb.toString();
}
public Encryption getEncryption()
{
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
}
public int getKeyID()
{
return mMessage.getInt( KEY_ID );
}
public static void main( String[] args )
{
String msg = "001010100000101010011010001001001001010011000111000111110111001100000010000101001011110111111000001100111100110000000010001110011011001101101101101101110011110011010000101111001111000011000010110010011110110010000100101101101001100000100000100001011100000000001110011111011000100100000000011011100000111111111101111010110111000111100011010111111110111000000000000000000000000000000000000000000100100001110110101111011000011010010100011001000101001011100101111011000001101100010101111001000100001111011111011001111110100111001100110110000000000000000000000000000000000000000000000101000001011110111010110010000100010110001100001101001110111001000111100100111011011011110101011111011000010100110111001000101000010011101010000000000000000000000000000000000000000001001000001000011011110110011110011001111100100000111000101001110111011110101001000101001100001110001100000000110000110000001011110000000101100110000011100000000000000000000000000000000111011001010000111111111111110000110110011000100101011000001111110000101000101111010101010101000100110111000100100111110010101010000100111010111010111010001011010010001011011001001001011100000101000010010010111100000110010000101010000110101100101111100110111100010001011011110101001011010100010000111000010000001001001001101000100110101011110100111100011111110101100000011011011001000001010100011001010100110000111100101011100110110011111100011011111110100111011101011111100001111011000101100011101000110001101000000000000000000000000000000000010001101101011101111010101010000111011011101001011111001110111001000010101011010000001011010111010000011011001100010110001010001000010001101101";
BinaryMessage message = BinaryMessage.load( msg );
LDU2Message ldu2 = new LDU2Message( message, DataUnitID.LDU2, null );
mLog.debug( ldu2.getMessage() );
mLog.debug( "CRC: " + CRC.format( ldu2.getCRCResults() ));
}
}

View File

@@ -0,0 +1,93 @@
package module.decode.p25.message.ldu;
import java.util.ArrayList;
import java.util.List;
import module.decode.p25.message.P25Message;
import module.decode.p25.reference.DataUnitID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
public abstract class LDUMessage extends P25Message
{
private final static Logger mLog = LoggerFactory.getLogger( LDUMessage.class );
public static final int IMBE_FRAME_1 = 64;
public static final int IMBE_FRAME_2 = 208;
public static final int IMBE_FRAME_3 = 392;
public static final int IMBE_FRAME_4 = 576;
public static final int IMBE_FRAME_5 = 760;
public static final int IMBE_FRAME_6 = 944;
public static final int IMBE_FRAME_7 = 1128;
public static final int IMBE_FRAME_8 = 1312;
public static final int IMBE_FRAME_9 = 1488;
public static final int[] LOW_SPEED_DATA = { 1456,1457,1458,1459,1460,1461,
1462,1463,1472,1473,1474,1475,1476,1477,1478,1479 };
public LDUMessage( BinaryMessage message, DataUnitID duid,
AliasList aliasList )
{
super( message, duid, aliasList );
}
public abstract boolean isEncrypted();
@Override
public String getMessage()
{
return getMessageStub();
}
public String getMessageStub()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
sb.append( " VOICE LSD:" );
sb.append( getLowSpeedData() );
sb.append( " " );
return sb.toString();
}
public String getLowSpeedData()
{
return mMessage.getHex( LOW_SPEED_DATA, 4 );
}
public boolean isValid()
{
// return mCRC[ 2 ] != null && mCRC[ 2 ] != CRC.FAILED_CRC;
return true;
}
/**
* Returns a 162 byte array containing 9 IMBE voice frames of 18-bytes
* (144-bits) each. Each frame is intact as transmitted and requires
* deinterleaving, error correction, derandomizing, etc.
*/
public List<byte[]> getIMBEFrames()
{
List<byte[]> frames = new ArrayList<byte[]>();
frames.add( mMessage.get( IMBE_FRAME_1, IMBE_FRAME_1 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_2, IMBE_FRAME_2 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_3, IMBE_FRAME_3 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_4, IMBE_FRAME_4 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_5, IMBE_FRAME_5 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_6, IMBE_FRAME_6 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_7, IMBE_FRAME_7 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_8, IMBE_FRAME_8 + 144 ).toByteArray() );
frames.add( mMessage.get( IMBE_FRAME_9, IMBE_FRAME_9 + 144 ).toByteArray() );
return frames;
}
}

View File

@@ -0,0 +1,127 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdjacentSiteStatusBroadcast extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( AdjacentSiteStatusBroadcast.class );
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] SYSTEM_ID = { 384,385,386,387,536,537,538,539,540,
541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] IDENTIFIER = { 720,721,722,723 };
public static final int[] CHANNEL = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,
754,755 };
private IBandIdentifier mIdentifierUpdate;
public AdjacentSiteStatusBroadcast( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SYS:" + getSystemID() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " CHAN:" + getChannel() );
sb.append( " " + Service.getServices( getSystemServiceClass() ).toString() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getChannelNumber()
{
return mMessage.getInt( CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
mIdentifierUpdate = message;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mIdentifierUpdate, getChannelNumber() );
}
public long getUplinkFrequencyB()
{
return calculateUplink( mIdentifierUpdate, getChannelNumber() );
}
}

View File

@@ -0,0 +1,98 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdjacentSiteStatusBroadcastExplicit extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( AdjacentSiteStatusBroadcastExplicit.class );
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] TRANSMIT_IDENTIFIER = { 376,377,382,383 };
public static final int[] TRANSMIT_CHANNEL = { 384,385,386,387,536,537,538,
539,540,541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] RECEIVE_IDENTIFIER = { 720,721,722,723 };
public static final int[] RECEIVE_CHANNEL = { 724,725,730,731,732,733,734,
735,740,741,742,743 };
public AdjacentSiteStatusBroadcastExplicit( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " DNLINK:" + getTransmitChannelNumber() );
sb.append( " UPLINK:" + getReceiveChannelNumber() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannel()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getTransmitChannelNumber()
{
return getTransmitIdentifier() + "-" + getTransmitChannel();
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannel()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getReceiveChannelNumber()
{
return getReceiveIdentifier() + "-" + getReceiveChannel();
}
}

View File

@@ -0,0 +1,50 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CallAlert extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( CallAlert.class );
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public CallAlert( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CALL_ALERT.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " SRC ADDR: " + getSourceAddress() );
sb.append( " TGT ADDR: " + getTargetAddress() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,43 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CallTermination extends LDU1Message
{
private final static Logger mLog = LoggerFactory.getLogger( CallTermination.class );
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public CallTermination( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " FROM:" + getSourceAddress() );
return sb.toString();
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,89 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChannelIdentifierUpdate extends LDU1Message
implements IBandIdentifier
{
private final static Logger mLog =
LoggerFactory.getLogger( ChannelIdentifierUpdate.class );
public static final int[] IDENTIFIER = { 364,365,366,367 };
public static final int[] BANDWIDTH = { 372,373,374,375,376,377,382,383,384 };
public static final int[] TRANSMIT_OFFSET = { 385,386,387,536,537,538,539,
540,541 };
public static final int[] CHANNEL_SPACING = { 546,547,548,549,550,551,556,
557,558,559 };
public static final int[] BASE_FREQUENCY = { 560,561,566,567,568,569,570,
571,720,721,722,723,724,725,730,731,732,733,734,735,740,741,742,743,744,
745,750,751,752,753,754,755 };
public ChannelIdentifierUpdate( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " IDEN:" + getIdentifier() );
sb.append( " BASE:" + getBaseFrequency() );
sb.append( " BW:" + getBandwidth() );
sb.append( " SPACING:" + getChannelSpacing() );
sb.append( " OFFSET:" + getTransmitOffset() );
return sb.toString();
}
@Override
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
/**
* Channel bandwidth in hertz
*/
@Override
public int getBandwidth()
{
return mMessage.getInt( BANDWIDTH ) * 125;
}
@Override
public long getChannelSpacing()
{
return mMessage.getLong( CHANNEL_SPACING ) * 125l;
}
@Override
public long getBaseFrequency()
{
return mMessage.getLong( BASE_FREQUENCY ) * 5l;
}
@Override
public long getTransmitOffset()
{
return -1 * mMessage.getLong( TRANSMIT_OFFSET ) * 250000l;
}
}

View File

@@ -0,0 +1,89 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChannelIdentifierUpdateExplicit extends LDU1Message
implements IBandIdentifier
{
private final static Logger mLog =
LoggerFactory.getLogger( ChannelIdentifierUpdateExplicit.class );
public static final int[] IDENTIFIER = { 364,365,366,367 };
public static final int[] BANDWIDTH = { 372,373,374,375 };
public static final int[] TRANSMIT_OFFSET = { 376,377,382,383,384,385,386,
387,536,537,538,539,540,541 };
public static final int[] CHANNEL_SPACING = { 546,547,548,549,550,551,556,
557,558,559 };
public static final int[] BASE_FREQUENCY = { 560,561,566,567,568,569,570,
571,720,721,722,723,724,725,730,731,732,733,734,735,740,741,742,743,744,
745,750,751,752,753,754,755 };
public ChannelIdentifierUpdateExplicit( LDU1Message source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " IDEN:" + getIdentifier() );
sb.append( " BASE:" + getBaseFrequency() );
sb.append( " BW:" + getBandwidth() );
sb.append( " SPACING:" + getChannelSpacing() );
sb.append( " OFFSET:" + getTransmitOffset() );
return sb.toString();
}
@Override
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
/**
* Channel bandwidth in hertz
*/
@Override
public int getBandwidth()
{
return mMessage.getInt( BANDWIDTH ) * 125;
}
@Override
public long getChannelSpacing()
{
return mMessage.getLong( CHANNEL_SPACING ) * 125l;
}
@Override
public long getBaseFrequency()
{
return mMessage.getLong( BASE_FREQUENCY ) * 5l;
}
@Override
public long getTransmitOffset()
{
return -1 * mMessage.getLong( TRANSMIT_OFFSET ) * 250000l;
}
}

View File

@@ -0,0 +1,87 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.ExtendedFunction;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExtendedFunctionCommand extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( ExtendedFunctionCommand.class );
public static final int[] EXTENDED_FUNCTION = { 364,365,366,367,372,373,374,
375,376,377,382,383,384,385,386,387 };
public static final int[] ARGUMENT = { 536,537,538,539,540,541,546,547,548,
549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] TARGET_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public ExtendedFunctionCommand( LDU1Message source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.EXTENDED_FUNCTION_COMMAND.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " EXTENDED FUNCTION:" + getExtendedFunction().getLabel() );
sb.append( " ARGUMENT: " + getTargetAddress() );
sb.append( " TGT ADDR: " + getTargetAddress() );
return sb.toString();
}
public ExtendedFunction getExtendedFunction()
{
return ExtendedFunction.fromValue( mMessage.getInt( EXTENDED_FUNCTION ) );
}
public String getArgument()
{
return mMessage.getHex( ARGUMENT, 6 );
}
public String getSourceAddress()
{
switch( getExtendedFunction() )
{
case RADIO_CHECK:
case RADIO_CHECK_ACK:
case RADIO_DETACH:
case RADIO_DETACH_ACK:
case RADIO_INHIBIT:
case RADIO_INHIBIT_ACK:
case RADIO_UNINHIBIT:
case RADIO_UNINHIBIT_ACK:
return getArgument();
default:
break;
}
return null;
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
@Override
public String getToID()
{
return getTargetAddress();
}
}

View File

@@ -0,0 +1,52 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GroupAffiliationQuery extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( GroupAffiliationQuery.class );
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public GroupAffiliationQuery( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.GROUP_AFFILIATION_QUERY.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getTargetAddress() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,160 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GroupVoiceChannelUpdate extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( GroupVoiceChannelUpdate.class );
public static final int[] IDENTIFIER_A = { 364,365,366,367 };
public static final int[] CHANNEL_A = { 372,373,374,375,376,377,382,383,384,
385,386,387 };
public static final int[] GROUP_ADDRESS_A = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559 };
public static final int[] IDENTIFIER_B = { 560,561,566,567 };
public static final int[] CHANNEL_B = { 568,569,570,571,720,721,722,723,724,
725,730,731 };
public static final int[] GROUP_ADDRESS_B = { 732,733,734,735,740,741,742,
743,744,745,750,751,752,753,754,755 };
private IBandIdentifier mIdentifierUpdateA;
private IBandIdentifier mIdentifierUpdateB;
public GroupVoiceChannelUpdate( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " CHAN A:" + getChannelA() );
sb.append( " GRP A:" + getGroupAddressA() );
if( hasChannelB() )
{
sb.append( " CHAN B:" + getChannelA() );
sb.append( " GRP B:" + getGroupAddressA() );
}
return sb.toString();
}
public int getChannelIdentifierA()
{
return mMessage.getInt( IDENTIFIER_A );
}
public int getChannelNumberA()
{
return mMessage.getInt( CHANNEL_A );
}
public String getChannelA()
{
return getChannelIdentifierA() + "-" + getChannelNumberA();
}
public int getChannelIdentifierB()
{
return mMessage.getInt( IDENTIFIER_B );
}
public int getChannelNumberB()
{
return mMessage.getInt( CHANNEL_B );
}
public String getChannelB()
{
return getChannelIdentifierB() + "-" + getChannelNumberB();
}
public boolean hasChannelB()
{
return getChannelNumberB() != 0;
}
public String getGroupAddressA()
{
return mMessage.getHex( GROUP_ADDRESS_A, 4 );
}
public String getGroupAddressB()
{
return mMessage.getHex( GROUP_ADDRESS_B, 4 );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getChannelIdentifierA() )
{
mIdentifierUpdateA = message;
}
if( identifier == getChannelIdentifierB() )
{
mIdentifierUpdateB = message;
}
}
@Override
public int[] getIdentifiers()
{
if( hasChannelB() )
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getChannelIdentifierA();
identifiers[ 1 ] = getChannelIdentifierB();
return identifiers;
}
else
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getChannelIdentifierA();
return identifiers;
}
}
public long getDownlinkFrequencyA()
{
return calculateDownlink( mIdentifierUpdateA, getChannelNumberA() );
}
public long getUplinkFrequencyA()
{
return calculateUplink( mIdentifierUpdateA, getChannelNumberA() );
}
public long getDownlinkFrequencyB()
{
return calculateDownlink( mIdentifierUpdateB, getChannelNumberA() );
}
public long getUplinkFrequencyB()
{
return calculateUplink( mIdentifierUpdateB, getChannelNumberA() );
}
}

View File

@@ -0,0 +1,157 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.P25Message.DuplexMode;
import module.decode.p25.message.P25Message.SessionMode;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GroupVoiceChannelUpdateExplicit extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( GroupVoiceChannelUpdateExplicit.class );
public static final int EMERGENCY_FLAG = 376;
public static final int ENCRYPTED_CHANNEL_FLAG = 377;
public static final int DUPLEX_MODE = 382;
public static final int SESSION_MODE = 383;
public static final int[] GROUP_ADDRESS = { 536,537,538,539,540,541,546,547,
548,549,550,551,556,557,558,559 };
public static final int[] TRANSMIT_IDENTIFIER = { 560,561,566,567 };
public static final int[] TRANSMIT_CHANNEL = { 568,569,570,571,720,721,722,
723,724,725,730,731 };
public static final int[] RECEIVE_IDENTIFIER = { 732,733,734,735 };
public static final int[] RECEIVE_CHANNEL = { 740,741,742,743,744,745,750,
751,752,753,754,755 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public GroupVoiceChannelUpdateExplicit( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
if( isEncryptedChannel() )
{
sb.append( " ENCRYPTED CHANNEL" );
}
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncryptedChannel()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public int getTransmitChannelIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public String getGroupAddress()
{
return mMessage.getHex( GROUP_ADDRESS, 4 );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitChannelIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveChannelIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitChannelIdentifier();
identifiers[ 1 ] = getReceiveChannelIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate,
getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate,
getReceiveChannelNumber() );
}
}

View File

@@ -0,0 +1,87 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GroupVoiceChannelUser extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( GroupVoiceChannelUser.class );
/* Service Options */
public static final int EMERGENCY_FLAG = 376;
public static final int ENCRYPTED_CHANNEL_FLAG = 377;
public static final int DUPLEX_MODE = 378;
public static final int SESSION_MODE = 379;
public static final int[] GROUP_ADDRESS = { 548,549,550,551,556,557,558,559,
560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public GroupVoiceChannelUser( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.GROUP_VOICE_CHANNEL_USER.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
if( isEncryptedChannel() )
{
sb.append( " ENCRYPTED CHANNEL" );
}
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getGroupAddress() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncryptedChannel()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getGroupAddress()
{
return mMessage.getHex( GROUP_ADDRESS, 4 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,96 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.Vendor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LDULCMessageFactory
{
private final static Logger mLog =
LoggerFactory.getLogger( LDULCMessageFactory.class );
/**
* Constructs a new sub-class of the TDULC message if the opcode is recognized
* or returns the original message.
*/
public static LDU1Message getMessage( LDU1Message message )
{
if( message.isImplicitFormat() || message.getVendor() == Vendor.STANDARD )
{
switch( message.getOpcode() )
{
case ADJACENT_SITE_STATUS_BROADCAST:
return new AdjacentSiteStatusBroadcast( message );
case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT:
return new AdjacentSiteStatusBroadcastExplicit( message );
case CALL_ALERT:
return new CallAlert( message );
case CALL_TERMINATION_OR_CANCELLATION:
return new CallTermination( message );
case CHANNEL_IDENTIFIER_UPDATE:
return new ChannelIdentifierUpdate( message );
case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT:
return new ChannelIdentifierUpdateExplicit( message );
case EXTENDED_FUNCTION_COMMAND:
return new ExtendedFunctionCommand( message );
case GROUP_AFFILIATION_QUERY:
return new GroupAffiliationQuery( message );
case GROUP_VOICE_CHANNEL_UPDATE:
return new GroupVoiceChannelUpdate( message );
case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT:
return new GroupVoiceChannelUpdateExplicit( message );
case GROUP_VOICE_CHANNEL_USER:
return new GroupVoiceChannelUser( message );
case MESSAGE_UPDATE:
return new MessageUpdate( message );
case NETWORK_STATUS_BROADCAST:
return new NetworkStatusBroadcast( message );
case NETWORK_STATUS_BROADCAST_EXPLICIT:
return new NetworkStatusBroadcastExplicit( message );
case PROTECTION_PARAMETER_BROADCAST:
return new ProtectionParameterBroadcast( message );
case RFSS_STATUS_BROADCAST:
return new RFSSStatusBroadcast( message );
case RFSS_STATUS_BROADCAST_EXPLICIT:
return new RFSSStatusBroadcastExplicit( message );
case SECONDARY_CONTROL_CHANNEL_BROADCAST:
return new SecondaryControlChannelBroadcast( message );
case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT:
return new SecondaryControlChannelBroadcastExplicit( message );
case STATUS_QUERY:
return new StatusQuery( message );
case STATUS_UPDATE:
return new StatusUpdate( message );
case SYSTEM_SERVICE_BROADCAST:
return new SystemServiceBroadcast( message );
case TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
return new TelephoneInterconnectAnswerRequest( message );
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
return new TelephoneInterconnectVoiceChannelUser( message );
case UNIT_AUTHENTICATION_COMMAND:
return new UnitAuthenticationCommand( message );
case UNIT_REGISTRATION_COMMAND:
return new UnitRegistrationCommand( message );
case UNIT_TO_UNIT_ANSWER_REQUEST:
return new UnitToUnitAnswerRequest( message );
case UNIT_TO_UNIT_VOICE_CHANNEL_USER:
return new UnitToUnitVoiceChannelUser( message );
default:
break;
}
}
else
{
switch( message.getVendor() )
{
default:
break;
}
}
return message;
}
}

View File

@@ -0,0 +1,58 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MessageUpdate extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( MessageUpdate.class );
public static final int[] SHORT_DATA_MESSAGE = { 364,365,366,367,372,373,
374,375,376,377,382,383,384,385,386,387 };
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public MessageUpdate( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.MESSAGE_UPDATE.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " MSG:" + getShortDataMessage() );
sb.append( " SRC ADDR: " + getSourceAddress() );
sb.append( " TGT ADDR: " + getTargetAddress() );
return sb.toString();
}
public String getShortDataMessage()
{
return mMessage.getHex( SHORT_DATA_MESSAGE, 4 );
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,110 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NetworkStatusBroadcast extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( NetworkStatusBroadcast.class );
public static final int[] NETWORK_ID = { 376,377,382,383,384,385,386,387,
536,537,538,539,540,541,546,547,548,549,550,551 };
public static final int[] SYSTEM_ID = { 556,557,558,559,560,561,566,567,568,
569,570,571 };
public static final int[] IDENTIFIER = { 720,721,722,723 };
public static final int[] CHANNEL = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,
754,755 };
private IBandIdentifier mIdentifierUpdate;
public NetworkStatusBroadcast( LDU1Message source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.NETWORK_STATUS_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " NETWORK:" + getNetworkID() );
sb.append( " SYS:" + getSystem() );
sb.append( " CHAN:" + getChannel() );
sb.append( " " + Service.getServices( getSystemServiceClass() ).toString() );
return sb.toString();
}
public String getNetworkID()
{
return mMessage.getHex( NETWORK_ID, 5 );
}
public String getSystem()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
public int getChannelNumber()
{
return mMessage.getInt( CHANNEL );
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
mIdentifierUpdate = message;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mIdentifierUpdate, getChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mIdentifierUpdate, getChannelNumber() );
}
}

View File

@@ -0,0 +1,131 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NetworkStatusBroadcastExplicit extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( NetworkStatusBroadcastExplicit.class );
public static final int[] NETWORK_ID = { 364,365,366,367,372,373,374,375,
376,377,382,383,384,385,386,387,536,537,538,539 };
public static final int[] SYSTEM_ID = { 540,541,546,547,548,549,550,551,556,
557,558,559 };
public static final int[] TRANSMIT_IDENTIFIER = { 560,561,566,567 };
public static final int[] TRANSMIT_CHANNEL = { 568,569,570,571,720,721,722,
723,724,725,730,731 };
public static final int[] RECEIVE_IDENTIFIER = { 732,733,734,735 };
public static final int[] RECEIVE_CHANNEL = { 740,741,742,743,744,745,750,
751,752,753,754,755 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public NetworkStatusBroadcastExplicit( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.NETWORK_STATUS_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " NETWORK:" + getNetworkID() );
sb.append( " SYS:" + getSystem() );
sb.append( " TRANSMIT:" + getTransmitChannel() );
sb.append( " RECEIVE:" + getReceiveChannel() );
return sb.toString();
}
public String getNetworkID()
{
return mMessage.getHex( NETWORK_ID, 5 );
}
public String getSystem()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getTransmitChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getReceiveChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannelNumber();
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitIdentifier();
identifiers[ 1 ] = getReceiveIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate,
getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate,
getReceiveChannelNumber() );
}
}

View File

@@ -0,0 +1,59 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.Encryption;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProtectionParameterBroadcast extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( ProtectionParameterBroadcast.class );
public static final int[] ALGORITHM_ID = { 536,537,538,539,540,541,546,547 };
public static final int[] KEY_ID = { 548,549,550,551,556,557,558,559,560,
561,566,567,568,569,570,571 };
public static final int[] TARGET_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public ProtectionParameterBroadcast( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.PROTECTION_PARAMETER_BROADCAST.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " ENCRYPTION:" + getEncryption().name() );
sb.append( " KEY:" + getEncryptionKey() );
sb.append( " ADDRESSS:" + getTargetAddress() );
return sb.toString();
}
public Encryption getEncryption()
{
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
}
public int getEncryptionKey()
{
return mMessage.getInt( KEY_ID );
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,130 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RFSSStatusBroadcast extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( RFSSStatusBroadcast.class );
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] SYSTEM_ID = { 384,385,386,387,536,537,538,539,540,
541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] IDENTIFIER = { 720,721,722,723 };
public static final int[] CHANNEL = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,
754,755 };
private IBandIdentifier mIdentifierUpdate;
public RFSSStatusBroadcast( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.RFSS_STATUS_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SYS:" + getSystem() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " CHAN:" + getChannelNumber() );
sb.append( " " + SystemService.toString( getSystemServiceClass() ) );
sb.append( " " + mMessage.toString() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getSystem()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getChannelNumber()
{
return mMessage.getInt( CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
mIdentifierUpdate = message;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mIdentifierUpdate, getChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mIdentifierUpdate, getChannelNumber() );
}
}

View File

@@ -0,0 +1,150 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RFSSStatusBroadcastExplicit extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( RFSSStatusBroadcastExplicit.class );
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] RECEIVE_IDENTIFIER = { 376,377,382,383 };
public static final int[] RECEIVE_CHANNEL = { 384,385,386,387,536,537,538,
539,540,541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] TRANSMIT_IDENTIFIER = { 720,721,722,723 };
public static final int[] TRANSMIT_CHANNEL = { 724,725,730,731,732,733,734,
735,740,741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,754,755 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public RFSSStatusBroadcastExplicit( LDU1Message source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.RFSS_STATUS_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " TRANSMIT:" + getTransmitChannelNumber() );
sb.append( " RECEIVE:" + getReceiveChannelNumber() );
sb.append( " " + SystemService.toString( getSystemServiceClass() ) );
sb.append( " " + mMessage.toString() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public String getTransmitChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannelNumber();
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public String getReceiveChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannelNumber();
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitIdentifier();
identifiers[ 1 ] = getReceiveIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannelNumber() );
}
}

View File

@@ -0,0 +1,156 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SecondaryControlChannelBroadcast extends LDU1Message
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( SecondaryControlChannelBroadcast.class );
public static final int[] RFSS_ID = { 364,365,366,367,372,373,374,375 };
public static final int[] SITE_ID = { 376,377,382,383,384,385,386,387 };
public static final int[] IDENTIFIER_A = { 536,537,538,539 };
public static final int[] CHANNEL_A = { 540,541,546,547,548,549,550,551,
556,557,558,559 };
public static final int[] SYSTEM_SERVICE_CLASS_A = { 560,561,566,567,568,569,570,571 };
public static final int[] IDENTIFIER_B = { 720,721,722,723 };
public static final int[] CHANNEL_B = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS_B = { 744,745,750,751,752,753,754,755 };
private IBandIdentifier mIdentifierProviderA;
private IBandIdentifier mIdentifierProviderB;
public SecondaryControlChannelBroadcast( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.SECONDARY_CONTROL_CHANNEL_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " CHAN A:" + getChannelA() );
sb.append( " " + SystemService.toString( getSystemServiceClassA() ) );
sb.append( " CHAN B:" + getChannelB() );
sb.append( " " + SystemService.toString( getSystemServiceClassB() ) );
return sb.toString();
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getIdentifierA()
{
return mMessage.getInt( IDENTIFIER_A );
}
public int getChannelA()
{
return mMessage.getInt( CHANNEL_A );
}
public String getChannelNumberA()
{
return getIdentifierA() + "-" + getChannelA();
}
public int getIdentifierB()
{
return mMessage.getInt( IDENTIFIER_B );
}
public int getChannelB()
{
return mMessage.getInt( CHANNEL_B );
}
public String getChannelNumberB()
{
return getIdentifierB() + "-" + getChannelB();
}
public int getSystemServiceClassA()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS_A );
}
public int getSystemServiceClassB()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS_B );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getIdentifierA() )
{
mIdentifierProviderA = message;
}
if( identifier == getIdentifierB() )
{
mIdentifierProviderB = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getIdentifierA();
identifiers[ 1 ] = getIdentifierB();
return identifiers;
}
public long getDownlinkFrequencyA()
{
return calculateDownlink( mIdentifierProviderA, getChannelA() );
}
public long getUplinkFrequencyA()
{
return calculateUplink( mIdentifierProviderA, getChannelA() );
}
public long getDownlinkFrequencyB()
{
return calculateDownlink( mIdentifierProviderB, getChannelB() );
}
public long getUplinkFrequencyB()
{
return calculateUplink( mIdentifierProviderB, getChannelB() );
}
}

View File

@@ -0,0 +1,141 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SecondaryControlChannelBroadcastExplicit
extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( SecondaryControlChannelBroadcastExplicit.class );
public static final int[] RFSS_ID = { 364,365,366,367,372,373,374,375 };
public static final int[] SITE_ID = { 376,377,382,383,384,385,386,387 };
public static final int[] TRANSMIT_IDENTIFIER = { 536,537,538,539 };
public static final int[] TRANSMIT_CHANNEL = { 540,541,546,547,548,549,550,
551,556,557,558,559 };
public static final int[] RECEIVE_IDENTIFIER = { 560,561,566,567 };
public static final int[] RECEIVE_CHANNEL = { 568,569,570,571,720,721,722,
723,724,725,730,731 };
public static final int[] SYSTEM_SERVICE_CLASS = { 732,733,734,735,740,741,
742,743 };
private IBandIdentifier mTransmitIdentifierProvider;
private IBandIdentifier mReceiveIdentifierProvider;
public SecondaryControlChannelBroadcastExplicit( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " TRANSMIT:" + getTransmitChannel() );
sb.append( " RECEIVE:" + getReceiveChannel() );
sb.append( " " + SystemService.toString( getSystemServiceClass() ) );
return sb.toString();
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getTransmitChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getReceiveChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannelNumber();
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierProvider = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierProvider = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitIdentifier();
identifiers[ 1 ] = getReceiveIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierProvider,
getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierProvider,
getReceiveChannelNumber() );
}
}

View File

@@ -0,0 +1,52 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StatusQuery extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( StatusQuery.class );
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public StatusQuery( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.STATUS_QUERY.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getTargetAddress() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,65 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StatusUpdate extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( StatusUpdate.class );
public static final int[] USER_STATUS = { 364,365,366,367,372,373,374,375 };
public static final int[] UNIT_STATUS = { 376,377,382,383,384,385,386,387 };
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public StatusUpdate( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.STATUS_UPDATE.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " STATUS USER:" + getUserStatus() );
sb.append( " UNIT:" + getUnitStatus() );
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getTargetAddress() );
return sb.toString();
}
public String getUserStatus()
{
return mMessage.getHex( USER_STATUS, 2 );
}
public String getUnitStatus()
{
return mMessage.getHex( UNIT_STATUS, 2 );
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,64 @@
package module.decode.p25.message.ldu.lc;
import java.util.List;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SystemServiceBroadcast extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( SystemServiceBroadcast.class );
public static final int[] REQUEST_PRIORITY_LEVEL = { 384,385,386,387 };
public static final int[] AVAILABLE_SERVICES = { 536,537,538,539,540,541,
546,547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SUPPORTED_SERVICES = { 720,721,722,723,724,725,
730,731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public SystemServiceBroadcast( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.SYSTEM_SERVICE_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " SERVICES AVAILABLE " );
sb.append( getAvailableServices() );
sb.append( " SUPPORTED " );
sb.append( getSupportedServices() );
return sb.toString();
}
public List<Service> getAvailableServices()
{
long bitmap = mMessage.getLong( AVAILABLE_SERVICES );
return Service.getServices( bitmap );
}
public List<Service> getSupportedServices()
{
long bitmap = mMessage.getLong( SUPPORTED_SERVICES );
return Service.getServices( bitmap );
}
}

View File

@@ -0,0 +1,81 @@
package module.decode.p25.message.ldu.lc;
import java.util.ArrayList;
import java.util.List;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.Digit;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TelephoneInterconnectAnswerRequest extends LDU1Message
{
private final static Logger mLog = LoggerFactory.getLogger(
TelephoneInterconnectAnswerRequest.class );
public static final int[] DIGIT_1 = { 364,365,366,367 };
public static final int[] DIGIT_2 = { 372,373,374,375 };
public static final int[] DIGIT_3 = { 376,377,382,383 };
public static final int[] DIGIT_4 = { 384,385,386,387 };
public static final int[] DIGIT_5 = { 536,537,538,539 };
public static final int[] DIGIT_6 = { 540,541,546,547 };
public static final int[] DIGIT_7 = { 548,549,550,551 };
public static final int[] DIGIT_8 = { 556,557,558,559 };
public static final int[] DIGIT_9 = { 560,561,566,567 };
public static final int[] DIGIT_10 = { 568,569,570,571 };
public static final int[] TARGET_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public TelephoneInterconnectAnswerRequest( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " TEL:" );
sb.append( getTelephoneNumber() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getTelephoneNumber()
{
List<Integer> digits = new ArrayList<Integer>();
digits.add( mMessage.getInt( DIGIT_1 ) );
digits.add( mMessage.getInt( DIGIT_2 ) );
digits.add( mMessage.getInt( DIGIT_3 ) );
digits.add( mMessage.getInt( DIGIT_4 ) );
digits.add( mMessage.getInt( DIGIT_5 ) );
digits.add( mMessage.getInt( DIGIT_6 ) );
digits.add( mMessage.getInt( DIGIT_7 ) );
digits.add( mMessage.getInt( DIGIT_8 ) );
digits.add( mMessage.getInt( DIGIT_9 ) );
digits.add( mMessage.getInt( DIGIT_10 ) );
return Digit.decode( digits );
}
}

View File

@@ -0,0 +1,96 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.P25Message.DuplexMode;
import module.decode.p25.message.P25Message.SessionMode;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TelephoneInterconnectVoiceChannelUser extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( TelephoneInterconnectVoiceChannelUser.class );
/* Service Options */
public static final int EMERGENCY_FLAG = 376;
public static final int ENCRYPTED_CHANNEL_FLAG = 377;
public static final int DUPLEX_MODE = 382;
public static final int SESSION_MODE = 383;
public static final int[] CALL_TIMER = { 548,549,550,551,556,557,558,559,
560,561,566,567,568,569,570,571 };
public static final int[] ADDRESS = { 720,721,722,723,724,725,730,731,732,
733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public TelephoneInterconnectVoiceChannelUser( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
if( isEncryptedChannel() )
{
sb.append( " ENCRYPTED CHANNEL" );
}
sb.append( " TIMER:" + getCallTimer() + " SECS" );
sb.append( " ADDR:" + getAddress() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncryptedChannel()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getAddress()
{
return mMessage.getHex( ADDRESS, 6 );
}
/**
* Call timer in seconds
*/
public int getCallTimer()
{
int units = mMessage.getInt( CALL_TIMER );
return (int)( units / 10 );
}
}

View File

@@ -0,0 +1,70 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnitAuthenticationCommand extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( UnitAuthenticationCommand.class );
public static final int[] NETWORK_ID = { 364,365,366,367,372,373,374,375,
376,377,382,383,384,385,386,387,536,537,538,539 };
public static final int[] SYSTEM_ID = { 540,541,546,547,548,549,550,551,556,
557,558,559 };
public static final int[] TARGET_ID = { 560,561,566,567,568,569,570,571,720,
721,722,723,724,725,730,731,732,733,734,735,740,741,742,743 };
public UnitAuthenticationCommand( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.UNIT_AUTHENTICATION_COMMAND.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " ADDRESS:" + getCompleteTargetAddress() );
return sb.toString();
}
public String getCompleteTargetAddress()
{
StringBuilder sb = new StringBuilder();
sb.append( getNetworkID() );
sb.append( ":" );
sb.append( getSystemID() );
sb.append( ":" );
sb.append( getTargetID() );
return sb.toString();
}
public String getNetworkID()
{
return mMessage.getHex( NETWORK_ID, 5 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getTargetID()
{
return mMessage.getHex( TARGET_ID, 6 );
}
}

View File

@@ -0,0 +1,71 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnitRegistrationCommand extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( UnitRegistrationCommand.class );
public static final int[] NETWORK_ID = { 364,365,366,367,372,373,374,375,
376,377,382,383,384,385,386,387,536,537,538,539 };
public static final int[] SYSTEM_ID = { 540,541,546,547,548,549,550,551,556,
557,558,559 };
public static final int[] TARGET_ID = { 560,561,566,567,568,569,570,571,720,
721,722,723,724,725,730,731,732,733,734,735,740,741,742,743 };
public UnitRegistrationCommand( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.UNIT_REGISTRATION_COMMAND.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " ADDRESS:" + getCompleteTargetAddress() );
return sb.toString();
}
public String getCompleteTargetAddress()
{
StringBuilder sb = new StringBuilder();
sb.append( getNetworkID() );
sb.append( ":" );
sb.append( getSystemID() );
sb.append( ":" );
sb.append( getTargetID() );
return sb.toString();
}
public String getNetworkID()
{
return mMessage.getHex( NETWORK_ID, 5 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getTargetID()
{
return mMessage.getHex( TARGET_ID, 6 );
}
}

View File

@@ -0,0 +1,89 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.P25Message.DuplexMode;
import module.decode.p25.message.P25Message.SessionMode;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnitToUnitAnswerRequest extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( UnitToUnitAnswerRequest.class );
/* Service Options */
public static final int EMERGENCY_FLAG = 364;
public static final int ENCRYPTED_CHANNEL_FLAG = 365;
public static final int DUPLEX_MODE = 366;
public static final int SESSION_MODE = 367;
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public UnitToUnitAnswerRequest( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.UNIT_TO_UNIT_ANSWER_REQUEST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
if( isEncryptedChannel() )
{
sb.append( " ENCRYPTED CHANNEL" );
}
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getTargetAddress() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncryptedChannel()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,89 @@
package module.decode.p25.message.ldu.lc;
import module.decode.p25.message.P25Message.DuplexMode;
import module.decode.p25.message.P25Message.SessionMode;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnitToUnitVoiceChannelUser extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( UnitToUnitVoiceChannelUser.class );
/* Service Options */
public static final int EMERGENCY_FLAG = 376;
public static final int ENCRYPTED_CHANNEL_FLAG = 377;
public static final int DUPLEX_MODE = 382;
public static final int SESSION_MODE = 383;
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public UnitToUnitVoiceChannelUser( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.UNIT_TO_UNIT_VOICE_CHANNEL_USER.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
if( isEncryptedChannel() )
{
sb.append( " ENCRYPTED CHANNEL" );
}
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getTargetAddress() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncryptedChannel()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,52 @@
package module.decode.p25.message.ldu.lc.motorola;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MotorolaOpcode_0 extends LDU1Message
{
private final static Logger mLog =
LoggerFactory.getLogger( MotorolaOpcode_0.class );
public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
public MotorolaOpcode_0( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.STATUS_QUERY.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getTargetAddress() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,169 @@
package module.decode.p25.message.pdu;
import module.decode.p25.message.P25Message;
import module.decode.p25.message.tsbk.vendor.VendorOpcode;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import module.decode.p25.reference.PDUFormat;
import module.decode.p25.reference.ServiceAccessPoint;
import module.decode.p25.reference.Vendor;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
public class PDUMessage extends P25Message
{
public static final int CONFIRMATION_REQUIRED_INDICATOR = 65;
public static final int PACKET_DIRECTION_INDICATOR = 66;
public static final int[] FORMAT = { 67,68,69,70,71 };
public static final int[] SAP_ID = { 74,75,76,77,78,79 };
public static final int[] VENDOR_ID = { 80,81,82,83,84,85,86,87 };
public static final int[] LOGICAL_LINK_ID = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] BLOCKS_TO_FOLLOW = { 113,114,115,116,117,118,119 };
public static final int[] PAD_OCTET_COUNT = { 123,124,125,126,127 };
public static final int[] OPCODE = { 122,123,124,125,126,127 };
public static final int[] DATA_HEADER_OFFSET = { 138,139,140,141,142,143 };
public static final int[] PDU_CRC = { 144,145,146,147,148,149,150,151,152,
153,154,155,156,157,158,159 };
public PDUMessage( BinaryMessage message, DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Setup a CRC array to hold the header CRC and the multi-block CRC */
mCRC = new CRC[ 2 ];
mCRC[ 0 ] = CRC.PASSED;
}
protected String getMessageStub()
{
StringBuilder sb = new StringBuilder();
Vendor vendor = getVendor();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
if( vendor == Vendor.STANDARD )
{
sb.append( " " );
sb.append( getOpcode().getLabel() );
}
else
{
sb.append( " " );
sb.append( vendor.getLabel() );
}
return sb.toString();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
switch( getFormat() )
{
case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL:
break;
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
sb.append( " PAD OCTETS:" + getPadOctetCount() );
sb.append( " DATA HDR OFFSET:" + getDataHeaderOffset() );
break;
default:
}
sb.append( " " );
sb.append( getConfirmation() );
sb.append( " " );
sb.append( getDirection() );
sb.append( " FMT:" );
sb.append( getFormat().getLabel() );
sb.append( " SAP:" );
sb.append( getServiceAccessPoint().name() );
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
sb.append( " LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " BLKS TO FOLLOW:" );
sb.append( getBlocksToFollowCount() );
return sb.toString();
}
public String getConfirmation()
{
return mMessage.get( CONFIRMATION_REQUIRED_INDICATOR ) ? "CONFIRMED" : "UNCONFIRMED";
}
public String getDirection()
{
return mMessage.get( PACKET_DIRECTION_INDICATOR ) ? "OSP" : "ISP";
}
public boolean isOutbound()
{
return mMessage.get( PACKET_DIRECTION_INDICATOR );
}
public PDUFormat getFormat()
{
return PDUFormat.fromValue( mMessage.getInt( FORMAT ) );
}
public ServiceAccessPoint getServiceAccessPoint()
{
return ServiceAccessPoint.fromValue( mMessage.getInt( SAP_ID ) );
}
public Vendor getVendor()
{
return Vendor.fromValue( mMessage.getInt( VENDOR_ID ) );
}
public String getLogicalLinkID()
{
return mMessage.getHex( LOGICAL_LINK_ID, 6 );
}
public int getBlocksToFollowCount()
{
return mMessage.getInt( BLOCKS_TO_FOLLOW );
}
public int getPadOctetCount()
{
return mMessage.getInt( PAD_OCTET_COUNT );
}
public Opcode getOpcode()
{
if( getFormat() == PDUFormat.ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL )
{
return Opcode.fromValue( mMessage.getInt( OPCODE ) );
}
return Opcode.UNKNOWN;
}
public VendorOpcode getVendorOpcode()
{
if( getFormat() == PDUFormat.ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL )
{
return VendorOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
return VendorOpcode.UNKNOWN;
}
public int getDataHeaderOffset()
{
return mMessage.getInt( DATA_HEADER_OFFSET );
}
}

View File

@@ -0,0 +1,131 @@
package module.decode.p25.message.pdu;
import module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended;
import module.decode.p25.message.pdu.osp.control.CallAlertExtended;
import module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended;
import module.decode.p25.message.pdu.osp.control.GroupAffiliationResponseExtended;
import module.decode.p25.message.pdu.osp.control.MessageUpdateExtended;
import module.decode.p25.message.pdu.osp.control.NetworkStatusBroadcastExtended;
import module.decode.p25.message.pdu.osp.control.ProtectionParameterBroadcast;
import module.decode.p25.message.pdu.osp.control.RFSSStatusBroadcastExtended;
import module.decode.p25.message.pdu.osp.control.RoamingAddressUpdateExtended;
import module.decode.p25.message.pdu.osp.control.StatusQueryExtended;
import module.decode.p25.message.pdu.osp.control.StatusUpdateExtended;
import module.decode.p25.message.pdu.osp.control.UnitRegistrationResponseExtended;
import module.decode.p25.message.pdu.osp.data.GroupDataChannelGrantExtended;
import module.decode.p25.message.pdu.osp.data.IndividualDataChannelGrantExtended;
import module.decode.p25.message.pdu.osp.voice.GroupVoiceChannelGrantExplicit;
import module.decode.p25.message.pdu.osp.voice.TelephoneInterconnectChannelGrantExplicit;
import module.decode.p25.message.pdu.osp.voice.TelephoneInterconnectChannelGrantUpdateExplicit;
import module.decode.p25.message.pdu.osp.voice.UnitToUnitAnswerRequestExplicit;
import module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantExtended;
import module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantUpdateExtended;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import module.decode.p25.reference.PDUFormat;
import module.decode.p25.reference.Vendor;
import alias.AliasList;
import bits.BinaryMessage;
public class PDUMessageFactory
{
public static PDUMessage getMessage( BinaryMessage message,
DataUnitID duid,
AliasList aliasList )
{
PDUFormat format = PDUFormat.fromValue(
message.getInt( PDUMessage.FORMAT ) );
switch( format )
{
case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL:
Vendor vendor = Vendor.fromValue(
message.getInt( PDUMessage.VENDOR_ID ) );
switch( vendor )
{
case STANDARD:
Opcode opcode = Opcode.fromValue(
message.getInt( PDUMessage.OPCODE ) );
switch( opcode )
{
case ADJACENT_STATUS_BROADCAST:
return new AdjacentStatusBroadcastExtended(
message, duid, aliasList );
case CALL_ALERT:
return new CallAlertExtended( message, duid,
aliasList );
case GROUP_AFFILIATION_QUERY:
return new GroupAffiliationQueryExtended(
message, duid, aliasList );
case GROUP_AFFILIATION_RESPONSE:
return new GroupAffiliationResponseExtended(
message, duid, aliasList );
case GROUP_DATA_CHANNEL_GRANT:
return new GroupDataChannelGrantExtended(
message, duid, aliasList );
case GROUP_VOICE_CHANNEL_GRANT:
return new GroupVoiceChannelGrantExplicit(
message, duid, aliasList );
case INDIVIDUAL_DATA_CHANNEL_GRANT:
return new IndividualDataChannelGrantExtended(
message, duid, aliasList );
case MESSAGE_UPDATE:
return new MessageUpdateExtended( message, duid,
aliasList );
case NETWORK_STATUS_BROADCAST:
return new NetworkStatusBroadcastExtended(
message, duid, aliasList );
case PROTECTION_PARAMETER_BROADCAST:
return new ProtectionParameterBroadcast(
message, duid, aliasList );
case RFSS_STATUS_BROADCAST:
return new RFSSStatusBroadcastExtended( message,
duid, aliasList );
case ROAMING_ADDRESS_UPDATE:
return new RoamingAddressUpdateExtended(
message, duid, aliasList );
case STATUS_QUERY:
return new StatusQueryExtended( message, duid,
aliasList );
case STATUS_UPDATE:
return new StatusUpdateExtended( message, duid,
aliasList );
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT:
return new TelephoneInterconnectChannelGrantExplicit(
message, duid, aliasList );
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE:
return new TelephoneInterconnectChannelGrantUpdateExplicit(
message, duid, aliasList );
case UNIT_REGISTRATION_RESPONSE:
return new UnitRegistrationResponseExtended(
message, duid, aliasList );
case UNIT_TO_UNIT_ANSWER_REQUEST:
return new UnitToUnitAnswerRequestExplicit(
message, duid, aliasList );
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT:
return new UnitToUnitVoiceChannelGrantExtended(
message, duid, aliasList );
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE:
return new UnitToUnitVoiceChannelGrantUpdateExtended(
message, duid, aliasList );
default:
break;
}
case MOTOROLA:
break;
default:
break;
}
return new PDUMessage( message, duid, aliasList );
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
return new PDUMessage( message, duid, aliasList );
default:
return new PDUMessage( message, duid, aliasList );
}
}
}

View File

@@ -0,0 +1,200 @@
package module.decode.p25.message.pdu;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public abstract class UnitToUnitChannelGrantExtended extends PDUMessage
implements IdentifierReceiver
{
/* Service Options */
public static final int EMERGENCY_FLAG = 128;
public static final int ENCRYPTED_CHANNEL_FLAG = 129;
public static final int DUPLEX_MODE = 130;
public static final int SESSION_MODE = 131;
public static final int[] SOURCE_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SERVICE_OPTIONS = { 128,129,130,131,132,133,134,
135 };
public static final int[] SOURCE_WACN = { 160,161,162,163,164,165,166,167,
168,169,170,171,172,173,174,175,176,177,178,179 };
public static final int[] SOURCE_SYSTEM_ID = { 180,181,182,183,184,185,186,
187,188,189,190,191 };
public static final int[] SOURCE_ID = { 192,193,194,195,196,197,198,199,200,
201,202,203,204,205,206,207,208,209,210,211,212,213,214,215 };
public static final int[] TARGET_ADDRESS = { 216,217,218,219,220,221,222,
223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239 };
public static final int[] TRANSMIT_IDENTIFIER = { 240,241,242,243 };
public static final int[] TRANSMIT_NUMBER = { 244,245,246,247,248,249,250,
251,252,253,254,255 };
public static final int[] RECEIVE_IDENTIFIER = { 256,257,258,259 };
public static final int[] RECEIVE_NUMBER = { 260,261,262,263,264,265,266,
267,268,269,270,271 };
public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325,
326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,
343,344,345,346,347,348,349,350,351 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public UnitToUnitChannelGrantExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU2( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
sb.append( " FROM ADDR:" );
sb.append( getSourceAddress() );
sb.append( " ID:" );
sb.append( getSourceID() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " WACN:" );
sb.append( getSourceWACN() );
sb.append( " SYS:" );
sb.append( getSourceSystemID() );
sb.append( " CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber() );
sb.append( " " + getDownlinkFrequency() );
sb.append( " CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber() );
sb.append( " " + getUplinkFrequency() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
public String getSourceWACN()
{
return mMessage.getHex( SOURCE_WACN, 5 );
}
public String getSourceSystemID()
{
return mMessage.getHex( SOURCE_SYSTEM_ID, 3 );
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public int getTransmitChannelIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_NUMBER );
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_NUMBER );
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannelNumber() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitChannelIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveChannelIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitChannelIdentifier();
idens[ 1 ] = getReceiveChannelIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,209 @@
package module.decode.p25.message.pdu.confirmed;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.PDUType;
import module.decode.p25.reference.Vendor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCP25;
public class PDUConfirmedMessage extends PDUMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( PDUConfirmedMessage.class );
public static final int SEQUENCE_RESET_FLAG = 128; //SYN
public static final int[] PACKET_SEQUENCE_NUMBER = { 129,130,131 };
public static final int FINAL_FRAGMENT_FLAG = 132;
public static final int[] FRAGMENT_SEQUENCE_NUMBER = { 133,134,135 };
public static final int[] PDU_TYPE = { 176,177,178,179 };
public PDUConfirmedMessage( BinaryMessage message, AliasList aliasList )
{
super( message, DataUnitID.PDUC, aliasList );
checkCRC();
}
/**
* Constructor to support subclassing of this message. Initial construction
* of the message enables CRC checks to be calculated and message type to
* be inspected so that we can construct an accurate subclass message,
* without having to recalculate CRC checksums.
*/
protected PDUConfirmedMessage( PDUConfirmedMessage message )
{
super( message.getSourceMessage(), DataUnitID.PDUC, message.getAliasList() );
mCRC = message.getCRCResults();
}
private void checkCRC()
{
int blocks = getBlocksToFollowCount();
/* The NID and Header have already passed CRC */
mCRC = new CRC[ 2 + blocks ];
mCRC[ 0 ] = CRC.PASSED;
mCRC[ 1 ] = CRC.PASSED;
for( int x = 0; x < getBlocksToFollowCount(); x++ )
{
/* Data blocks start at 160 and every 144 thereafter */
mCRC[ x + 2 ] = CRCP25.checkCRC9( mMessage, 160 + ( x * 144 ) );
}
//TODO: check the final 4-byte end of block sequence CRC
}
/* Override for now */
public boolean isValid()
{
return true;
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "[" + this.getClass() + "]" );
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
sb.append( " " );
sb.append( getDirection() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "] LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " " );
sb.append( getFormat().getLabel() );
sb.append( " SAP:" );
sb.append( getServiceAccessPoint().getLabel() );
if( getVendor() != Vendor.STANDARD )
{
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
}
sb.append( " PACKET #" );
sb.append( getPacketSequenceNumber() );
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
{
sb.append( ".C" );
}
else
{
sb.append( "." );
sb.append( getFragmentSequenceNumber() );
if( isFinalFragment() )
{
sb.append( "C" );
}
}
sb.append( " BLOCKS:" );
sb.append( getBlocksToFollowCount() );
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
/**
* Packet Data Unit Type
*/
public PDUType getPDUType()
{
return PDUType.fromValue( mMessage.getInt( PDU_TYPE ), isOutbound() );
}
/**
* Number of bytes/octets contained in the message, exclusive of the data
* header.
*/
public int getOctetCount()
{
/* 16 octets per block, and 12 octets in final block */
int count = getBlocksToFollowCount() * 16 - 4;
int padding = getPadOctetCount();
/* Hack: motorola sndcp packet data control - when it says there are 15
* pad octets and only 1 data block of 12 data octets, that means there
* are more pad octets than available data and they appear to suppress
* the final padded data block containing the 12 pad octets. The data
* block count should be 2, but it says 1, so they're either not sending
* the final block, or there is an error in the block count */
if( padding > count )
{
count -= ( padding - 12 );
}
else
{
count -= padding;
}
count -= getDataHeaderOffset();
return count;
}
/**
* Indicates that the receiver should accept this packet, despite the
* apparent out-of-order packet and fragment sequence numbering. This
* indicates that something happened to the synchronization between
* the tower and the radio and now the tower commands the radio to reset
* the sequence numbers and accept this packet/fragment instead of discarding
* it as a duplicate.
*/
public boolean isSequenceReset()
{
return mMessage.get( SEQUENCE_RESET_FLAG );
}
/**
* Packet sequence number. Numbers run from 0 to 7 and will continually
* roll-over if there are more than 8 total packets.
*/
public int getPacketSequenceNumber()
{
return mMessage.getInt( PACKET_SEQUENCE_NUMBER );
}
/**
* Indicates that this packet fragment is the final fragment
*/
public boolean isFinalFragment()
{
return mMessage.get( FINAL_FRAGMENT_FLAG );
}
/**
* Packet fragment sequence number
*/
public int getFragmentSequenceNumber()
{
return mMessage.getInt( FRAGMENT_SEQUENCE_NUMBER );
}
public String toString()
{
return getMessage();
}
}

View File

@@ -0,0 +1,80 @@
package module.decode.p25.message.pdu.confirmed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PDUConfirmedMessageFactory
{
public final static Logger mLog =
LoggerFactory.getLogger( PDUConfirmedMessage.class );
public static PDUConfirmedMessage getMessage( PDUConfirmedMessage message )
{
switch( message.getServiceAccessPoint() )
{
case ADDRESS_RESOLUTION_PROTOCOL:
break;
case CHANNEL_REASSIGNMENT:
break;
case CIRCUIT_DATA:
break;
case CIRCUIT_DATA_CONTROL:
break;
case ENCRYPTED_KEY_MANAGEMENT_MESSAGE:
break;
case ENCRYPTED_TRUNKING_CONTROL:
break;
case ENCRYPTED_USER_DATA:
break;
case EXTENDED_ADDRESS:
break;
case MR_CONFIGURATION:
break;
case MR_LOOPBACK:
break;
case MR_OUT_OF_SERVICE:
break;
case MR_PAGING:
break;
case MR_STATISTICS:
break;
case PACKET_DATA:
break;
case REGISTRATION_AND_AUTHORIZATION:
break;
/* Deliberate fall-through */
case SNDCP_PACKET_DATA_CONTROL:
case UNENCRYPTED_USER_DATA:
switch( message.getPDUType() )
{
case SN_ACTIVATE_TDS_CONTEXT_ACCEPT:
return new SNDCPActivateTDSContextAccept( message );
case SN_ACTIVATE_TDS_CONTEXT_REJECT:
return new SNDCPActivateTDSContextReject( message );
case SN_ACTIVATE_TDS_CONTEXT_REQUEST:
return new SNDCPActivateTDSContextRequest( message );
case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT:
case SN_DEACTIVATE_TDS_CONTEXT_REQUEST:
return new SNDCPDeactivateTDSContext( message );
case SN_RF_CONFIRMED_DATA:
return new SNDCPUserData( message );
case SN_RF_UNCONFIRMED_DATA:
break;
default:
break;
}
break;
case SYSTEM_CONFIGURATION:
break;
case UNENCRYPTED_KEY_MANAGEMENT_MESSAGE:
break;
case UNENCRYPTED_TRUNKING_CONTROL:
break;
default:
}
return message;
}
}

View File

@@ -0,0 +1,253 @@
package module.decode.p25.message.pdu.confirmed;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.MDPConfigurationOption;
import module.decode.p25.reference.PDUType;
import module.decode.p25.reference.SNDCPActivationRejectReason;
import module.decode.p25.reference.Vendor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCP25;
public class SNDCPActivateTDSContextAccept extends PDUConfirmedMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( SNDCPActivateTDSContextAccept.class );
/* SN-Activate TDS Context Accept */
public static final int[] NSAPI = { 180,181,182,183 };
public static final int[] PDUPM = { 184,185,186,187 };
public static final int[] READY = { 188,189,190,191 };
public static final int[] STANDBY = { 192,193,194,195 };
public static final int[] NAT = { 196,197,198,199 };
public static final int[] IP_1 = { 200,201,202,203,204,205,206,207 };
public static final int[] IP_2 = { 208,209,210,211,212,213,214,215 };
public static final int[] IP_3 = { 216,217,218,219,220,221,222,223 };
public static final int[] IP_4 = { 224,225,226,227,228,229,230,231 };
public static final int[] IPHC = { 232,233,234,235,236,237,238,239 };
public static final int[] TCPSS = { 240,241,242,243 };
public static final int[] UDPSS = { 244,245,246,247 };
public static final int[] MTU = { 248,249,250,251 };
public static final int[] UDPC = { 252,253,254,255 };
public static final int[] MDPCO = { 256,257,258,259,260,261,262,263 };
public static final int[] DATA_ACCESS_CONTROL = { 264,265,266,267,268,
269,270,271,272,273,274,275,276,277,278,279 };
public SNDCPActivateTDSContextAccept( PDUConfirmedMessage message )
{
super( message );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " PDUC LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " ACCEPT SNDCP PACKET DATA ACTIVATE " );
sb.append( getNetworkAddressType() );
sb.append( " " );
sb.append( getIPAddress() );
sb.append( " NSAPI:" );
sb.append( getNSAPI() );
sb.append( " MTU:" );
sb.append( getMaximumTransmissionUnit() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "]" );
sb.append( " PACKET #" );
sb.append( getPacketSequenceNumber() );
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
{
sb.append( ".C" );
}
else
{
sb.append( "." );
sb.append( getFragmentSequenceNumber() );
if( isFinalFragment() )
{
sb.append( "C" );
}
}
sb.append( " " );
return sb.toString();
}
/**
* Network Service Access Point Identifier - up to 14 NSAPI's can be
* allocated to the mobile with each NSAPI to be used for a specific
* protocol layer.
*/
public int getNSAPI()
{
return mMessage.getInt( NSAPI );
}
public int getPDUPriorityMaximum()
{
return mMessage.getInt( PDUPM );
}
public String getReadyTimer()
{
switch( mMessage.getInt( READY ) )
{
case 1:
return "1 Second";
case 2:
return "2 Seconds";
case 3:
return "4 Seconds";
case 4:
return "6 Seconds";
case 5:
return "8 Seconds";
case 6:
return "10 Seconds";
case 7:
return "15 Seconds";
case 8:
return "20 Seconds";
case 9:
return "25 Seconds";
case 10:
return "30 Seconds";
case 11:
return "60 Seconds";
case 12:
return "120 Seconds";
case 13:
return "180 Seconds";
case 14:
return "300 Seconds";
case 15:
return "Always in Ready";
}
return "UNKNOWN";
}
public String getStandbyTimer()
{
switch( mMessage.getInt( STANDBY ) )
{
case 1:
return "10 Seconds";
case 2:
return "30 Seconds";
case 3:
return "1 Minutes";
case 4:
return "5 Minutes";
case 5:
return "10 Minutes";
case 6:
return "30 Minutes";
case 7:
return "1 Hour";
case 8:
return "2 Hours";
case 9:
return "4 Hours";
case 10:
return "8 Hours";
case 11:
return "12 Hours";
case 12:
return "24 Hours";
case 13:
return "48 Hours";
case 14:
return "72 Hours";
case 15:
return "Always in Ready";
}
return "Unknown";
}
public String getNetworkAddressType()
{
return mMessage.getInt( NAT ) == 0 ? "IPV4 STATIC" : "IPV4 DYNAMIC";
}
public String getIPAddress()
{
StringBuilder sb = new StringBuilder();
sb.append( mMessage.getInt( IP_1 ) );
sb.append( "." );
sb.append( mMessage.getInt( IP_2 ) );
sb.append( "." );
sb.append( mMessage.getInt( IP_3 ) );
sb.append( "." );
sb.append( mMessage.getInt( IP_4 ) );
return sb.toString();
}
public boolean hasIPHeaderCompression()
{
return mMessage.getInt( IPHC ) == 1;
}
public boolean hasUserDataPayloadCompression()
{
return mMessage.getInt( UDPC ) == 1;
}
public int getTCPStateSlots()
{
return mMessage.getInt( TCPSS );
}
public int getUDPStateSlots()
{
return mMessage.getInt( UDPSS );
}
public String getMaximumTransmissionUnit()
{
int mtu = mMessage.getInt( MTU );
switch( mtu )
{
case 1:
return "296 BYTES";
case 2:
return "510 BYTES";
case 3:
return "1020 BYTES";
case 4:
return "1500 BYTES";
default:
return "UNK-" + mtu;
}
}
public MDPConfigurationOption getMDPConfigurationOption()
{
return MDPConfigurationOption.fromValue( mMessage.getInt( MDPCO ) );
}
public String getDataAccessControl()
{
return mMessage.getHex( DATA_ACCESS_CONTROL, 4 );
}
}

View File

@@ -0,0 +1,54 @@
package module.decode.p25.message.pdu.confirmed;
import module.decode.p25.reference.SNDCPActivationRejectReason;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SNDCPActivateTDSContextReject extends PDUConfirmedMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( SNDCPActivateTDSContextReject.class );
public static final int[] NSAPI = { 180,181,182,183 };
public static final int[] REJECT_CODE = { 184,185,186,187,188,189,190,191 };
public SNDCPActivateTDSContextReject( PDUConfirmedMessage message )
{
super( message );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " PDUC LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " REJECT SNDCP PACKET DATA ACTIVATE - REASON:" );
sb.append( getReason().getLabel() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "] " );
sb.append( mMessage.toString() );
return sb.toString();
}
/**
* Network Service Access Point Identifier - up to 14 NSAPI's can be
* allocated to the mobile with each NSAPI to be used for a specific
* protocol layer.
*/
public int getNSAPI()
{
return mMessage.getInt( NSAPI );
}
public SNDCPActivationRejectReason getReason()
{
return SNDCPActivationRejectReason.fromValue( mMessage.getInt( REJECT_CODE ) );
}
}

View File

@@ -0,0 +1,149 @@
package module.decode.p25.message.pdu.confirmed;
import module.decode.p25.reference.MDPConfigurationOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SNDCPActivateTDSContextRequest extends PDUConfirmedMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( SNDCPActivateTDSContextRequest.class );
public static final int[] SNDCP_VERSION = { 180,181,182,183 };
public static final int[] NSAPI = { 184,185,186,187 };
public static final int[] NAT = { 188,189,190,191 };
public static final int[] IP_1 = { 192,193,194,195,196,197,198,199 };
public static final int[] IP_2 = { 200,201,202,203,204,205,206,207 };
public static final int[] IP_3 = { 208,209,210,211,212,213,214,215 };
public static final int[] IP_4 = { 216,217,218,219,220,221,222,223 };
public static final int[] DSUT = { 224,225,226,227 };
public static final int[] UDPC = { 228,229,230,231 };
public static final int[] IPHC = { 232,233,234,235,236,237,238,239 };
public static final int[] TCPSS = { 240,241,242,243 };
public static final int[] UDPSS = { 244,245,246,247 };
public static final int[] MDPCO = { 248,249,250,251,252,253,254,255 };
public SNDCPActivateTDSContextRequest( PDUConfirmedMessage message )
{
super( message );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " PDUC LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " REQUEST SNDCP PACKET DATA ACTIVATE " );
sb.append( getNetworkAddressType() );
sb.append( " " );
sb.append( getIPAddress() );
sb.append( " NSAPI:" );
sb.append( getNSAPI() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "]" );
sb.append( " PACKET #" );
sb.append( getPacketSequenceNumber() );
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
{
sb.append( ".C" );
}
else
{
sb.append( "." );
sb.append( getFragmentSequenceNumber() );
if( isFinalFragment() )
{
sb.append( "C" );
}
}
sb.append( " " );
return sb.toString();
}
/**
* SNDCP Version: 1 = P25 SNDCP Version 1
*/
public int getSNDCPVersion()
{
return mMessage.getInt( SNDCP_VERSION );
}
/**
* Network Service Access Point Identifier - up to 14 NSAPI's can be
* allocated to the mobile with each NSAPI to be used for a specific
* protocol layer.
*/
public int getNSAPI()
{
return mMessage.getInt( NSAPI );
}
public String getNetworkAddressType()
{
return mMessage.getInt( NAT ) == 0 ? "IPV4 STATIC" : "IPV4 DYNAMIC";
}
public String getIPAddress()
{
StringBuilder sb = new StringBuilder();
sb.append( mMessage.getInt( IP_1 ) );
sb.append( "." );
sb.append( mMessage.getInt( IP_2 ) );
sb.append( "." );
sb.append( mMessage.getInt( IP_3 ) );
sb.append( "." );
sb.append( mMessage.getInt( IP_4 ) );
return sb.toString();
}
public String getDataSubscriberUnitType()
{
switch( mMessage.getInt( DSUT ) )
{
case 0:
return "DATA ONLY MRC";
case 1:
return "DATA AND VOICE MRC";
}
return "UNKNOWN";
}
public boolean hasIPHeaderCompression()
{
return mMessage.getInt( IPHC ) == 1;
}
public boolean hasUserDataPayloadCompression()
{
return mMessage.getInt( UDPC ) == 1;
}
public int getTCPStateSlots()
{
return mMessage.getInt( TCPSS );
}
public int getUDPStateSlots()
{
return mMessage.getInt( UDPSS );
}
public MDPConfigurationOption getMDPConfigurationOption()
{
return MDPConfigurationOption.fromValue( mMessage.getInt( MDPCO ) );
}
}

View File

@@ -0,0 +1,61 @@
package module.decode.p25.message.pdu.confirmed;
import module.decode.p25.reference.PDUType;
import module.decode.p25.reference.SNDCPActivationRejectReason;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SNDCPDeactivateTDSContext extends PDUConfirmedMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( SNDCPDeactivateTDSContext.class );
public static final int[] NSAPI = { 180,181,182,183 };
public static final int[] DEACTIVATION_TYPE = { 184,185,186,187,188,189,
190,191 };
public SNDCPDeactivateTDSContext( PDUConfirmedMessage message )
{
super( message );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " PDUC LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " REQUEST SNDCP PACKET DATA DEACTIVATE " );
sb.append( getDeactivationType() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "] " );
return sb.toString();
}
/**
* Network Service Access Point Identifier - up to 14 NSAPI's can be
* allocated to the mobile with each NSAPI to be used for a specific
* protocol layer.
*/
public int getNSAPI()
{
return mMessage.getInt( NSAPI );
}
public String getDeactivationType()
{
if( getPDUType() == PDUType.SN_DEACTIVATE_TDS_CONTEXT_REQUEST )
{
return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ?
"ALL NSAPIS" : "THIS NSAPI";
}
return null;
}
}

View File

@@ -0,0 +1,356 @@
package module.decode.p25.message.pdu.confirmed;
import module.decode.p25.reference.IPHeaderCompression;
import module.decode.p25.reference.IPProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SNDCPUserData extends PDUConfirmedMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( SNDCPUserData.class );
public static final int DATA_BLOCK_START = 176;
public static final int[] NSAPI = { 180,181,182,183 };
public static final int[] PCOMP = { 184,185,186,187 };
public static final int[] DCOMP = { 188,189,190,191 };
/* IP Packet Header */
public static final int[] IP_VERSION = { 192,193,194,195 };
public static final int[] IHL = { 196,197,198,199 };
public static final int[] DSCP = { 200,201,202,203,204,205 };
public static final int[] ECN = { 206,207 };
public static final int[] TOTAL_LENGTH = { 208,209,210,211,212,213,214,215,
216,217,218,219,220,221,222,223 };
public static final int[] IDENTIFICATION = { 224,225,226,227,228,229,230,231,
232,233,234,235,236,237,238,239 };
public static final int[] FLAGS = { 240,241,242 };
public static final int[] FRAGMENT_OFFSET = { 243,244,245,246,247,248,249,
250,251,252,253,254,255 };
public static final int[] TTL = { 256,257,258,259,260,261,262,263 };
public static final int[] PROTOCOL = { 264,265,266,267,268,269,270,271 };
public static final int[] HEADER_CHECKSUM = { 272,273,274,275,276,277,278,
279,280,281,282,283,284,285,286,287 };
public static final int[] SOURCE_IP_1 = { 288,289,290,291,292,293,294,295 };
public static final int[] SOURCE_IP_2 = { 296,297,298,299,300,301,302,303 };
public static final int[] BLOCK_SERIAL_2 = { 304,305,306,307,308,309,310 };
public static final int[] BLOCK_CRC9_2 = { 311,312,313,314,315,316,317,318,
319 };
public static final int[] SOURCE_IP_3 = { 320,321,322,323,324,325,326,327 };
public static final int[] SOURCE_IP_4 = { 328,329,330,331,332,333,334,335 };
public static final int[] DESTINATION_IP_1 = { 336,337,338,339,340,341,342,343 };
public static final int[] DESTINATION_IP_2 = { 344,345,346,347,348,349,350,351 };
public static final int[] DESTINATION_IP_3 = { 352,353,354,355,356,357,358,359 };
public static final int[] DESTINATION_IP_4 = { 360,361,362,363,364,365,366,367 };
public static final int IP_DATA_START = 368;
/* User Datagram Protocol */
public static final int[] UDP_SOURCE_PORT = { 368,369,370,371,372,373,374,
375 };
public static final int[] UDP_DESTINATION_PORT = { 376,377,378,379,380,381,
382,383 };
public static final int[] UDP_LENGTH = { 384,385,386,387,388,389,390,391,
392,393,394,395,396,397,398,399 };
public static final int[] UDP_CHECKSUM = { 400,401,402,403,404,405,406,407,
408,409,410,411,412,413,414,415 };
public static final int UDP_DATA_START = 416;
public SNDCPUserData( PDUConfirmedMessage message )
{
super( message );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " PDUC LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " SNDCP NSAPI:" );
sb.append( getNSAPI() );
sb.append( " " );
IPProtocol protocol = getIPProtocol();
sb.append( protocol.getLabel() );
sb.append( "/IP" );
sb.append( " FM:" );
sb.append( getSourceIPAddress() );
if( protocol == IPProtocol.UDP )
{
sb.append( ":" );
sb.append( getUDPSourcePort() );
}
sb.append( " TO:" );
sb.append( getDestinationIPAddress() );
if( protocol == IPProtocol.UDP )
{
sb.append( ":" );
sb.append( getUDPDestinationPort() );
}
sb.append( " DATA:" );
sb.append( getPayload() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "]" );
sb.append( " PACKET #" );
sb.append( getPacketSequenceNumber() );
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
{
sb.append( ".C" );
}
else
{
sb.append( "." );
sb.append( getFragmentSequenceNumber() );
if( isFinalFragment() )
{
sb.append( "C" );
}
}
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
/**
* Network Service Access Point Identifier - up to 14 NSAPI's can be
* allocated to the mobile with each NSAPI to be used for a specific
* protocol layer.
*/
public int getNSAPI()
{
return mMessage.getInt( NSAPI );
}
public IPHeaderCompression getIPHeaderCompressionState()
{
return IPHeaderCompression.fromValue( mMessage.getInt( PCOMP ) );
}
/**
* No enum values were defined for this
*/
public boolean hasUserDataCompression()
{
return mMessage.getInt( DCOMP ) > 0;
}
/**
* IP Version - should always be 4 (IPV4)
*/
public int getIPVersion()
{
return mMessage.getInt( IP_VERSION );
}
/**
* IP packet header length in 32-bit words
*/
public int getInternetHeaderLength()
{
return mMessage.getInt( IHL );
}
/**
* IP packet DSCP
*/
public int getDifferentiatedServicesCodePoint()
{
return mMessage.getInt( DSCP );
}
/**
* IP packet ECN
*/
public int getExplicitCongestionNotification()
{
return mMessage.getInt( ECN );
}
/**
* IP packet size in bytes, including header and data
*/
public int getTotalLength()
{
return mMessage.getInt( TOTAL_LENGTH );
}
/**
* IP packet identification
*/
public int getIdentification()
{
return mMessage.getInt( IDENTIFICATION );
}
/**
* IP packet flags. 0-Reserved, 1-Don't Fragment, 2-More Fragments
*/
public int getFlags()
{
return mMessage.getInt( FLAGS );
}
public boolean isFragment()
{
return getFlags() > 0;
}
/**
* IP packet - identifies the offset value in 8-byte blocks (64-bit blocks)
* for this packet fragment from the beginning.
*/
public int getFragmentOffset()
{
return mMessage.getInt( FRAGMENT_OFFSET );
}
/**
* IP packet time (hops) to live
*/
public int getTimeToLive()
{
return mMessage.getInt( TTL );
}
/**
* Identifies the IP protocol carried by the IP packet
*/
public IPProtocol getIPProtocol()
{
return IPProtocol.fromValue( mMessage.getInt( PROTOCOL ) );
}
/**
* Source IPV4 address
*/
public String getSourceIPAddress()
{
StringBuilder sb = new StringBuilder();
sb.append( mMessage.getInt( SOURCE_IP_1 ) );
sb.append( "." );
sb.append( mMessage.getInt( SOURCE_IP_2 ) );
sb.append( "." );
sb.append( mMessage.getInt( SOURCE_IP_3 ) );
sb.append( "." );
sb.append( mMessage.getInt( SOURCE_IP_4 ) );
return sb.toString();
}
/**
* Destination IPV4 address
*/
public String getDestinationIPAddress()
{
StringBuilder sb = new StringBuilder();
sb.append( mMessage.getInt( DESTINATION_IP_1 ) );
sb.append( "." );
sb.append( mMessage.getInt( DESTINATION_IP_2 ) );
sb.append( "." );
sb.append( mMessage.getInt( DESTINATION_IP_3 ) );
sb.append( "." );
sb.append( mMessage.getInt( DESTINATION_IP_4 ) );
return sb.toString();
}
/**
* UDP source port
*/
public int getUDPSourcePort()
{
return mMessage.getInt( UDP_SOURCE_PORT );
}
/**
* UDP destination port
*/
public int getUDPDestinationPort()
{
return mMessage.getInt( UDP_DESTINATION_PORT );
}
/**
* UDP data payload size in bytes
*/
public int getUDPDataLength()
{
return mMessage.getInt( UDP_LENGTH );
}
/**
* Hex string representation of the packet data, including packet header
* data if present.
*/
public String getPayload()
{
StringBuilder sb = new StringBuilder();
/* Adjust for PDU header (2 bytes) plus IP Packet header (20 bytes) */
int start = 22;
if( getIPProtocol() == IPProtocol.UDP )
{
/* Adjust for UDP header (8 bytes) */
start = 30;
}
int spacingCounter = 0;
/* Append octets inserting a space between each 32-bit value */
int octets = getOctetCount() + getDataHeaderOffset();
for( int x = start; x < octets; x++ )
{
sb.append( getPacketOctet( x ) );
spacingCounter++;
if( spacingCounter >= 4 )
{
sb.append( " " );
spacingCounter = 0;
}
}
return sb.toString();
}
/**
* Returns the octet identified by the 0-indexed 'octet' argument location.
* If there is a Data Header Offset, the 0 index points to the first octet
* of the data header. Otherwise, the 0 index points to the first octet
* of the packet. You must account for the data header offset when
* specifying the octet.
*/
public String getPacketOctet( int octet )
{
int block = (int)( octet / 16 );
int offset = octet % 16;
int start = DATA_BLOCK_START + ( block * 144 ) + ( offset * 8 );
return mMessage.getHex( start, start + 7, 2 );
}
}

View File

@@ -0,0 +1,161 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class AdjacentStatusBroadcastExtended extends PDUMessage
implements IdentifierReceiver
{
public static final int[] LRA = { 88,89,90,91,92,93,94,95 };
public static final int[] SYSTEM_ID = { 100,101,102,103,104,105,106,107,108,
109,110,111 };
public static final int[] RF_SUBSYSTEM_ID = { 128,129,130,131,132,133,134,
135 };
public static final int[] SITE_ID = { 136,137,138,139,140,141,142,143 };
public static final int[] TRANSMIT_IDENTIFIER = { 160,161,162,163 };
public static final int[] TRANSMIT_CHANNEL = { 164,165,166,167,168,169,170,
171,172,173,174,175 };
public static final int[] RECEIVE_IDENTIFIER = { 176,177,178,179 };
public static final int[] RECEIVE_CHANNEL = { 180,181,182,183,184,185,186,
187,188,189,190,191 };
public static final int[] SYSTEM_SERVICE_CLASS = { 192,193,194,195,196,197,
198,199 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
private IBandIdentifier mTransmitIdentifierProvider;
private IBandIdentifier mReceiveIdentifierProvider;
public AdjacentStatusBroadcastExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.ADJACENT_STATUS_BROADCAST.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SYS:" + getSystemID() );
sb.append( " RFSS:" + getRFSubsystemID() );
sb.append( " SITE:" + getSiteID() );
sb.append( " CTRL CHAN:" + getReceiveIdentifier() + "-" + getReceiveChannel() );
sb.append( " DN:" + getDownlinkFrequency() );
sb.append( " " + getTransmitIdentifier() + "-" + getTransmitChannel() );
sb.append( " UP:" + getUplinkFrequency() );
sb.append( " SYS SVC CLASS:" +
SystemService.toString( getSystemServiceClass() ) );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RF_SUBSYSTEM_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannel()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannel()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierProvider, getTransmitChannel() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierProvider, getReceiveChannel() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierProvider = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierProvider = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitIdentifier();
idens[ 1 ] = getReceiveIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,81 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class CallAlertExtended extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] WACN = { 128,129,130,131,132,133,134,135,136,137,
138,139,140,141,142,143,160,161,162,163 };
public static final int[] SYSTEM_ID = { 164,165,166,167,168,169,170,171,172,
173,174,175 };
public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184,
185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
public CallAlertExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.CALL_ALERT.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " FROM:" );
sb.append( getSourceID() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " WACN:" );
sb.append( getWACN() );
sb.append( " SYS:" );
sb.append( getSystemID() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getWACN()
{
return mMessage.getHex( WACN, 5 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
}

View File

@@ -0,0 +1,81 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class GroupAffiliationQueryExtended extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] WACN = { 128,129,130,131,132,133,134,135,136,137,
138,139,140,141,142,143,160,161,162,163 };
public static final int[] SYSTEM_ID = { 164,165,166,167,168,169,170,171,172,
173,174,175 };
public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184,
185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
public GroupAffiliationQueryExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.GROUP_AFFILIATION_QUERY.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " FROM:" );
sb.append( getSourceID() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " WACN:" );
sb.append( getWACN() );
sb.append( " SYS:" );
sb.append( getSystemID() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getWACN()
{
return mMessage.getHex( WACN, 5 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
}

View File

@@ -0,0 +1,132 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import module.decode.p25.reference.Response;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class GroupAffiliationResponseExtended extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135,
136,137,138,139,140,141,142,143,160,161,162,163 };
public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170,
171,172,173,174,175 };
public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184,
185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 };
public static final int[] GROUP_WACN = { 200,201,202,203,204,205,206,207,
208,209,210,211,212,213,214,215,216,217,218,219 };
public static final int[] GROUP_SYSTEM_ID = { 220,221,222,223,224,225,226,
227,228,229,230,231 };
public static final int[] GROUP_ID = { 232,233,234,235,236,237,238,239,240,
241,242,243,244,245,246,247 };
public static final int[] ANNOUNCEMENT_GROUP_ID = { 248,249,250,251,252,253,
254,255,256,257,258,259,260,261,262,263 };
public static final int[] GROUP_AFFILIATION_RESPONSE_VALUE = { 270,271 };
public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325,
326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,
343,344,345,346,347,348,349,350,351 };
public GroupAffiliationResponseExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.GROUP_AFFILIATION_RESPONSE.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( "AFFILIATION:" );
sb.append( getResponse().name() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " FROM:" );
sb.append( getSourceID() );
sb.append( " WACN:" );
sb.append( getSourceWACN() );
sb.append( " SYS:" );
sb.append( getSourceSystemID() );
sb.append( " GROUP:" );
sb.append( getGroupID() );
sb.append( " WACN:" );
sb.append( getGroupWACN() );
sb.append( " SYS:" );
sb.append( getGroupSystemID() );
sb.append( " ANNOUNCEMENT GROUP:" );
sb.append( getAnnouncementGroupID() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceWACN()
{
return mMessage.getHex( SOURCE_WACN, 5 );
}
public String getSourceSystemID()
{
return mMessage.getHex( SOURCE_SYSTEM_ID, 3 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
public String getGroupWACN()
{
return mMessage.getHex( GROUP_WACN, 5 );
}
public String getGroupSystemID()
{
return mMessage.getHex( GROUP_SYSTEM_ID, 3 );
}
public String getGroupID()
{
return mMessage.getHex( GROUP_ID, 4 );
}
public String getAnnouncementGroupID()
{
return mMessage.getHex( GROUP_ID, 4 );
}
public Response getResponse()
{
return Response.fromValue(
mMessage.getInt( GROUP_AFFILIATION_RESPONSE_VALUE ) );
}
}

View File

@@ -0,0 +1,91 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class MessageUpdateExtended extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135,136,137,
138,139,140,141,142,143,160,161,162,163 };
public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170,171,172,
173,174,175 };
public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184,
185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 };
public static final int[] SHORT_DATA_MESSAGE = { 200,201,202,203,204,205,
206,207,208,209,210,211,212,213,214,215 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
public MessageUpdateExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.CALL_ALERT.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " MSG:" );
sb.append( getShortDataMessage() );
sb.append( " FROM:" );
sb.append( getSourceID() );
sb.append( " WACN:" );
sb.append( getSourceWACN() );
sb.append( " SYS:" );
sb.append( getSourceSystemID() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceWACN()
{
return mMessage.getHex( SOURCE_WACN, 5 );
}
public String getSourceSystemID()
{
return mMessage.getHex( SOURCE_SYSTEM_ID, 3 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
public String getShortDataMessage()
{
return mMessage.getHex( SHORT_DATA_MESSAGE, 4 );
}
}

View File

@@ -0,0 +1,160 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import module.decode.p25.reference.P25NetworkCallsign;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class NetworkStatusBroadcastExtended extends PDUMessage
implements IdentifierReceiver
{
public static final int[] LRA = { 88,89,90,91,92,93,94,95 };
public static final int[] SYSTEM_ID = { 100,101,102,103,104,105,106,107,108,
109,110,111 };
public static final int[] WACN = { 160,161,162,163,164,165,166,167,168,169,
170,171,172,173,174,175,176,177,178,179 };
public static final int[] TRANSMIT_IDENTIFIER = { 184,185,186,187 };
public static final int[] TRANSMIT_CHANNEL = { 188,189,190,191,192,193,194,
195,196,197,198,199 };
public static final int[] RECEIVE_IDENTIFIER = { 200,201,202,203 };
public static final int[] RECEIVE_CHANNEL = { 204,205,206,207,208,209,210,
211,212,213,214,215 };
public static final int[] SYSTEM_SERVICE_CLASS = { 216,217,218,219,220,221,
222,223 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public NetworkStatusBroadcastExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.NETWORK_STATUS_BROADCAST.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " WACN:" + getWACN() );
sb.append( " SYS:" + getSystemID() );
sb.append( " CTRL CHAN:" + getReceiveIdentifier() + "-" + getReceiveChannel() );
sb.append( " DN:" + getDownlinkFrequency() );
sb.append( " " + getTransmitIdentifier() + "-" + getTransmitChannel() );
sb.append( " UP:" + getUplinkFrequency() );
sb.append( " SYS SVC CLASS:" +
SystemService.toString( getSystemServiceClass() ) );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getWACN()
{
return mMessage.getHex( WACN, 5 );
}
public String getNetworkCallsign()
{
return P25NetworkCallsign.getCallsign( mMessage.getInt( WACN ),
mMessage.getInt( SYSTEM_ID ) );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannel()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannel()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannel() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannel() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitIdentifier();
idens[ 1 ] = getReceiveIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,113 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Encryption;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class ProtectionParameterBroadcast extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] ENCRYPTION_TYPE = { 128,129,130,131,132,133,134,
135 };
public static final int[] ALGORITHM_ID = { 136,137,138,139,140,141,142,143 };
public static final int[] KEY_ID = { 160,161,162,163,164,165,166,167,168,
169,170,171,172,173,174,175 };
public static final int[] INBOUND_INITIALIZATION_VECTOR_A = { 176,177,178,
179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,
197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,
215 };
public static final int[] INBOUND_INITIALIZATION_VECTOR_B = { 216,217,218,
219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,
237,238,239,240,241,242,243,244,245,246,247 };
public static final int[] OUTBOUND_INITIALIZATION_VECTOR_A = { 248,249,250,
251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,
269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,
287 };
public static final int[] OUTBOUND_INITIALIZATION_VECTOR_B = { 288,289,290,
291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,
309,310,311,312,313,314,315,316,317,318,319 };
public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325,
326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,
343,344,345,346,347,348,349,350,351 };
public ProtectionParameterBroadcast( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.PROTECTION_PARAMETER_BROADCAST.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " ENCRYPTION:" );
sb.append( getEncryptionType().name() );
sb.append( " ALGORITHM ID:" );
sb.append( getAlgorithmID() );
sb.append( " KEY ID:" );
sb.append( getKeyID() );
sb.append( " INBOUND IV:" );
sb.append( getInboundInitializationVector() );
sb.append( " OUTBOUND IV:" );
sb.append( getOutboundInitializationVector() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public Encryption getEncryptionType()
{
return Encryption.fromValue( mMessage.getInt( ENCRYPTION_TYPE ) );
}
public String getAlgorithmID()
{
return mMessage.getHex( ALGORITHM_ID, 2 );
}
public String getKeyID()
{
return mMessage.getHex( KEY_ID, 4 );
}
public String getInboundInitializationVector()
{
return mMessage.getHex( INBOUND_INITIALIZATION_VECTOR_A, 10 ) +
mMessage.getHex( INBOUND_INITIALIZATION_VECTOR_B, 8 );
}
public String getOutboundInitializationVector()
{
return mMessage.getHex( OUTBOUND_INITIALIZATION_VECTOR_A, 10 ) +
mMessage.getHex( OUTBOUND_INITIALIZATION_VECTOR_B, 8 );
}
}

View File

@@ -0,0 +1,164 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class RFSSStatusBroadcastExtended extends PDUMessage
implements IdentifierReceiver
{
public static final int[] LRA = { 88,89,90,91,92,93,94,95 };
public static final int ACTIVE_NETWORK_CONNECTION_INDICATOR = 99;
public static final int[] SYSTEM_ID = { 100,101,102,103,104,105,106,107,108,
109,110,111 };
public static final int[] RFSS_ID = { 160,161,162,163,164,165,166,167 };
public static final int[] SITE_ID = { 168,169,170,171,172,173,174,175 };
public static final int[] TRANSMIT_IDENTIFIER = { 184,185,186,187 };
public static final int[] TRANSMIT_CHANNEL = { 188,189,190,191,192,193,194,
195,196,197,198,199 };
public static final int[] RECEIVE_IDENTIFIER = { 200,201,202,203 };
public static final int[] RECEIVE_CHANNEL = { 204,205,206,207,208,209,210,
211,212,213,214,215 };
public static final int[] SYSTEM_SERVICE_CLASS = { 216,217,218,219,220,221,
222,223 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public RFSSStatusBroadcastExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.NETWORK_STATUS_BROADCAST.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SYS:" + getSystemID() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " CTRL CHAN:" + getReceiveIdentifier() + "-" + getReceiveChannel() );
sb.append( " DN:" + getDownlinkFrequency() );
sb.append( " " + getTransmitIdentifier() + "-" + getTransmitChannel() );
sb.append( " UP:" + getUplinkFrequency() );
sb.append( " SYS SVC CLASS:" +
SystemService.toString( getSystemServiceClass() ) );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public boolean hasActiveNetworkConnection()
{
return mMessage.get( ACTIVE_NETWORK_CONNECTION_INDICATOR );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannel()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannel()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannel() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannel() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitIdentifier();
idens[ 1 ] = getReceiveIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,269 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.P25MessageFramer;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class RoamingAddressUpdateExtended extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int LAST_MESSAGE_INDICATOR = 128;
public static final int[] MESSAGE_SEQUENCE_NUMBER = { 132,133,134,135 };
public static final int[] WACN_A = { 136,137,138,139,140,141,142,143,160,
161,162,163,164,165,166,167,168,169,170,171 };
public static final int[] SYSTEM_ID_A = { 172,173,174,175,176,177,178,179,
180,181,182,183 };
public static final int[] SOURCE_ID_FORMAT_1 = { 184,185,186,187,188,189,
190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207 };
public static final int[] WACN_B = { 184,185,186,187,188,189,190,191,192,
193,194,195,196,197,198,199,200,201,202,203 };
public static final int[] SYSTEM_ID_B = { 204,205,206,207,208,209,210,211,
212,213,214,215 };
public static final int[] WACN_C = { 216,217,218,219,220,221,222,223,224,
225,226,227,228,229,230,231,232,233,234,235 };
public static final int[] SYSTEM_ID_C = { 236,237,238,239,240,241,242,243,
244,245,246,247 };
public static final int[] WACN_D = { 248,249,250,251,252,253,254,255,256,
257,258,259,260,261,262,263,264,265,266,267 };
public static final int[] SYSTEM_ID_D = { 268,269,270,271,272,273,274,275,
276,277,278,279 };
public static final int[] SOURCE_ID_FORMAT_2 = { 280,281,282,283,284,285,
286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303 };
public static final int[] WACN_E = { 280,281,282,283,284,285,
286,287,288,289,290,291,292,293,294,295,296,297,298,299 };
public static final int[] SYSTEM_ID_E = { 300,301,302,303,304,305,306,307,
308,309,310,311 };
public static final int[] WACN_F = { 312,313,314,315,316,317,318,319,320,
321,322,323,324,325,326,327,328,329,330,331 };
public static final int[] SYSTEM_ID_F = { 332,333,334,335,336,337,338,339,
340,341,342,343 };
public static final int[] WACN_G = { 344,345,346,347,348,349,350,351,352,
353,354,355,356,357,358,359,360,361,362,363 };
public static final int[] SYSTEM_ID_G = { 364,365,366,367,368,369,370,371,
372,373,374,375 };
public static final int[] SOURCE_ID_FORMAT_3 = { 376,377,378,379,380,381,
382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399 };
public static final int[] MULTIPLE_BLOCK_CRC_FORMAT_1 = { 224,225,226,227,
228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,
246,247,248,249,250,251,252,253,254,255 };
public static final int[] MULTIPLE_BLOCK_CRC_FORMAT_2 = { 320,321,322,323,
324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,
342,343,344,345,346,347,348,349,350,351 };
public static final int[] MULTIPLE_BLOCK_CRC_FORMAT_3 = { 416,417,418,419,
420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,
438,439,440,441,442,443,444,445,446,447 };
private enum Format{ FORMAT_1, FORMAT_2, FORMAT_3 };
private Format mFormat;
public RoamingAddressUpdateExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
if( mMessage.size() == P25MessageFramer.PDU2_BEGIN )
{
mFormat = Format.FORMAT_1;
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
else if( mMessage.size() == P25MessageFramer.PDU3_BEGIN )
{
mFormat = Format.FORMAT_2;
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU2( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
else if( mMessage.size() == P25MessageFramer.PDU3_DECODED_END )
{
mFormat = Format.FORMAT_3;
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU3( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
else
{
mFormat = Format.FORMAT_1;
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
}
@Override
public String getEventType()
{
return Opcode.ROAMING_ADDRESS_UPDATE.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " ROAMING ADDRESS STACK FROM:" );
sb.append( getSourceID() );
if( isLastBlock() )
{
sb.append( " LAST BLOCK" );
}
sb.append( "MSG SEQ:" );
sb.append( getMessageSequenceNumber() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " WACN A:" + getWACNA() );
sb.append( " SYS A:" + getSystemIDA() );
if( isFormat2() )
{
sb.append( " WACN B:" + getWACNB() );
sb.append( " SYS B:" + getSystemIDB() );
sb.append( " WACN C:" + getWACNC() );
sb.append( " SYS C:" + getSystemIDC() );
sb.append( " WACN D:" + getWACND() );
sb.append( " SYS D:" + getSystemIDD() );
}
if( isFormat3() )
{
sb.append( " WACN E:" + getWACNE() );
sb.append( " SYS E:" + getSystemIDE() );
sb.append( " WACN F:" + getWACNF() );
sb.append( " SYS F:" + getSystemIDF() );
sb.append( " WACN G:" + getWACNG() );
sb.append( " SYS G:" + getSystemIDG() );
}
return sb.toString();
}
public boolean isLastBlock()
{
return mMessage.get( LAST_MESSAGE_INDICATOR );
}
public int getMessageSequenceNumber()
{
return mMessage.getInt( MESSAGE_SEQUENCE_NUMBER );
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getWACNA()
{
return mMessage.getHex( WACN_A, 5 );
}
public String getSystemIDA()
{
return mMessage.getHex( SYSTEM_ID_A, 3 );
}
public String getWACNB()
{
return isFormat2() ? mMessage.getHex( WACN_B, 5 ) : null;
}
public String getSystemIDB()
{
return isFormat2() ? mMessage.getHex( SYSTEM_ID_B, 3 ) : null;
}
public String getWACNC()
{
return isFormat2() ? mMessage.getHex( WACN_C, 5 ) : null;
}
public String getSystemIDC()
{
return isFormat2() ? mMessage.getHex( SYSTEM_ID_C, 3 ) : null;
}
public String getWACND()
{
return isFormat2() ? mMessage.getHex( WACN_D, 5 ) : null;
}
public String getSystemIDD()
{
return isFormat2() ? mMessage.getHex( SYSTEM_ID_D, 3 ) : null;
}
public String getWACNE()
{
return isFormat3() ? mMessage.getHex( WACN_E, 5 ) : null;
}
public String getSystemIDE()
{
return isFormat3() ? mMessage.getHex( SYSTEM_ID_E, 3 ) : null;
}
public String getWACNF()
{
return isFormat3() ? mMessage.getHex( WACN_F, 5 ) : null;
}
public String getSystemIDF()
{
return isFormat3() ? mMessage.getHex( SYSTEM_ID_F, 3 ) : null;
}
public String getWACNG()
{
return isFormat3() ? mMessage.getHex( WACN_G, 5 ) : null;
}
public String getSystemIDG()
{
return isFormat3() ? mMessage.getHex( SYSTEM_ID_G, 3 ) : null;
}
public String getSourceID()
{
switch( mFormat )
{
case FORMAT_1:
return mMessage.getHex( SOURCE_ID_FORMAT_1, 6 );
case FORMAT_2:
return mMessage.getHex( SOURCE_ID_FORMAT_2, 6 );
case FORMAT_3:
return mMessage.getHex( SOURCE_ID_FORMAT_2, 6 );
}
return null;
}
public boolean isFormat2()
{
return mFormat != Format.FORMAT_3;
}
public boolean isFormat3()
{
return mFormat == Format.FORMAT_3;
}
}

View File

@@ -0,0 +1,81 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class StatusQueryExtended extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135,136,137,
138,139,140,141,142,143,160,161,162,163 };
public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170,171,172,
173,174,175 };
public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184,
185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
public StatusQueryExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.STATUS_QUERY.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " FROM:" );
sb.append( getSourceID() );
sb.append( " WACN:" );
sb.append( getSourceWACN() );
sb.append( " SYS:" );
sb.append( getSourceSystemID() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceWACN()
{
return mMessage.getHex( SOURCE_WACN, 5 );
}
public String getSourceSystemID()
{
return mMessage.getHex( SOURCE_SYSTEM_ID, 3 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
}

View File

@@ -0,0 +1,97 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class StatusUpdateExtended extends PDUMessage
{
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135,136,137,
138,139,140,141,142,143,160,161,162,163 };
public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170,171,172,
173,174,175 };
public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184,
185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 };
public static final int[] UNIT_STATUS = { 200,201,202,203,204,205,206,207 };
public static final int[] USER_STATUS = { 208,209,210,211,212,213,214,215 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
public StatusUpdateExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.STATUS_UPDATE.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " FROM:" );
sb.append( getSourceID() );
sb.append( " WACN:" );
sb.append( getSourceWACN() );
sb.append( " SYS:" );
sb.append( getSourceSystemID() );
sb.append( " STATUS USER:" + getUserStatus() );
sb.append( " UNIT:" + getUnitStatus() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceWACN()
{
return mMessage.getHex( SOURCE_WACN, 5 );
}
public String getSourceSystemID()
{
return mMessage.getHex( SOURCE_SYSTEM_ID, 3 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
public String getUserStatus()
{
return mMessage.getHex( USER_STATUS, 2 );
}
public String getUnitStatus()
{
return mMessage.getHex( UNIT_STATUS, 2 );
}
}

View File

@@ -0,0 +1,99 @@
package module.decode.p25.message.pdu.osp.control;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import module.decode.p25.reference.Response;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class UnitRegistrationResponseExtended extends PDUMessage
{
public static final int[] ASSIGNED_SOURCE_ADDRESS = { 88,89,90,91,92,
93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] WACN = { 128,129,130,131,132,133,134,135,
136,137,138,139,140,141,142,143,160,161,162,163 };
public static final int[] SYSTEM_ID = { 164,165,166,167,168,169,170,
171,172,173,174,175 };
public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184,
185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 };
public static final int[] SOURCE_ADDRESS = { 200,201,202,203,204,205,206,
207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223 };
public static final int[] UNIT_REGISTRATION_RESPONSE_VALUE = { 230,231 };
public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325,
326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,
343,344,345,346,347,348,349,350,351 };
public UnitRegistrationResponseExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.UNIT_REGISTRATION_RESPONSE.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( "REGISTRATION:" );
sb.append( getResponse().name() );
sb.append( " SRC ID:" );
sb.append( getSourceID() );
sb.append( " ADDR:" );
sb.append( getSourceAddress() );
sb.append( " WACN:" );
sb.append( getWACN() );
sb.append( " SYS:" );
sb.append( getSystemID() );
return sb.toString();
}
public String getAssignedSourceAddress()
{
return mMessage.getHex( ASSIGNED_SOURCE_ADDRESS, 6 );
}
public String getWACN()
{
return mMessage.getHex( WACN, 5 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
public Response getResponse()
{
return Response.fromValue(
mMessage.getInt( UNIT_REGISTRATION_RESPONSE_VALUE ) );
}
}

View File

@@ -0,0 +1,179 @@
package module.decode.p25.message.pdu.osp.data;
import java.util.Date;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class GroupDataChannelGrantExtended extends PDUMessage
implements IdentifierReceiver
{
/* Service Options */
public static final int EMERGENCY_FLAG = 128;
public static final int ENCRYPTED_CHANNEL_FLAG = 129;
public static final int DUPLEX_MODE = 130;
public static final int SESSION_MODE = 131;
public static final int[] SOURCE_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SERVICE_OPTIONS = { 128,129,130,131,132,133,134,
135 };
public static final int[] TRANSMIT_IDENTIFIER = { 160,161,162,163 };
public static final int[] TRANSMIT_NUMBER = { 164,165,166,167,168,169,170,
171,172,173,174,175 };
public static final int[] RECEIVE_IDENTIFIER = { 176,177,178,179 };
public static final int[] RECEIVE_NUMBER = { 180,181,182,183,184,185,186,
187,188,189,190,191 };
public static final int[] GROUP_ADDRESS = { 192,193,194,195,196,197,198,199,
200,201,202,203,204,205,206,207 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public GroupDataChannelGrantExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
sb.append( " FROM:" );
sb.append( getSourceAddress() );
sb.append( " TO:" );
sb.append( getGroupAddress() );
sb.append( " CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber() );
sb.append( " " + getDownlinkFrequency() );
sb.append( " CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber() );
sb.append( " " + getUplinkFrequency() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
public String getGroupAddress()
{
return mMessage.getHex( GROUP_ADDRESS, 4 );
}
public int getTransmitChannelIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_NUMBER );
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_NUMBER );
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannelNumber() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitChannelIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveChannelIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitChannelIdentifier();
idens[ 1 ] = getReceiveChannelIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,25 @@
package module.decode.p25.message.pdu.osp.data;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.UnitToUnitChannelGrantExtended;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
public class IndividualDataChannelGrantExtended
extends UnitToUnitChannelGrantExtended
implements IdentifierReceiver
{
public IndividualDataChannelGrantExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
}
@Override
public String getEventType()
{
return Opcode.INDIVIDUAL_DATA_CHANNEL_GRANT.getDescription();
}
}

View File

@@ -0,0 +1,177 @@
package module.decode.p25.message.pdu.osp.voice;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class GroupVoiceChannelGrantExplicit extends PDUMessage
implements IdentifierReceiver
{
/* Service Options */
public static final int EMERGENCY_FLAG = 128;
public static final int ENCRYPTED_CHANNEL_FLAG = 129;
public static final int DUPLEX_MODE = 130;
public static final int SESSION_MODE = 131;
public static final int[] SOURCE_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SERVICE_OPTIONS = { 128,129,130,131,132,133,134,
135 };
public static final int[] TRANSMIT_IDENTIFIER = { 176,177,178,179 };
public static final int[] TRANSMIT_NUMBER = { 180,181,182,183,184,185,186,
187,188,189,190,191 };
public static final int[] RECEIVE_IDENTIFIER = { 192,193,194,195 };
public static final int[] RECEIVE_NUMBER = { 196,197,198,199,200,201,202,
203,204,205,206,207 };
public static final int[] GROUP_ADDRESS = { 208,209,210,211,212,213,214,215,
216,217,218,219,220,221,222,223 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public GroupVoiceChannelGrantExplicit( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.GROUP_VOICE_CHANNEL_GRANT.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
sb.append( " FROM:" );
sb.append( getSourceAddress() );
sb.append( " GRP:" );
sb.append( getGroupAddress() );
sb.append( " CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber() );
sb.append( " " + getDownlinkFrequency() );
sb.append( " CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber() );
sb.append( " " + getUplinkFrequency() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
public String getGroupAddress()
{
return mMessage.getHex( GROUP_ADDRESS, 4 );
}
public int getTransmitChannelIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_NUMBER );
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_NUMBER );
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannelNumber() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitChannelIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveChannelIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitChannelIdentifier();
idens[ 1 ] = getReceiveChannelIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,184 @@
package module.decode.p25.message.pdu.osp.voice;
import java.util.Date;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class TelephoneInterconnectChannelGrantExplicit extends PDUMessage
implements IdentifierReceiver
{
/* Service Options */
public static final int EMERGENCY_FLAG = 128;
public static final int ENCRYPTED_CHANNEL_FLAG = 129;
public static final int DUPLEX_MODE = 130;
public static final int SESSION_MODE = 131;
public static final int[] ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SERVICE_OPTIONS = { 128,129,130,131,132,133,134,
135 };
public static final int[] TRANSMIT_IDENTIFIER = { 160,161,162,163 };
public static final int[] TRANSMIT_NUMBER = { 164,165,166,167,168,169,170,
171,172,173,174,175 };
public static final int[] RECEIVE_IDENTIFIER = { 176,177,178,179 };
public static final int[] RECEIVE_NUMBER = { 180,181,182,183,184,185,186,
187,188,189,190,191 };
public static final int[] CALL_TIMER = { 192,193,194,195,196,197,198,199,
200,201,202,203,204,205,206,207 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public TelephoneInterconnectChannelGrantExplicit( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
sb.append( " ADDR:" );
sb.append( getAddress() );
sb.append( " CALL TIMER:" );
sb.append( mTimeDurationFormat.format( new Date( getCallTimer() ) ) );
sb.append( " CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber() );
sb.append( " " + getDownlinkFrequency() );
sb.append( " CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber() );
sb.append( " " + getUplinkFrequency() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getAddress()
{
return mMessage.getHex( ADDRESS, 6 );
}
/*
* Call timer in milliseconds
*/
public long getCallTimer()
{
int timer = mMessage.getInt( CALL_TIMER );
return timer / 100;
}
public int getTransmitChannelIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_NUMBER );
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_NUMBER );
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannelNumber() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitChannelIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveChannelIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitChannelIdentifier();
idens[ 1 ] = getReceiveChannelIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,22 @@
package module.decode.p25.message.pdu.osp.voice;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
public class TelephoneInterconnectChannelGrantUpdateExplicit
extends TelephoneInterconnectChannelGrantExplicit
{
public TelephoneInterconnectChannelGrantUpdateExplicit( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
}
@Override
public String getEventType()
{
return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE.getDescription();
}
}

View File

@@ -0,0 +1,111 @@
package module.decode.p25.message.pdu.osp.voice;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class UnitToUnitAnswerRequestExplicit extends PDUMessage
{
/* Service Options */
public static final int EMERGENCY_FLAG = 128;
public static final int ENCRYPTED_CHANNEL_FLAG = 129;
public static final int DUPLEX_MODE = 130;
public static final int SESSION_MODE = 131;
public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] WACN = { 168,169,170,171,172,173,174,175,176,177,
178,179,180,181,182,183,184,185,186,187 };
public static final int[] SYSTEM_ID = { 188,189,190,191,192,193,194,195,196,
197,198,199 };
public static final int[] SOURCE_ID = { 200,201,202,203,204,205,206,207,208,
209,210,211,212,213,214,215,216,217,218,219,220,221,222,223 };
public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229,
230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255 };
public UnitToUnitAnswerRequestExplicit( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU1( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.UNIT_TO_UNIT_ANSWER_REQUEST.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
sb.append( " FROM:" );
sb.append( getSourceID() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " WACN:" + getWACN() );
sb.append( " SYS:" + getSystemID() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncryptedChannel()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getWACN()
{
return mMessage.getHex( WACN, 5 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,25 @@
package module.decode.p25.message.pdu.osp.voice;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.UnitToUnitChannelGrantExtended;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
public class UnitToUnitVoiceChannelGrantExtended
extends UnitToUnitChannelGrantExtended
implements IdentifierReceiver
{
public UnitToUnitVoiceChannelGrantExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
}
@Override
public String getEventType()
{
return Opcode.UNIT_TO_UNIT_VOICE_CHANNEL_GRANT.getDescription();
}
}

View File

@@ -0,0 +1,207 @@
package module.decode.p25.message.pdu.osp.voice;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.Opcode;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRCP25;
public class UnitToUnitVoiceChannelGrantUpdateExtended extends PDUMessage
implements IdentifierReceiver
{
/* Service Options */
public static final int EMERGENCY_FLAG = 128;
public static final int ENCRYPTED_CHANNEL_FLAG = 129;
public static final int DUPLEX_MODE = 130;
public static final int SESSION_MODE = 131;
public static final int[] SOURCE_ADDRESS = { 88,89,90,91,92,93,94,95,96,97,
98,99,100,101,102,103,104,105,106,107,108,109,110,111 };
public static final int[] SERVICE_OPTIONS = { 128,129,130,131,132,133,134,
135 };
public static final int[] SOURCE_WACN = { 160,161,162,163,164,165,166,167,
168,169,170,171,172,173,174,175,176,177,178,179 };
public static final int[] SOURCE_SYSTEM_ID = { 180,181,182,183,184,185,186,
187,188,189,190,191 };
public static final int[] SOURCE_ID = { 192,193,194,195,196,197,198,199,200,
201,202,203,204,205,206,207,208,209,210,211,212,213,214,215 };
public static final int[] TARGET_ADDRESS = { 216,217,218,219,220,221,222,
223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239 };
public static final int[] TRANSMIT_IDENTIFIER = { 240,241,242,243 };
public static final int[] TRANSMIT_NUMBER = { 244,245,246,247,248,249,250,
251,252,253,254,255 };
public static final int[] RECEIVE_IDENTIFIER = { 256,257,258,259 };
public static final int[] RECEIVE_NUMBER = { 260,261,262,263,264,265,266,
267,268,269,270,271 };
public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325,
326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,
343,344,345,346,347,348,349,350,351 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public UnitToUnitVoiceChannelGrantUpdateExtended( BinaryMessage message,
DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
/* Header block is already error detected/corrected - perform error
* detection correction on the intermediate and final data blocks */
mMessage = CRCP25.correctPDU2( mMessage );
mCRC[ 1 ] = mMessage.getCRC();
}
@Override
public String getEventType()
{
return Opcode.UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
sb.append( " FROM ADDR:" );
sb.append( getSourceAddress() );
sb.append( " ID:" );
sb.append( getSourceID() );
sb.append( " TO:" );
sb.append( getTargetAddress() );
sb.append( " WACN:" );
sb.append( getSourceWACN() );
sb.append( " SYS:" );
sb.append( getSourceSystemID() );
sb.append( " CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber() );
sb.append( " " + getDownlinkFrequency() );
sb.append( " CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber() );
sb.append( " " + getUplinkFrequency() );
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
public String getSourceID()
{
return mMessage.getHex( SOURCE_ID, 6 );
}
public String getSourceWACN()
{
return mMessage.getHex( SOURCE_WACN, 5 );
}
public String getSourceSystemID()
{
return mMessage.getHex( SOURCE_SYSTEM_ID, 3 );
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public int getTransmitChannelIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_NUMBER );
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_NUMBER );
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannelNumber() );
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannelNumber() );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitChannelIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveChannelIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
}
public int[] getIdentifiers()
{
int[] idens = new int[ 2 ];
idens[ 0 ] = getTransmitChannelIdentifier();
idens[ 1 ] = getReceiveChannelIdentifier();
return idens;
}
}

View File

@@ -0,0 +1,21 @@
package module.decode.p25.message.tdu;
import module.decode.p25.message.P25Message;
import module.decode.p25.reference.DataUnitID;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
public class TDUMessage extends P25Message
{
public TDUMessage( BinaryMessage message, DataUnitID duid,
AliasList aliasList )
{
super( message, duid, aliasList );
/* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */
mCRC = new CRC[ 1 ];
mCRC[ 0 ] = CRC.PASSED;
}
}

View File

@@ -0,0 +1,127 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdjacentSiteStatusBroadcast extends TDULinkControlMessage
implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( AdjacentSiteStatusBroadcast.class );
public static final int[] LRA = { 72,73,74,75,88,89,90,91 };
public static final int[] SYSTEM_ID = { 96,97,98,99,112,113,114,115,
116,117,118,119 };
public static final int[] RFSS_ID = { 120,121,122,123,136,137,138,139 };
public static final int[] SITE_ID = { 140,141,142,143,144,145,146,147 };
public static final int[] IDENTIFIER = { 160,161,162,163 };
public static final int[] CHANNEL = { 164,165,166,167,168,169,170,
171,184,185,186,187 };
public static final int[] SYSTEM_SERVICE_CLASS = { 188,189,190,191,192,193,
194,195 };
private IBandIdentifier mIdentifierUpdate;
public AdjacentSiteStatusBroadcast( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SYS:" + getSystemID() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " CHAN:" + getChannel() );
sb.append( " " + Service.getServices( getSystemServiceClass() ).toString() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getChannelNumber()
{
return mMessage.getInt( CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
mIdentifierUpdate = message;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mIdentifierUpdate, getChannelNumber() );
}
public long getUplinkFrequencyB()
{
return calculateUplink( mIdentifierUpdate, getChannelNumber() );
}
}

View File

@@ -0,0 +1,98 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdjacentSiteStatusBroadcastExplicit extends TDULinkControlMessage
{
private final static Logger mLog =
LoggerFactory.getLogger( AdjacentSiteStatusBroadcastExplicit.class );
public static final int[] LRA = { 72,73,74,75,88,89,90,91 };
public static final int[] TRANSMIT_IDENTIFIER = { 92,93,94,95 };
public static final int[] TRANSMIT_CHANNEL = { 96,97,98,99,112,113,114,115,
116,117,118,119 };
public static final int[] RFSS_ID = { 120,121,122,123,136,137,138,139 };
public static final int[] SITE_ID = { 140,141,142,143,144,145,146,147 };
public static final int[] RECEIVE_IDENTIFIER = { 160,161,162,163 };
public static final int[] RECEIVE_CHANNEL = { 164,165,166,167,168,169,170,
171,184,185,186,187 };
public AdjacentSiteStatusBroadcastExplicit( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " DNLINK:" + getTransmitChannelNumber() );
sb.append( " UPLINK:" + getReceiveChannelNumber() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannel()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getTransmitChannelNumber()
{
return getTransmitIdentifier() + "-" + getTransmitChannel();
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannel()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getReceiveChannelNumber()
{
return getReceiveIdentifier() + "-" + getReceiveChannel();
}
}

View File

@@ -0,0 +1,49 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CallAlert extends TDULinkControlMessage
{
private final static Logger mLog =
LoggerFactory.getLogger( CallAlert.class );
public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118,
119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 };
public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166,
167,168,169,170,171,184,185,186,187,188,189,190,190,192,193,194,195 };
public CallAlert( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CALL_ALERT.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " SRC ADDR: " + getSourceAddress() );
sb.append( " TGT ADDR: " + getTargetAddress() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,42 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CallTermination extends TDULinkControlMessage
{
private final static Logger mLog = LoggerFactory.getLogger( CallTermination.class );
public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166,
167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 };
public CallTermination( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " BY:" + getSourceAddress() );
return sb.toString();
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

View File

@@ -0,0 +1,88 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChannelIdentifierUpdate extends TDULinkControlMessage
implements IBandIdentifier
{
private final static Logger mLog =
LoggerFactory.getLogger( ChannelIdentifierUpdate.class );
public static final int[] IDENTIFIER = { 72,73,74,75 };
public static final int[] BANDWIDTH = { 88,89,90,91,92,93,94,95,96 };
public static final int[] TRANSMIT_OFFSET = { 97,98,99,112,113,114,115,116,
117 };
public static final int[] CHANNEL_SPACING = { 118,119,120,121,122,123,136,
137,138,139 };
public static final int[] BASE_FREQUENCY = { 140,141,142,143,144,145,146,
147,160,161,162,163,164,165,166,167,168,169,170,171,184,185,186,
187,188,189,190,191,192,193,194,195 };
public ChannelIdentifierUpdate( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " IDEN:" + getIdentifier() );
sb.append( " BASE:" + getBaseFrequency() );
sb.append( " BW:" + getBandwidth() );
sb.append( " SPACING:" + getChannelSpacing() );
sb.append( " OFFSET:" + getTransmitOffset() );
return sb.toString();
}
@Override
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
/**
* Channel bandwidth in hertz
*/
@Override
public int getBandwidth()
{
return mMessage.getInt( BANDWIDTH ) * 125;
}
@Override
public long getChannelSpacing()
{
return mMessage.getLong( CHANNEL_SPACING ) * 125l;
}
@Override
public long getBaseFrequency()
{
return mMessage.getLong( BASE_FREQUENCY ) * 5l;
}
@Override
public long getTransmitOffset()
{
return -1 * mMessage.getLong( TRANSMIT_OFFSET ) * 250000l;
}
}

View File

@@ -0,0 +1,88 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChannelIdentifierUpdateExplicit extends TDULinkControlMessage
implements IBandIdentifier
{
private final static Logger mLog =
LoggerFactory.getLogger( ChannelIdentifierUpdateExplicit.class );
public static final int[] IDENTIFIER = { 72,73,74,75 };
public static final int[] BANDWIDTH = { 88,89,90,91 };
public static final int[] TRANSMIT_OFFSET = { 92,93,94,95,96,97,98,99,112,
113,114,115,116,117 };
public static final int[] CHANNEL_SPACING = { 118,119,120,121,122,
123,136,137,138,139 };
public static final int[] BASE_FREQUENCY = { 140,141,142,143,144,145,146,
147,160,161,162,163,164,165,166,167,168,169,170,171,184,185,186,
187,188,189,190,191,192,193,194,195 };
public ChannelIdentifierUpdateExplicit( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " IDEN:" + getIdentifier() );
sb.append( " BASE:" + getBaseFrequency() );
sb.append( " BW:" + getBandwidth() );
sb.append( " SPACING:" + getChannelSpacing() );
sb.append( " OFFSET:" + getTransmitOffset() );
return sb.toString();
}
@Override
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
/**
* Channel bandwidth in hertz
*/
@Override
public int getBandwidth()
{
return mMessage.getInt( BANDWIDTH ) * 125;
}
@Override
public long getChannelSpacing()
{
return mMessage.getLong( CHANNEL_SPACING ) * 125l;
}
@Override
public long getBaseFrequency()
{
return mMessage.getLong( BASE_FREQUENCY ) * 5l;
}
@Override
public long getTransmitOffset()
{
return -1 * mMessage.getLong( TRANSMIT_OFFSET ) * 250000l;
}
}

View File

@@ -0,0 +1,86 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.reference.ExtendedFunction;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExtendedFunctionCommand extends TDULinkControlMessage
{
private final static Logger mLog =
LoggerFactory.getLogger( ExtendedFunctionCommand.class );
public static final int[] EXTENDED_FUNCTION = { 72,73,74,75,88,89,90,91,92,
93,94,95,96,97,98,99 };
public static final int[] ARGUMENT = { 112,113,114,115,116,117,118,119,120,
121,122,123,136,137,138,139,140,141,142,143,144,145,145,147 };
public static final int[] TARGET_ADDRESS = { 160,161,162,163,164,165,166,
167,168,169,170,171,184,185,186,187,188,189,190,190,192,193,194,195 };
public ExtendedFunctionCommand( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.EXTENDED_FUNCTION_COMMAND.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " EXTENDED FUNCTION:" + getExtendedFunction().getLabel() );
sb.append( " ARGUMENT: " + getTargetAddress() );
sb.append( " TGT ADDR: " + getTargetAddress() );
return sb.toString();
}
public ExtendedFunction getExtendedFunction()
{
return ExtendedFunction.fromValue( mMessage.getInt( EXTENDED_FUNCTION ) );
}
public String getArgument()
{
return mMessage.getHex( ARGUMENT, 6 );
}
public String getSourceAddress()
{
switch( getExtendedFunction() )
{
case RADIO_CHECK:
case RADIO_CHECK_ACK:
case RADIO_DETACH:
case RADIO_DETACH_ACK:
case RADIO_INHIBIT:
case RADIO_INHIBIT_ACK:
case RADIO_UNINHIBIT:
case RADIO_UNINHIBIT_ACK:
return getArgument();
default:
break;
}
return null;
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
@Override
public String getToID()
{
return getTargetAddress();
}
}

View File

@@ -0,0 +1,51 @@
package module.decode.p25.message.tdu.lc;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GroupAffiliationQuery extends TDULinkControlMessage
{
private final static Logger mLog =
LoggerFactory.getLogger( GroupAffiliationQuery.class );
public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118,
119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 };
public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166,
167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 };
public GroupAffiliationQuery( TDULinkControlMessage source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.GROUP_AFFILIATION_QUERY.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " FROM:" + getSourceAddress() );
sb.append( " TO:" + getTargetAddress() );
return sb.toString();
}
public String getTargetAddress()
{
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
public String getSourceAddress()
{
return mMessage.getHex( SOURCE_ADDRESS, 6 );
}
}

Some files were not shown because too many files have changed in this diff Show More