mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 00:57:31 -05:00
Updates pull request from Roman Tsypuk to convert the build system from
ant to gradle. Removes linux and windows application launch scripts and produces an executable jar with all resources included.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.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 );
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.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>();
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBroadcaster.dispose();
|
||||
mBroadcaster = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 > 0.0 )
|
||||
{
|
||||
if( sample >= THRESHOLD )
|
||||
{
|
||||
dispatch( Dibit.D01_PLUS_3 );
|
||||
}
|
||||
else
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.instrument.Instrumentable;
|
||||
import io.github.dsheirer.instrument.tap.Tap;
|
||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
||||
import io.github.dsheirer.instrument.tap.stream.AdditiveFloatTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.FloatTap;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.RealBuffer;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
import io.github.dsheirer.sample.real.RealSampleProvider;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
|
||||
import io.github.dsheirer.dsp.gain.DirectGainControl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class C4FMSymbolFilter implements Listener<RealBuffer>, IFrequencyChangeListener, RealSampleProvider, Instrumentable
|
||||
{
|
||||
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<TapGroup> 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_THRESHOLD = 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 RealSampleListener mListener;
|
||||
|
||||
private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
|
||||
|
||||
private int mFrequencyAdjustmentRequested = 0;
|
||||
private int mFrequencyCorrection = 0;
|
||||
private int mFrequencyCorrectionMaximum = 3000;
|
||||
private boolean mResetFrequencyTracker = false;
|
||||
private FrequencyCorrectionProcessor mFrequencyCorrectionProcessor;
|
||||
private Listener<FrequencyChangeEvent> mFrequencyChangeListener;
|
||||
|
||||
/**
|
||||
* 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( int frequencyCorrectionMaximum )
|
||||
{
|
||||
mFrequencyCorrectionMaximum = frequencyCorrectionMaximum;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mGainController = null;
|
||||
// mFrequencyCorrectionControl = null;
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
for( float sample: buffer.getSamples() )
|
||||
{
|
||||
receive( sample );
|
||||
}
|
||||
|
||||
/* If a frequency correction was requested during the processing of this
|
||||
* buffer, we'll apply the change and it will be reflected in the next
|
||||
* arriving buffer. Reset the lock on frequency correction and reset
|
||||
* the internal frequency correction tracker */
|
||||
if( mFrequencyAdjustmentRequested != 0 )
|
||||
{
|
||||
int correction = mFrequencyCorrection + mFrequencyAdjustmentRequested;
|
||||
|
||||
if( correction > mFrequencyCorrectionMaximum )
|
||||
{
|
||||
correction = mFrequencyCorrectionMaximum;
|
||||
}
|
||||
else if( correction < -mFrequencyCorrectionMaximum )
|
||||
{
|
||||
correction = -mFrequencyCorrectionMaximum;
|
||||
}
|
||||
|
||||
broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
|
||||
|
||||
mFrequencyAdjustmentRequested = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void receive( float sample )
|
||||
{
|
||||
sample = mGainController.correct( sample );
|
||||
|
||||
if( mResetFrequencyTracker )
|
||||
{
|
||||
mCoarseFrequencyCorrection = 0.0f;
|
||||
mFineFrequencyCorrection = 0.0f;
|
||||
|
||||
broadcast( new FrequencyChangeEvent(
|
||||
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, 0 ));
|
||||
|
||||
mResetFrequencyTracker = 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 );
|
||||
|
||||
/* Queue a frequency adjustment (once per buffer) as needed */
|
||||
if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_THRESHOLD )
|
||||
{
|
||||
mFrequencyAdjustmentRequested =
|
||||
500 * ( mCoarseFrequencyCorrection > 0 ? 1 : -1 );
|
||||
}
|
||||
|
||||
if( mSymbolSpreadTap != null )
|
||||
{
|
||||
mSymbolSpreadTap.receive( mSymbolSpread );
|
||||
}
|
||||
|
||||
/* dispatch the interpolated value to the listener */
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( output );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener( RealSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener( RealSampleListener listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
TapGroup group = new TapGroup( "C4FM Symbol Filter" );
|
||||
|
||||
group.add(
|
||||
new AdditiveFloatTap( INSTRUMENT_SYMBOL_SPREAD, 0, 0.1f, -2.0f ) );
|
||||
|
||||
mAvailableTaps.add( group );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
{
|
||||
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
|
||||
{
|
||||
mSymbolSpreadTap = (FloatTap)tap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
{
|
||||
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
|
||||
{
|
||||
mSymbolSpreadTap = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a frequency change event to the registered listener
|
||||
* @param event
|
||||
*/
|
||||
public void broadcast( FrequencyChangeEvent event )
|
||||
{
|
||||
if( mFrequencyChangeListener != null )
|
||||
{
|
||||
mFrequencyChangeListener.receive( event );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener for frequency change events.
|
||||
* @param listener
|
||||
*/
|
||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
|
||||
{
|
||||
mFrequencyChangeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
if( mFrequencyCorrectionProcessor == null )
|
||||
{
|
||||
mFrequencyCorrectionProcessor = new FrequencyCorrectionProcessor();
|
||||
}
|
||||
|
||||
return mFrequencyCorrectionProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives notifications that the channel frequency correction has been
|
||||
* applied and updates internal tracking value.
|
||||
*/
|
||||
public class FrequencyCorrectionProcessor implements Listener<FrequencyChangeEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive( FrequencyChangeEvent event )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
mFrequencyCorrection = event.getValue().intValue();
|
||||
|
||||
//Reset internal frequency tracking
|
||||
mCoarseFrequencyCorrection = 0.0f;
|
||||
mFineFrequencyCorrection = 0.0f;
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mResetFrequencyTracker = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*******************************************************************************
|
||||
* 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 io.github.dsheirer.module.decode.p25;
|
||||
|
||||
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
||||
{
|
||||
private P25_LSMDecoder.Modulation mModulation = P25Decoder.Modulation.C4FM;
|
||||
|
||||
private int mCallTimeout = 1;
|
||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||
private boolean mIgnoreDataCalls = true;
|
||||
|
||||
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 = "ignore_data_calls" )
|
||||
public boolean getIgnoreDataCalls()
|
||||
{
|
||||
return mIgnoreDataCalls;
|
||||
}
|
||||
|
||||
public void setIgnoreDataCalls( boolean ignore )
|
||||
{
|
||||
mIgnoreDataCalls = ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this field is now deprecated.
|
||||
* @return
|
||||
*/
|
||||
@XmlTransient
|
||||
@Deprecated
|
||||
public int getCallTimeout()
|
||||
{
|
||||
return mCallTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the call timeout value in seconds ( 10 - 600 );
|
||||
* @param timeout
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCallTimeout( int timeout )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*******************************************************************************
|
||||
* 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 io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.event.CallEvent;
|
||||
|
||||
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 String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "P25 " );
|
||||
sb.append( getCallEventType().name() );
|
||||
sb.append( " CHAN[" );
|
||||
sb.append( getChannel() );
|
||||
sb.append( "] TO[" );
|
||||
sb.append( getToID() );
|
||||
sb.append( "] FROM[" );
|
||||
sb.append( getFromID() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.instrument.Instrumentable;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
|
||||
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
|
||||
|
||||
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
|
||||
{
|
||||
private P25MessageProcessor mMessageProcessor;
|
||||
|
||||
public P25Decoder( AliasList aliasList )
|
||||
{
|
||||
mMessageProcessor = new P25MessageProcessor( aliasList );
|
||||
mMessageProcessor.setMessageListener( this );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mMessageProcessor.dispose();
|
||||
mMessageProcessor = null;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EditorValidationException;
|
||||
import io.github.dsheirer.gui.editor.ValidatingEditor;
|
||||
import io.github.dsheirer.module.decode.AuxDecodeConfigurationEditor;
|
||||
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.source.SourceConfigurationEditor;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class P25DecoderEditor extends ValidatingEditor<Channel>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25DecoderEditor.class );
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JComboBox<P25_LSMDecoder.Modulation> mComboModulation;
|
||||
private JCheckBox mIgnoreDataCalls;
|
||||
private JLabel mTrafficChannelPoolSizeLabel;
|
||||
private JSlider mTrafficChannelPoolSize;
|
||||
|
||||
public P25DecoderEditor()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "insets 0 0 0 0,wrap 3", "[right][grow,fill][]", "" ) );
|
||||
|
||||
mComboModulation = new JComboBox<P25_LSMDecoder.Modulation>();
|
||||
mComboModulation.setModel( new DefaultComboBoxModel<P25_LSMDecoder.Modulation>(
|
||||
P25_LSMDecoder.Modulation.values() ) );
|
||||
mComboModulation.setEnabled( false );
|
||||
mComboModulation.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Modulation:" ) );
|
||||
add( mComboModulation );
|
||||
|
||||
mIgnoreDataCalls = new JCheckBox( "Ignore Data Calls" );
|
||||
mIgnoreDataCalls.setEnabled( false );
|
||||
mIgnoreDataCalls.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
add( mIgnoreDataCalls );
|
||||
|
||||
mTrafficChannelPoolSize = new JSlider( JSlider.HORIZONTAL, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MINIMUM,
|
||||
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MAXIMUM, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT );
|
||||
mTrafficChannelPoolSize.setEnabled( false );
|
||||
mTrafficChannelPoolSize.setMajorTickSpacing( 10 );
|
||||
mTrafficChannelPoolSize.setMinorTickSpacing( 5 );
|
||||
mTrafficChannelPoolSize.setPaintTicks( true );
|
||||
mTrafficChannelPoolSize.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
mTrafficChannelPoolSizeLabel.setText( "Traffic Pool: " + mTrafficChannelPoolSize.getValue() );
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
mTrafficChannelPoolSizeLabel = new JLabel( "Traffic Pool: " + mTrafficChannelPoolSize.getValue() + " " );
|
||||
|
||||
add( mTrafficChannelPoolSizeLabel );
|
||||
add( mTrafficChannelPoolSize );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( hasItem() && isModified() )
|
||||
{
|
||||
DecodeConfigP25Phase1 p25 = new DecodeConfigP25Phase1();
|
||||
|
||||
p25.setModulation( (P25Decoder.Modulation)mComboModulation.getSelectedItem() );
|
||||
p25.setIgnoreDataCalls( mIgnoreDataCalls.isSelected() );
|
||||
p25.setTrafficChannelPoolSize( mTrafficChannelPoolSize.getValue() );
|
||||
|
||||
getItem().setDecodeConfiguration( p25 );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate( Editor<Channel> editor ) throws EditorValidationException
|
||||
{
|
||||
/**
|
||||
* Enforce CQPSK modulation uses a Tuner Source for I/Q sample data
|
||||
*/
|
||||
if( editor instanceof SourceConfigurationEditor &&
|
||||
((P25Decoder.Modulation)mComboModulation.getSelectedItem()) == P25Decoder.Modulation.CQPSK )
|
||||
{
|
||||
SourceConfiguration config = ((SourceConfigurationEditor)editor).getSourceConfiguration();
|
||||
|
||||
if( config != null && !( config instanceof SourceConfigTuner ) )
|
||||
{
|
||||
throw new EditorValidationException( editor,
|
||||
"<html><body width='175'><h3>LSM Simulcast</h3>"
|
||||
+ "<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</p>" );
|
||||
}
|
||||
}
|
||||
|
||||
if( editor instanceof AuxDecodeConfigurationEditor)
|
||||
{
|
||||
AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration();
|
||||
|
||||
if( config != null && !config.getAuxDecoders().isEmpty() )
|
||||
{
|
||||
throw new EditorValidationException( editor,
|
||||
"<html><body width='175'><h3>Auxilary Decoders</h3>"
|
||||
+ "<p>The auxiliary decoders work with analog audio and are not "
|
||||
+ "compatible with the P25 Decoder. Please uncheck any auxiliary "
|
||||
+ "decoders that you have selected for this channel</p>" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setControlsEnabled( boolean enabled )
|
||||
{
|
||||
if( mComboModulation.isEnabled() != enabled )
|
||||
{
|
||||
mComboModulation.setEnabled( enabled );
|
||||
}
|
||||
|
||||
if( mTrafficChannelPoolSize.isEnabled() != enabled )
|
||||
{
|
||||
mTrafficChannelPoolSize.setEnabled( enabled );
|
||||
}
|
||||
|
||||
if( mIgnoreDataCalls.isEnabled() != enabled )
|
||||
{
|
||||
mIgnoreDataCalls.setEnabled( enabled );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( Channel item )
|
||||
{
|
||||
super.setItem( item );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
setControlsEnabled( true );
|
||||
|
||||
DecodeConfiguration config = getItem().getDecodeConfiguration();
|
||||
|
||||
if( config instanceof DecodeConfigP25Phase1 )
|
||||
{
|
||||
DecodeConfigP25Phase1 p25 = (DecodeConfigP25Phase1)config;
|
||||
|
||||
mComboModulation.setSelectedItem( p25.getModulation() );
|
||||
mIgnoreDataCalls.setSelected( p25.getIgnoreDataCalls() );
|
||||
mTrafficChannelPoolSize.setValue( p25.getTrafficChannelPoolSize() );
|
||||
setModified( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboModulation.setSelectedItem( P25Decoder.Modulation.C4FM );
|
||||
mIgnoreDataCalls.setSelected( false );
|
||||
mTrafficChannelPoolSize.setValue( DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT );
|
||||
setModified( true );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled( false );
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,139 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.BitSetFullException;
|
||||
import io.github.dsheirer.bits.ISyncDetectListener;
|
||||
import io.github.dsheirer.bits.MultiSyncPatternMatcher;
|
||||
import io.github.dsheirer.bits.SoftSyncDetector;
|
||||
import io.github.dsheirer.bits.SyncDetector;
|
||||
import io.github.dsheirer.dsp.psk.LSMDemodulator;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.dsp.symbol.FrameSync;
|
||||
import io.github.dsheirer.edac.BCH_63_16_11;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU2Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.lc.LDULCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessageFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.PDUConfirmedMessageFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.tdu.TDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.tdu.lc.TDULCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.tdu.lc.TDULinkControlMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessageFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.vselp.VSELP1Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.vselp.VSELP2Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
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 SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
|
||||
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD );
|
||||
|
||||
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher( 48 );
|
||||
private ArrayList<P25MessageAssembler> mAssemblers =
|
||||
new ArrayList<P25MessageAssembler>();
|
||||
|
||||
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, LSMDemodulator demodulator )
|
||||
{
|
||||
this( aliasList );
|
||||
|
||||
if( demodulator != null )
|
||||
{
|
||||
/* For CQPSK, we include 3 additional sync detectors to watch for and
|
||||
* correct +/-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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for( P25MessageAssembler a: mAssemblers )
|
||||
{
|
||||
a.dispose();
|
||||
}
|
||||
|
||||
mAssemblers.clear();
|
||||
|
||||
mListener = null;
|
||||
mAliasList = null;
|
||||
|
||||
mMatcher.dispose();
|
||||
mMatcher = null;
|
||||
|
||||
mPrimarySyncDetector.dispose();
|
||||
mPrimarySyncDetector = null;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) );
|
||||
}
|
||||
}
|
||||
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 VSELP1:
|
||||
mComplete = true;
|
||||
dispatch( new VSELP1Message( mMessage.copy(), mDUID, mAliasList ) );
|
||||
break;
|
||||
case VSELP2:
|
||||
mComplete = true;
|
||||
dispatch( new VSELP2Message( mMessage.copy(), mDUID, mAliasList ) );
|
||||
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 LSMDemodulator mDemodulator;
|
||||
private double mCorrection;
|
||||
|
||||
public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDUMessage;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.instrument.tap.Tap;
|
||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
||||
import io.github.dsheirer.instrument.tap.stream.DibitTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.FloatTap;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
|
||||
import io.github.dsheirer.sample.real.RealBuffer;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
|
||||
|
||||
/* Instrumentation Taps */
|
||||
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 List<TapGroup> mAvailableTaps;
|
||||
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, int frequencyCorrectionMaximum )
|
||||
{
|
||||
super( aliasList );
|
||||
|
||||
/* Shape gain and frequency offsets to optimize sample stream */
|
||||
mSymbolFilter = new C4FMSymbolFilter( frequencyCorrectionMaximum );
|
||||
|
||||
/* 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() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mSymbolFilter.dispose();
|
||||
mSymbolFilter = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
|
||||
mC4FMSlicer.dispose();
|
||||
mC4FMSlicer = null;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.C4FM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getFilteredRealBufferListener()
|
||||
{
|
||||
return mSymbolFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instrumentation taps for monitoring internal processing
|
||||
*/
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<>();
|
||||
|
||||
TapGroup group = new TapGroup( "P25 C4FM Decoder" );
|
||||
|
||||
group.add( new FloatTap( INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT, 0, 0.1f ) );
|
||||
group.add( new DibitTap( INSTRUMENT_C4FM_SLICER_OUTPUT, 0, 0.1f ) );
|
||||
|
||||
mAvailableTaps.add( group );
|
||||
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mAvailableTaps.addAll( mSymbolFilter.getTapGroups() );
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instrumentation tap to monitor internal processing.
|
||||
*/
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.registerTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
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:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the instrumentation tap.
|
||||
*/
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.unregisterTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
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() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.setFrequencyChangeListener( listener );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.setFrequencyChangeListener( null );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
return mSymbolFilter.getFrequencyChangeListener();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*******************************************************************************
|
||||
* 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 io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.instrument.tap.Tap;
|
||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
||||
import io.github.dsheirer.instrument.tap.stream.ComplexSampleTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.ComplexTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.DibitTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.QPSKTap;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
|
||||
import io.github.dsheirer.sample.complex.IComplexBufferListener;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.LSMDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.QPSKPolarSlicer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
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_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output";
|
||||
private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output";
|
||||
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
|
||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||
private ComplexFeedForwardGainControl mAGC =
|
||||
new ComplexFeedForwardGainControl( 32 );
|
||||
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
||||
private QPSKPolarSlicer mQPSKSlicer = new QPSKPolarSlicer();
|
||||
private P25MessageFramer mMessageFramer;
|
||||
|
||||
public P25_LSMDecoder( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
|
||||
mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass(
|
||||
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0f );
|
||||
|
||||
mBasebandFilter.setListener( mStreamConverter );
|
||||
|
||||
mStreamConverter.setListener( mAGC );
|
||||
|
||||
mAGC.setListener( mLSMDemodulator );
|
||||
|
||||
mLSMDemodulator.setSymbolListener( mQPSKSlicer );
|
||||
|
||||
mMessageFramer = new P25MessageFramer( aliasList, mLSMDemodulator );
|
||||
mQPSKSlicer.addListener( mMessageFramer );
|
||||
|
||||
mMessageFramer.setListener( getMessageProcessor() );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = null;
|
||||
|
||||
mStreamConverter.dispose();
|
||||
mStreamConverter = null;
|
||||
|
||||
mAGC.dispose();
|
||||
mAGC = null;
|
||||
|
||||
mLSMDemodulator.dispose();
|
||||
mLSMDemodulator = null;
|
||||
|
||||
mQPSKSlicer.dispose();
|
||||
mQPSKSlicer = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
return new Listener<FrequencyChangeEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return mBasebandFilter;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.CQPSK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of instrumentation taps for monitoring internal processing
|
||||
*/
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<>();
|
||||
|
||||
TapGroup group = new TapGroup( "P25 LSM Decoder" );
|
||||
|
||||
group.add( new ComplexTap( INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f ) );
|
||||
group.add( new ComplexTap( INSTRUMENT_AGC_OUTPUT, 0, 1.0f ) );
|
||||
group.add( new QPSKTap( INSTRUMENT_LSM_DEMODULATOR_OUTPUT, 0, 1.0f ) );
|
||||
group.add( new DibitTap( INSTRUMENT_QPSK_SLICER_OUTPUT, 0, 0.1f ) );
|
||||
|
||||
mAvailableTaps.add( group );
|
||||
|
||||
if( mLSMDemodulator != null )
|
||||
{
|
||||
mAvailableTaps.addAll( mLSMDemodulator.getTapGroups() );
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
{
|
||||
if( mLSMDemodulator != null )
|
||||
{
|
||||
mLSMDemodulator.registerTap( 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( mLSMDemodulator );
|
||||
break;
|
||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
||||
QPSKTap qpsk = (QPSKTap)tap;
|
||||
mLSMDemodulator.setSymbolListener( qpsk );
|
||||
qpsk.setListener( mQPSKSlicer );
|
||||
break;
|
||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
||||
mQPSKSlicer.addListener( (DibitTap)tap );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
{
|
||||
if( mLSMDemodulator != null )
|
||||
{
|
||||
mLSMDemodulator.unregisterTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
||||
mStreamConverter.setListener( mAGC );
|
||||
break;
|
||||
case INSTRUMENT_AGC_OUTPUT:
|
||||
mAGC.setListener( mLSMDemodulator );
|
||||
break;
|
||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
||||
mLSMDemodulator.setSymbolListener( mQPSKSlicer );
|
||||
break;
|
||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
||||
mQPSKSlicer.removeListener( (DibitTap)tap );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PatchGroup
|
||||
{
|
||||
private long mExpireThreshold = 5000; //5 seconds
|
||||
private long mLastUpdated;
|
||||
private String mPatchGroupID;
|
||||
private List<String> mPatchedGroups = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs a patch group with the specified patch group ID
|
||||
* @param patchGroupID that identifies this patch group
|
||||
*/
|
||||
public PatchGroup(String patchGroupID)
|
||||
{
|
||||
mPatchGroupID = patchGroupID;
|
||||
mLastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch group ID for this patch group
|
||||
*/
|
||||
public String getPatchGroupID()
|
||||
{
|
||||
return mPatchGroupID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the patched group to this patch group. Duplicate patched groups are ignored.
|
||||
*
|
||||
* Note: the patch group ID will not be added as a patched group, to avoid circular dependencies.
|
||||
*
|
||||
* @return true if the patched group was added to this patch group
|
||||
*/
|
||||
public boolean addPatchedGroup(String patchedGroup)
|
||||
{
|
||||
if(patchedGroup != null && !patchedGroup.equals(mPatchGroupID) && !mPatchedGroups.contains(patchedGroup))
|
||||
{
|
||||
mPatchedGroups.add(patchedGroup);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patched talkgroups that are part of this patch group.
|
||||
*/
|
||||
public List<String> getPatchedGroups()
|
||||
{
|
||||
return mPatchedGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the timestamp for this patch group.
|
||||
*/
|
||||
public void updateTimestamp()
|
||||
{
|
||||
mLastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this patch group is expired, meaning that it hasn't been updated in the past (expire
|
||||
* threshold) seconds.
|
||||
*/
|
||||
public boolean isExpired()
|
||||
{
|
||||
return System.currentTimeMillis() - mLastUpdated > mExpireThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the patch group expiration threshold (default = 5000 ms). A patch group is considered expired if
|
||||
* no new updates have been received within the past (expire threshold) milliseconds
|
||||
*
|
||||
* @param expireThreshold in milliseconds
|
||||
*/
|
||||
public void setExpireThreshold(long expireThreshold)
|
||||
{
|
||||
mExpireThreshold = expireThreshold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.CallEvent;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.PatchGroupAlias;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupID;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class PatchGroupManager
|
||||
{
|
||||
private AliasList mAliasList;
|
||||
private List<PatchGroup> mPatchGroups = new ArrayList<>();
|
||||
private Broadcaster<CallEvent> mCallEventBroadcaster;
|
||||
|
||||
/**
|
||||
* Manages active P25 patch groups and auto-expires patch groups if not updated every 5 seconds.
|
||||
*
|
||||
* @param aliasList to keep updated with patch group changes
|
||||
* @param broadcaster for rebroadcasting patch group related call events
|
||||
*/
|
||||
public PatchGroupManager(AliasList aliasList, Broadcaster<CallEvent> broadcaster)
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
mCallEventBroadcaster = broadcaster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the patch group.
|
||||
*
|
||||
* @param patchGroupID of the patch group to remove
|
||||
*/
|
||||
public void removePatchGroup(String patchGroupID)
|
||||
{
|
||||
removePatchGroup(patchGroupID, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the patch group.
|
||||
*
|
||||
* @param patchGroupID of the patch group to remove
|
||||
* @param expired true if the patch group is being removed because it expired, false if we receive a
|
||||
* message commanding commanding a patch group delete.
|
||||
*/
|
||||
private void removePatchGroup(String patchGroupID, boolean expired)
|
||||
{
|
||||
PatchGroup patchGroup = getPatchGroup(patchGroupID);
|
||||
|
||||
if(patchGroup != null)
|
||||
{
|
||||
//Cleanup the alias list
|
||||
if(mAliasList != null)
|
||||
{
|
||||
Alias alias = mAliasList.getTalkgroupAlias(patchGroupID);
|
||||
|
||||
if(alias instanceof PatchGroupAlias)
|
||||
{
|
||||
PatchGroupAlias patchGroupAlias = (PatchGroupAlias)alias;
|
||||
|
||||
mAliasList.removeAlias(patchGroupAlias);
|
||||
|
||||
//Replace our temporary patch group alias with the original alias for the patch group ID
|
||||
if(patchGroupAlias.hasPatchGroupAlias())
|
||||
{
|
||||
mAliasList.addAlias(patchGroupAlias.getPatchGroupAlias());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPatchGroups.remove(patchGroup);
|
||||
|
||||
if(mCallEventBroadcaster != null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append((expired ? "EXPIRED " : "DELETED "));
|
||||
sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID());
|
||||
sb.append(" ").append(patchGroup.getPatchedGroups());
|
||||
|
||||
|
||||
mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_DELETE)
|
||||
.to(patchGroup.getPatchGroupID())
|
||||
.details(sb.toString())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/updates a patch group alias to the alias list containing aliases for each of the patched talkgroups. If the
|
||||
* patch group alias already exists, any patched talkgroups will be removed from the existing patch group alias and
|
||||
* replaced with the aliases corresponding to the patched talkgroup aliases.
|
||||
*
|
||||
* @param patchGroupID for the patch group
|
||||
* @param patchedTalkgroups containing the talkgroup IDs for each of the patched talkgroups
|
||||
*/
|
||||
public void updatePatchGroup(String patchGroupID, List<String> patchedTalkgroups)
|
||||
{
|
||||
boolean updated = false;
|
||||
|
||||
PatchGroup patchGroup = getPatchGroup(patchGroupID);
|
||||
|
||||
if(patchGroup == null)
|
||||
{
|
||||
//Remove any expired patch groups before we add a new one
|
||||
cleanupPatchGroups();
|
||||
patchGroup = new PatchGroup(patchGroupID);
|
||||
mPatchGroups.add(patchGroup);
|
||||
updated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
patchGroup.updateTimestamp();
|
||||
}
|
||||
|
||||
for(String patchedGroup : patchedTalkgroups)
|
||||
{
|
||||
if(patchGroup.addPatchedGroup(patchedGroup))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
//If we make any changes to the patch group, update the patch group alias
|
||||
if(updated)
|
||||
{
|
||||
PatchGroupAlias patchGroupAlias = null;
|
||||
|
||||
//Check for an existing alias for the patch talkgroup - do not include wildcard aliases
|
||||
if(mAliasList != null)
|
||||
{
|
||||
Alias existingAlias = mAliasList.getTalkgroupAlias(patchGroupID, false);
|
||||
|
||||
if(existingAlias instanceof PatchGroupAlias)
|
||||
{
|
||||
patchGroupAlias = (PatchGroupAlias)existingAlias;
|
||||
}
|
||||
else
|
||||
{
|
||||
patchGroupAlias = new PatchGroupAlias();
|
||||
|
||||
patchGroupAlias.addAliasID(new TalkgroupID(patchGroupID));
|
||||
|
||||
if(existingAlias != null)
|
||||
{
|
||||
mAliasList.removeAlias(existingAlias);
|
||||
|
||||
patchGroupAlias.setPatchGroupAlias(existingAlias);
|
||||
}
|
||||
|
||||
mAliasList.addAlias(patchGroupAlias);
|
||||
}
|
||||
|
||||
if(patchGroupAlias != null)
|
||||
{
|
||||
patchGroupAlias.setPatchedTalkgroupIDs(patchGroup.getPatchedGroups());
|
||||
|
||||
patchGroupAlias.clearPatchedAliases();
|
||||
|
||||
for(String patchedTalkgroup : patchGroup.getPatchedGroups())
|
||||
{
|
||||
Alias patchedAlias = mAliasList.getTalkgroupAlias(patchedTalkgroup);
|
||||
|
||||
if(patchedAlias != null)
|
||||
{
|
||||
patchGroupAlias.addPatchedAlias(patchedAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(mCallEventBroadcaster != null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID());
|
||||
sb.append(" ").append(patchGroup.getPatchedGroups());
|
||||
|
||||
mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_ADD)
|
||||
.aliasList(mAliasList)
|
||||
.details(sb.toString())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the patch group matching the patch group ID or null
|
||||
*/
|
||||
private PatchGroup getPatchGroup(String patchGroupID)
|
||||
{
|
||||
for(PatchGroup patchGroup : mPatchGroups)
|
||||
{
|
||||
if(patchGroup.getPatchGroupID().equals(patchGroupID))
|
||||
{
|
||||
return patchGroup;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the last updated timestamp on all patch groups and removes any patch groups that have not been
|
||||
* updated in the past 5 seconds
|
||||
*/
|
||||
public void cleanupPatchGroups()
|
||||
{
|
||||
Iterator<PatchGroup> it = mPatchGroups.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
PatchGroup patchGroup = it.next();
|
||||
|
||||
if(patchGroup.isExpired())
|
||||
{
|
||||
it.remove();
|
||||
|
||||
removePatchGroup(patchGroup.getPatchGroupID(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
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!" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
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!" );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package io.github.dsheirer.module.decode.p25.audio;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import io.github.dsheirer.audio.AudioPacket;
|
||||
import io.github.dsheirer.audio.IAudioPacketProvider;
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.channel.metadata.Metadata;
|
||||
import io.github.dsheirer.message.IMessageListener;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import jmbe.iface.AudioConversionLibrary;
|
||||
import jmbe.iface.AudioConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.dsp.gain.NonClippingGain;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU2Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDUMessage;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class P25AudioModule extends Module implements Listener<Message>, IAudioPacketProvider, IMessageListener,
|
||||
ISquelchStateListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
|
||||
|
||||
private static final String IMBE_CODEC = "IMBE";
|
||||
private static boolean mLibraryLoadStatusLogged = false;
|
||||
|
||||
private boolean mCanConvertAudio = false;
|
||||
private boolean mEncryptedCall = false;
|
||||
private boolean mEncryptedCallStateEstablished = false;
|
||||
|
||||
private AudioConverter mAudioConverter;
|
||||
private Listener<AudioPacket> mAudioPacketListener;
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
|
||||
private Metadata mMetadata;
|
||||
private LDU1Message mCachedLDU1Message = null;
|
||||
|
||||
public P25AudioModule(Metadata metadata)
|
||||
{
|
||||
mMetadata = metadata;
|
||||
loadConverter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<Message> getMessageListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SquelchState> getSquelchStateListener()
|
||||
{
|
||||
return mSquelchStateListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mAudioConverter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes call header (HDU) and voice frame (LDU1/LDU2) messages to decode audio and to determine the
|
||||
* encrypted audio status of a call event. Only the HDU and LDU2 messages convey encrypted call status. If an
|
||||
* LDU1 message is received without a preceding HDU message, then the LDU1 message is cached until the first
|
||||
* LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are
|
||||
* then processed for audio if the call is unencrypted.
|
||||
*/
|
||||
public void receive(Message message)
|
||||
{
|
||||
if(mCanConvertAudio && mAudioPacketListener != null)
|
||||
{
|
||||
if(mEncryptedCallStateEstablished)
|
||||
{
|
||||
if(message instanceof LDUMessage)
|
||||
{
|
||||
processAudio((LDUMessage)message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(message instanceof HDUMessage)
|
||||
{
|
||||
mEncryptedCallStateEstablished = true;
|
||||
mEncryptedCall = ((HDUMessage)message).isEncryptedAudio();
|
||||
}
|
||||
else if(message instanceof LDU1Message)
|
||||
{
|
||||
//When we receive an LDU1 message with first receiving the HDU message, cache the LDU1 Message
|
||||
//until we can determine the encrypted call state from the next LDU2 message
|
||||
mCachedLDU1Message = (LDU1Message)message;
|
||||
}
|
||||
else if(message instanceof LDU2Message)
|
||||
{
|
||||
mEncryptedCallStateEstablished = true;
|
||||
LDU2Message ldu2 = (LDU2Message)message;
|
||||
mEncryptedCall = ldu2.isEncryptedAudio();
|
||||
|
||||
if(mCachedLDU1Message != null)
|
||||
{
|
||||
processAudio(mCachedLDU1Message);
|
||||
mCachedLDU1Message = null;
|
||||
}
|
||||
|
||||
processAudio(ldu2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets.
|
||||
*/
|
||||
private void processAudio(LDUMessage ldu)
|
||||
{
|
||||
if(!mEncryptedCall)
|
||||
{
|
||||
for(byte[] frame : ldu.getIMBEFrames())
|
||||
{
|
||||
float[] audio = mAudioConverter.decode(frame);
|
||||
|
||||
audio = mGain.apply(audio);
|
||||
|
||||
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Encrypted audio processing not implemented
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
if((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 &&
|
||||
library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1)
|
||||
{
|
||||
mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
|
||||
|
||||
if(mAudioConverter != null)
|
||||
{
|
||||
mCanConvertAudio = true;
|
||||
|
||||
if(!mLibraryLoadStatusLogged)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("JMBE audio conversion library [");
|
||||
sb.append(library.getVersion());
|
||||
sb.append("] successfully loaded - P25 audio will be available");
|
||||
|
||||
mLog.info(sb.toString());
|
||||
|
||||
mLibraryLoadStatusLogged = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged)
|
||||
{
|
||||
mLog.info("JMBE audio conversion library NOT FOUND");
|
||||
mLibraryLoadStatusLogged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion());
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
|
||||
* flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
|
||||
* END audio packet so that downstream processors like the audio recorder can properly close out a call sequence.
|
||||
*/
|
||||
public class SquelchStateListener implements Listener<SquelchState>
|
||||
{
|
||||
@Override
|
||||
public void receive(SquelchState state)
|
||||
{
|
||||
if(state == SquelchState.SQUELCH)
|
||||
{
|
||||
if(mAudioPacketListener != null)
|
||||
{
|
||||
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
|
||||
}
|
||||
|
||||
mEncryptedCallStateEstablished = false;
|
||||
mEncryptedCall = false;
|
||||
mCachedLDU1Message = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.github.dsheirer.module.decode.p25.message;
|
||||
|
||||
/**
|
||||
* Interface for adjacent site (ie neighbor) messages
|
||||
*/
|
||||
public interface IAdjacentSite
|
||||
{
|
||||
public String getUniqueID();
|
||||
public String getRFSS();
|
||||
public String getSystemID();
|
||||
public String getSiteID();
|
||||
public String getLRA();
|
||||
public String getSystemServiceClass();
|
||||
|
||||
public String getDownlinkChannel();
|
||||
public long getDownlinkFrequency();
|
||||
|
||||
public String getUplinkChannel();
|
||||
public long getUplinkFrequency();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.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();
|
||||
|
||||
/**
|
||||
* Downlink (tower to mobile) Frequency for the specified channel
|
||||
*/
|
||||
public abstract long getDownlinkFrequency(int channelNumber);
|
||||
|
||||
/**
|
||||
* Uplink (mobile to tower) Frequency for the specified channel
|
||||
*/
|
||||
public abstract long getUplinkFrequency(int channelNumber);
|
||||
|
||||
/**
|
||||
* Indicates if this band is an FDMA (false) or TDMA (true) band
|
||||
*/
|
||||
public boolean isTDMA();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.github.dsheirer.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();
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.map.Plottable;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
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());
|
||||
sb.append(" DATA UNIT ID:" + mMessage.getInt(DUID));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDUMessage;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
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() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.tdu.lc.TDULinkControlMessage;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.tdu.TDUMessage;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.MotorolaOpcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.vendor.VendorOpcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.hdu;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.edac.Golay18;
|
||||
import io.github.dsheirer.edac.ReedSolomon_63_47_17;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Encryption;
|
||||
|
||||
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 isEncryptedAudio()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.edac.Hamming10;
|
||||
import io.github.dsheirer.edac.ReedSolomon_63_47_17;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||
import io.github.dsheirer.module.decode.p25.reference.VendorLinkControlOpcode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LDU1Message extends LDUMessage
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class);
|
||||
|
||||
public static final int ENCRYPTED_LINK_CONTROL_WORD_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the contents of the Link Control Word are encrypted.
|
||||
*
|
||||
* @return true if the contents of the LCW are encrypted.
|
||||
*/
|
||||
public boolean isEncryptedLinkControlWord()
|
||||
{
|
||||
return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_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(isEncryptedLinkControlWord())
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.edac.Hamming10;
|
||||
import io.github.dsheirer.edac.ReedSolomon_63_47_17;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Encryption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the audio stream is encrypted
|
||||
*/
|
||||
public boolean isEncryptedAudio()
|
||||
{
|
||||
Encryption encryption = getEncryption();
|
||||
|
||||
return encryption != Encryption.UNENCRYPTED &&
|
||||
encryption != Encryption.UNKNOWN;
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IAdjacentSite;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class AdjacentSiteStatusBroadcast extends LDU1Message implements IdentifierReceiver, IAdjacentSite
|
||||
{
|
||||
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:" + getLRA());
|
||||
|
||||
sb.append(" SYS:" + getSystemID());
|
||||
|
||||
sb.append(" SITE:" + getRFSS() + "-" + getSiteID());
|
||||
|
||||
sb.append(" CHAN:" + getChannel());
|
||||
|
||||
sb.append(" " + getSystemServiceClass());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getUniqueID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSystemID());
|
||||
sb.append(":");
|
||||
sb.append(getRFSS());
|
||||
sb.append(":");
|
||||
sb.append(getSiteID());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getLRA()
|
||||
{
|
||||
return mMessage.getHex(LRA, 2);
|
||||
}
|
||||
|
||||
public String getSystemID()
|
||||
{
|
||||
return mMessage.getHex(SYSTEM_ID, 3);
|
||||
}
|
||||
|
||||
public String getRFSS()
|
||||
{
|
||||
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 String getSystemServiceClass()
|
||||
{
|
||||
return SystemService.toString(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;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getDownlinkChannel()
|
||||
{
|
||||
return getChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUplinkChannel()
|
||||
{
|
||||
return getChannel();
|
||||
}
|
||||
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getUplinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IAdjacentSite;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class AdjacentSiteStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver, IAdjacentSite
|
||||
{
|
||||
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};
|
||||
|
||||
private IBandIdentifier mTransmitBand;
|
||||
private IBandIdentifier mReceiveBand;
|
||||
|
||||
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:" + getLRA());
|
||||
|
||||
sb.append(" SITE:" + getRFSS() + "-" + getSiteID());
|
||||
|
||||
sb.append(" DNLINK:" + getDownlinkChannel());
|
||||
|
||||
sb.append(" UPLINK:" + getUplinkChannel());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getUniqueID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSystemID());
|
||||
sb.append(":");
|
||||
sb.append(getRFSS());
|
||||
sb.append(":");
|
||||
sb.append(getSiteID());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getLRA()
|
||||
{
|
||||
return mMessage.getHex(LRA, 2);
|
||||
}
|
||||
|
||||
public String getRFSS()
|
||||
{
|
||||
return mMessage.getHex(RFSS_ID, 2);
|
||||
}
|
||||
|
||||
public String getSystemID()
|
||||
{
|
||||
return "***";
|
||||
}
|
||||
|
||||
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 getDownlinkChannel()
|
||||
{
|
||||
return getTransmitIdentifier() + "-" + getTransmitChannel();
|
||||
}
|
||||
|
||||
public int getReceiveIdentifier()
|
||||
{
|
||||
return mMessage.getInt(RECEIVE_IDENTIFIER);
|
||||
}
|
||||
|
||||
public int getReceiveChannel()
|
||||
{
|
||||
return mMessage.getInt(RECEIVE_CHANNEL);
|
||||
}
|
||||
|
||||
public String getUplinkChannel()
|
||||
{
|
||||
return getReceiveIdentifier() + "-" + getReceiveChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSystemServiceClass()
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
if(mTransmitBand != null)
|
||||
{
|
||||
return mTransmitBand.getDownlinkFrequency(getTransmitChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveBand != null)
|
||||
{
|
||||
return mReceiveBand.getUplinkFrequency(getReceiveChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentifierMessage(int identifier, IBandIdentifier message)
|
||||
{
|
||||
if(identifier == getTransmitIdentifier())
|
||||
{
|
||||
mTransmitBand = message;
|
||||
}
|
||||
|
||||
if(identifier == getReceiveIdentifier())
|
||||
{
|
||||
mReceiveBand = message;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getIdentifiers()
|
||||
{
|
||||
int[] identifiers = new int[2];
|
||||
|
||||
identifiers[0] = getTransmitIdentifier();
|
||||
identifiers[1] = getReceiveIdentifier();
|
||||
|
||||
return identifiers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class CallAlert extends LDU1Message
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class CallTermination extends LDU1Message
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class ChannelIdentifierUpdate extends LDU1Message implements IBandIdentifier
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDownlinkFrequency(int channelNumber)
|
||||
{
|
||||
return getBaseFrequency() + (channelNumber * getChannelSpacing());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUplinkFrequency(int channelNumber)
|
||||
{
|
||||
return getDownlinkFrequency(channelNumber) + getTransmitOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTDMA()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class ChannelIdentifierUpdateExplicit extends LDU1Message implements IBandIdentifier
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDownlinkFrequency(int channelNumber)
|
||||
{
|
||||
return getBaseFrequency() + ( channelNumber * getChannelSpacing() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUplinkFrequency(int channelNumber)
|
||||
{
|
||||
return getDownlinkFrequency(channelNumber) + getTransmitOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTDMA()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class GroupVoiceChannelUpdate extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mIdentifierUpdateA != null)
|
||||
{
|
||||
return mIdentifierUpdateA.getDownlinkFrequency(getChannelNumberA());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequencyA()
|
||||
{
|
||||
if(mIdentifierUpdateA != null)
|
||||
{
|
||||
return mIdentifierUpdateA.getUplinkFrequency(getChannelNumberA());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getDownlinkFrequencyB()
|
||||
{
|
||||
if(mIdentifierUpdateB != null)
|
||||
{
|
||||
return mIdentifierUpdateB.getDownlinkFrequency(getChannelNumberB());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequencyB()
|
||||
{
|
||||
if(mIdentifierUpdateB != null)
|
||||
{
|
||||
return mIdentifierUpdateB.getUplinkFrequency(getChannelNumberB());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class GroupVoiceChannelUpdateExplicit extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode.*;
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Service;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class NetworkStatusBroadcast extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getUplinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class NetworkStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Encryption;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class RFSSStatusBroadcast extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getUplinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class RFSSStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class SecondaryControlChannelBroadcast extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mIdentifierProviderA != null)
|
||||
{
|
||||
return mIdentifierProviderA.getDownlinkFrequency(getChannelA());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequencyA()
|
||||
{
|
||||
if(mIdentifierProviderA != null)
|
||||
{
|
||||
return mIdentifierProviderA.getUplinkFrequency(getChannelA());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getDownlinkFrequencyB()
|
||||
{
|
||||
if(mIdentifierProviderB != null)
|
||||
{
|
||||
return mIdentifierProviderB.getDownlinkFrequency(getChannelB());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequencyB()
|
||||
{
|
||||
if(mIdentifierProviderB != null)
|
||||
{
|
||||
return mIdentifierProviderB.getUplinkFrequency(getChannelB());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class SecondaryControlChannelBroadcastExplicit extends LDU1Message implements IdentifierReceiver
|
||||
{
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierProvider != null)
|
||||
{
|
||||
return mTransmitIdentifierProvider.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierProvider != null)
|
||||
{
|
||||
return mReceiveIdentifierProvider.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.reference.Service;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Digit;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.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 P25Message.DuplexMode getDuplexMode()
|
||||
{
|
||||
return mMessage.get( DUPLEX_MODE ) ? P25Message.DuplexMode.FULL : P25Message.DuplexMode.HALF;
|
||||
}
|
||||
|
||||
public P25Message.SessionMode getSessionMode()
|
||||
{
|
||||
return mMessage.get( SESSION_MODE ) ?
|
||||
P25Message.SessionMode.CIRCUIT : P25Message.SessionMode.PACKET;
|
||||
}
|
||||
|
||||
public String getTargetAddress()
|
||||
{
|
||||
return mMessage.getHex( TARGET_ADDRESS, 6 );
|
||||
}
|
||||
|
||||
public String getSourceAddress()
|
||||
{
|
||||
return mMessage.getHex( SOURCE_ADDRESS, 6 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.ldu.lc.motorola;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.vendor.VendorOpcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.PDUFormat;
|
||||
import io.github.dsheirer.module.decode.p25.reference.ServiceAccessPoint;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||
|
||||
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( " " );
|
||||
|
||||
switch( getFormat() )
|
||||
{
|
||||
case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL:
|
||||
sb.append( "ATSBK" );
|
||||
break;
|
||||
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
|
||||
sb.append( "**** UNCONFIRMED MULTI-BLOCK TRUNKING CONTROL" );
|
||||
break;
|
||||
default:
|
||||
sb.append( getDUID().getLabel() );
|
||||
break;
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.CallAlertExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.GroupAffiliationResponseExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.MessageUpdateExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.NetworkStatusBroadcastExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.ProtectionParameterBroadcast;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.RFSSStatusBroadcastExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.RoamingAddressUpdateExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.StatusQueryExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.StatusUpdateExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.UnitRegistrationResponseExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.data.GroupDataChannelGrantExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.data.IndividualDataChannelGrantExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.GroupVoiceChannelGrantExplicit;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.TelephoneInterconnectChannelGrantExplicit;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.TelephoneInterconnectChannelGrantUpdateExplicit;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitAnswerRequestExplicit;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantExtended;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantUpdateExtended;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.PDUFormat;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.PDUType;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package io.github.dsheirer.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 SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
return new SNDCPActivateTDSContextAccept( message );
|
||||
case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT:
|
||||
return new SNDCPActivateTDSContextReject( message );
|
||||
case SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST:
|
||||
return new SNDCPActivateTDSContextRequest( message );
|
||||
case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
||||
return new SNDCPDeactivateTDSContext( message );
|
||||
case SNDCP_RF_CONFIRMED_DATA:
|
||||
return new SNDCPUserData( message );
|
||||
case SNDCP_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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PDUTypeUnknown extends PDUConfirmedMessage
|
||||
{
|
||||
public final static Logger mLog =
|
||||
LoggerFactory.getLogger( PDUTypeUnknown.class );
|
||||
|
||||
public PDUTypeUnknown( PDUConfirmedMessage message )
|
||||
{
|
||||
super( message );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "NAC:" );
|
||||
sb.append( getNAC() );
|
||||
sb.append( " LLID:" );
|
||||
sb.append( getLogicalLinkID() );
|
||||
sb.append( " PACKET DATA UNIT CONFIRMED - " );
|
||||
sb.append( getPDUType().getLabel() );
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
|
||||
public class PacketData extends PDUConfirmedMessage
|
||||
{
|
||||
public final static Logger mLog =
|
||||
LoggerFactory.getLogger( PacketData.class );
|
||||
|
||||
public static final int DATA_BLOCK_START = 176;
|
||||
|
||||
public PacketData( BinaryMessage message, AliasList aliasList )
|
||||
{
|
||||
super( message, aliasList );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "NAC:" );
|
||||
sb.append( getNAC() );
|
||||
sb.append( " PDUC LLID:" );
|
||||
sb.append( getLogicalLinkID() );
|
||||
|
||||
|
||||
sb.append( " PACKET DATA" );
|
||||
sb.append( " CRC[" );
|
||||
sb.append( getErrorStatus() );
|
||||
sb.append( "]" );
|
||||
|
||||
if( !mMessage.get( FINAL_FRAGMENT_FLAG ) )
|
||||
{
|
||||
sb.append( " RESENDING" );
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.reference.MDPConfigurationOption;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.reference.SNDCPActivationRejectReason;
|
||||
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.module.decode.p25.reference.MDPConfigurationOption;
|
||||
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.reference.PDUType;
|
||||
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.SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST )
|
||||
{
|
||||
return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ?
|
||||
"ALL NSAPIS" : "THIS NSAPI";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.reference.IPHeaderCompression;
|
||||
import io.github.dsheirer.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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.IAdjacentSite;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
public class AdjacentStatusBroadcastExtended extends PDUMessage implements IdentifierReceiver, IAdjacentSite
|
||||
{
|
||||
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:" + getLRA());
|
||||
|
||||
sb.append(" SYS:" + getSystemID());
|
||||
|
||||
sb.append(" RFSS:" + getRFSS());
|
||||
|
||||
sb.append(" SITE:" + getSiteID());
|
||||
|
||||
sb.append(" CTRL CHAN:" + getTransmitIdentifier() + "-" + getTransmitChannel());
|
||||
|
||||
sb.append(" DN:" + getDownlinkFrequency());
|
||||
|
||||
sb.append(" " + getReceiveIdentifier() + "-" + getReceiveChannel());
|
||||
|
||||
sb.append(" UP:" + getUplinkFrequency());
|
||||
|
||||
sb.append(" SYS SVC CLASS:" + getSystemServiceClass());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getUniqueID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSystemID());
|
||||
sb.append(":");
|
||||
sb.append(getRFSS());
|
||||
sb.append(":");
|
||||
sb.append(getSiteID());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getSystemID()
|
||||
{
|
||||
return mMessage.getHex(SYSTEM_ID, 3);
|
||||
}
|
||||
|
||||
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 long getDownlinkFrequency()
|
||||
{
|
||||
if(mTransmitIdentifierProvider != null)
|
||||
{
|
||||
return mTransmitIdentifierProvider.getDownlinkFrequency(getTransmitChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierProvider != null)
|
||||
{
|
||||
return mReceiveIdentifierProvider.getUplinkFrequency(getReceiveChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRFSS()
|
||||
{
|
||||
return mMessage.getHex(RF_SUBSYSTEM_ID, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLRA()
|
||||
{
|
||||
return mMessage.getHex(LRA, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSystemServiceClass()
|
||||
{
|
||||
return SystemService.toString(mMessage.getInt(SYSTEM_SERVICE_CLASS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownlinkChannel()
|
||||
{
|
||||
return getTransmitIdentifier() + "-" + getTransmitChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUplinkChannel()
|
||||
{
|
||||
return getReceiveIdentifier() + "-" + getReceiveChannel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Response;
|
||||
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.P25NetworkCallsign;
|
||||
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Encryption;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.P25MessageFramer;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.control;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Response;
|
||||
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.data;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.data;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.UnitToUnitChannelGrantExtended;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
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 P25Message.DuplexMode getDuplexMode()
|
||||
{
|
||||
return mMessage.get(DUPLEX_MODE) ? P25Message.DuplexMode.FULL : P25Message.DuplexMode.HALF;
|
||||
}
|
||||
|
||||
public P25Message.SessionMode getSessionMode()
|
||||
{
|
||||
return mMessage.get(SESSION_MODE) ?
|
||||
P25Message.SessionMode.CIRCUIT : P25Message.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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
|
||||
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 P25Message.DuplexMode getDuplexMode()
|
||||
{
|
||||
return mMessage.get( DUPLEX_MODE ) ? P25Message.DuplexMode.FULL : P25Message.DuplexMode.HALF;
|
||||
}
|
||||
|
||||
public P25Message.SessionMode getSessionMode()
|
||||
{
|
||||
return mMessage.get( SESSION_MODE ) ?
|
||||
P25Message.SessionMode.CIRCUIT : P25Message.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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.UnitToUnitChannelGrantExtended;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCP25;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Opcode;
|
||||
|
||||
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()
|
||||
{
|
||||
if(mTransmitIdentifierUpdate != null)
|
||||
{
|
||||
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveIdentifierUpdate != null)
|
||||
{
|
||||
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.message.tdu;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.reference.DataUnitID;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "NAC:" );
|
||||
sb.append( getNAC() );
|
||||
sb.append( " " );
|
||||
sb.append( getDUID().getLabel() );
|
||||
|
||||
sb.append( " TERMINATOR DATA UNIT" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.tdu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IAdjacentSite;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class AdjacentSiteStatusBroadcast extends TDULinkControlMessage implements IdentifierReceiver, IAdjacentSite
|
||||
{
|
||||
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:" + getLRA());
|
||||
|
||||
sb.append(" SYS:" + getSystemID());
|
||||
|
||||
sb.append(" SITE:" + getRFSS() + "-" + getSiteID());
|
||||
|
||||
sb.append(" CHAN:" + getChannel());
|
||||
|
||||
sb.append(" " + getSystemServiceClass());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getUniqueID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSystemID());
|
||||
sb.append(":");
|
||||
sb.append(getRFSS());
|
||||
sb.append(":");
|
||||
sb.append(getSiteID());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getLRA()
|
||||
{
|
||||
return mMessage.getHex(LRA, 2);
|
||||
}
|
||||
|
||||
public String getSystemID()
|
||||
{
|
||||
return mMessage.getHex(SYSTEM_ID, 3);
|
||||
}
|
||||
|
||||
public String getRFSS()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownlinkChannel()
|
||||
{
|
||||
return getChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUplinkChannel()
|
||||
{
|
||||
return getChannel();
|
||||
}
|
||||
|
||||
public int getChannelNumber()
|
||||
{
|
||||
return mMessage.getInt(CHANNEL);
|
||||
}
|
||||
|
||||
public String getSystemServiceClass()
|
||||
{
|
||||
return SystemService.toString(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()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mIdentifierUpdate != null)
|
||||
{
|
||||
return mIdentifierUpdate.getUplinkFrequency(getChannelNumber());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25.message.tdu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.IAdjacentSite;
|
||||
import io.github.dsheirer.module.decode.p25.message.IBandIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class AdjacentSiteStatusBroadcastExplicit extends TDULinkControlMessage implements IdentifierReceiver, IAdjacentSite
|
||||
{
|
||||
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};
|
||||
|
||||
private IBandIdentifier mTransmitBand;
|
||||
private IBandIdentifier mReceiveBand;
|
||||
|
||||
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:" + getLRA());
|
||||
|
||||
sb.append(" SITE:" + getRFSS() + "-" + getSiteID());
|
||||
|
||||
sb.append(" DNLINK:" + getDownlinkChannel());
|
||||
|
||||
sb.append(" UPLINK:" + getUplinkChannel());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getUniqueID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getSystemID());
|
||||
sb.append(":");
|
||||
sb.append(getRFSS());
|
||||
sb.append(":");
|
||||
sb.append(getSiteID());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getLRA()
|
||||
{
|
||||
return mMessage.getHex(LRA, 2);
|
||||
}
|
||||
|
||||
public String getRFSS()
|
||||
{
|
||||
return mMessage.getHex(RFSS_ID, 2);
|
||||
}
|
||||
|
||||
public String getSystemID()
|
||||
{
|
||||
return "***";
|
||||
}
|
||||
|
||||
public String getSiteID()
|
||||
{
|
||||
return mMessage.getHex(SITE_ID, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSystemServiceClass()
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
|
||||
public int getTransmitIdentifier()
|
||||
{
|
||||
return mMessage.getInt(TRANSMIT_IDENTIFIER);
|
||||
}
|
||||
|
||||
public int getTransmitChannel()
|
||||
{
|
||||
return mMessage.getInt(TRANSMIT_CHANNEL);
|
||||
}
|
||||
|
||||
public String getDownlinkChannel()
|
||||
{
|
||||
return getTransmitIdentifier() + "-" + getTransmitChannel();
|
||||
}
|
||||
|
||||
public int getReceiveIdentifier()
|
||||
{
|
||||
return mMessage.getInt(RECEIVE_IDENTIFIER);
|
||||
}
|
||||
|
||||
public int getReceiveChannel()
|
||||
{
|
||||
return mMessage.getInt(RECEIVE_CHANNEL);
|
||||
}
|
||||
|
||||
public String getUplinkChannel()
|
||||
{
|
||||
return getReceiveIdentifier() + "-" + getReceiveChannel();
|
||||
}
|
||||
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
if(mTransmitBand != null)
|
||||
{
|
||||
return mTransmitBand.getDownlinkFrequency(getTransmitChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mReceiveBand != null)
|
||||
{
|
||||
return mReceiveBand.getUplinkFrequency(getReceiveChannel());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentifierMessage(int identifier, IBandIdentifier message)
|
||||
{
|
||||
if(identifier == getTransmitIdentifier())
|
||||
{
|
||||
mTransmitBand = message;
|
||||
}
|
||||
|
||||
if(identifier == getReceiveIdentifier())
|
||||
{
|
||||
mReceiveBand = message;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getIdentifiers()
|
||||
{
|
||||
int[] identifiers = new int[2];
|
||||
|
||||
identifiers[0] = getTransmitIdentifier();
|
||||
identifiers[1] = getReceiveIdentifier();
|
||||
|
||||
return identifiers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.tdu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class CallAlert extends TDULinkControlMessage
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.github.dsheirer.module.decode.p25.message.tdu.lc;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode;
|
||||
|
||||
public class CallTermination extends TDULinkControlMessage
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user