mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 23:27:24 -05:00
Closes #7 NXDN Decoder * Implements: NXDN4800, NXDN9600, Type-C and Type-D decoding and trunk tracking. * Redesigns tuner and channel frequency error management.
1580 lines
42 KiB
Java
1580 lines
42 KiB
Java
/*
|
|
* *****************************************************************************
|
|
* Copyright (C) 2014-2026 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.bits;
|
|
|
|
import io.github.dsheirer.edac.CRC;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.nio.charset.Charset;
|
|
import java.util.BitSet;
|
|
import org.apache.commons.lang3.Validate;
|
|
import org.apache.commons.math3.util.FastMath;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
public class BinaryMessage extends BitSet
|
|
{
|
|
private static final long serialVersionUID = 1L;
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(BinaryMessage.class);
|
|
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
|
|
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
|
private static final String UTF_8 = "UTF-8";
|
|
private static final String GB2312 = "GB2312";
|
|
|
|
|
|
/**
|
|
* Logical (ie constructed) size of this bitset, despite the actual size of
|
|
* the super bitset that this class is based on
|
|
*/
|
|
private int mSize;
|
|
|
|
/**
|
|
* Pointer to the next fill index location, when adding bits to this bitset
|
|
* one at a time.
|
|
*/
|
|
private int mPointer = 0;
|
|
|
|
/**
|
|
* Used for temporary storage of CRC check results when we're passing this
|
|
* message to an EDAC function.
|
|
*/
|
|
private CRC mCRC;
|
|
|
|
/**
|
|
* Constructs a bitset that buffers bits added one at a time, up to the size
|
|
* of the this bitset.
|
|
* <p>
|
|
* Note: the super class bitset behind this class may have a size larger
|
|
* that the size parameter specified.
|
|
*
|
|
* @param size of constructed bitset
|
|
*/
|
|
public BinaryMessage(int size)
|
|
{
|
|
super(size);
|
|
mSize = size;
|
|
}
|
|
|
|
/**
|
|
* Constructs a bitset buffer and preloads it with the bits contained in
|
|
* the bitsToPreload parameter. If the bitsToPreload are longer than the
|
|
* size of the bitset, only those bits that fit will be preloaded
|
|
*
|
|
* @param size
|
|
* @param bitsToPreload
|
|
*/
|
|
public BinaryMessage(int size, boolean[] bitsToPreload)
|
|
{
|
|
this(size);
|
|
|
|
int pointer = 0;
|
|
|
|
while(!this.isFull() && pointer < bitsToPreload.length)
|
|
{
|
|
try
|
|
{
|
|
this.add(bitsToPreload[pointer]);
|
|
}
|
|
catch(BitSetFullException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
|
|
pointer++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a new BitSetBuffer from an existing one
|
|
*/
|
|
private BinaryMessage(BinaryMessage toCopyFrom)
|
|
{
|
|
this(toCopyFrom.size());
|
|
this.or(toCopyFrom);
|
|
this.mPointer = toCopyFrom.pointer();
|
|
this.mCRC = toCopyFrom.mCRC;
|
|
this.mSize = toCopyFrom.mSize;
|
|
}
|
|
|
|
public BinaryMessage(BitSet bitset, int size)
|
|
{
|
|
this(size);
|
|
this.or(bitset);
|
|
this.mPointer = size - 1;
|
|
}
|
|
|
|
public BinaryMessage(byte[] data)
|
|
{
|
|
this(BitSet.valueOf(data), data.length * 8);
|
|
}
|
|
|
|
/**
|
|
* Returns a mew binary message containing the bits from (inclusive) to
|
|
* end (exclusive).
|
|
*
|
|
* @param start
|
|
* @param end
|
|
* @return
|
|
*/
|
|
public BinaryMessage getSubMessage(int start, int end)
|
|
{
|
|
BitSet subset = this.get(start, end);
|
|
|
|
return new BinaryMessage(subset, end - start);
|
|
}
|
|
|
|
public CRC getCRC()
|
|
{
|
|
return mCRC;
|
|
}
|
|
|
|
public void setCRC(CRC crc)
|
|
{
|
|
mCRC = crc;
|
|
}
|
|
|
|
/**
|
|
* Indicates the number of bit errors that were corrected in this message.
|
|
*
|
|
* @return number of corrected bits in this message
|
|
* @see CorrectedBinaryMessage subclass that supports setting this value
|
|
*/
|
|
public int getCorrectedBitCount()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Current pointer index
|
|
*/
|
|
public int pointer()
|
|
{
|
|
return mPointer;
|
|
}
|
|
|
|
/**
|
|
* Sets the pointer to a specific value
|
|
*
|
|
* @param index
|
|
*/
|
|
public void setPointer(int index)
|
|
{
|
|
mPointer = index;
|
|
}
|
|
|
|
/**
|
|
* Moves the current pointer position left (negative adjustment) or
|
|
* right (positive adjustment)
|
|
*/
|
|
public void adjustPointer(int adjustment)
|
|
{
|
|
mPointer += adjustment;
|
|
}
|
|
|
|
/**
|
|
* Static method to construct a new BitSetBuffer, preloaded with the bits
|
|
* from the preload parameter, and then filled with the bits from the
|
|
* second bitsetbuffer parameter.
|
|
*
|
|
* @param preloadBits - boolean array of bits to be prepended to the new
|
|
* bitset
|
|
* @param bitsetToAppend - full bitset to be appended to the residual bits array
|
|
* @return - new Bitset preloaded with residual bits and new bitset
|
|
*/
|
|
public static BinaryMessage merge(boolean[] preloadBits, BinaryMessage bitsetToAppend)
|
|
{
|
|
BinaryMessage returnValue = new BinaryMessage(preloadBits.length + bitsetToAppend.size(), preloadBits);
|
|
|
|
int pointer = 0;
|
|
|
|
while(pointer < bitsetToAppend.size() && !returnValue.isFull())
|
|
{
|
|
try
|
|
{
|
|
returnValue.add(bitsetToAppend.get(pointer));
|
|
}
|
|
catch(BitSetFullException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
|
|
pointer++;
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/**
|
|
* @return a (new) copy of this binary message
|
|
*/
|
|
public BinaryMessage copy()
|
|
{
|
|
return new BinaryMessage(this);
|
|
}
|
|
|
|
public boolean isFull()
|
|
{
|
|
return mPointer >= mSize;
|
|
}
|
|
|
|
/**
|
|
* Overrides the in-build size() method of the bitset and returns the value
|
|
* specified at instantiation. The actual bitset size may be larger than
|
|
* this value, and that size is managed by the super class.
|
|
*/
|
|
@Override
|
|
public int size()
|
|
{
|
|
return mSize;
|
|
}
|
|
|
|
/**
|
|
* Current size of the message indicating the quantity of bits that have been added to this message. This value
|
|
* is less than or equal to the size() value.
|
|
* @return current size in bits.
|
|
*/
|
|
public int currentSize()
|
|
{
|
|
return mPointer;
|
|
}
|
|
|
|
public void setSize(int size)
|
|
{
|
|
mSize = size;
|
|
}
|
|
|
|
/**
|
|
* Clears (sets to false or 0) the bits in this bitset and resets the
|
|
* pointer to zero.
|
|
*/
|
|
@Override
|
|
public void clear()
|
|
{
|
|
this.clear(0, mSize);
|
|
mPointer = 0;
|
|
}
|
|
|
|
/**
|
|
* Adds a the bit parameters to this bitset, placing it in the index
|
|
* specified by mPointer, and incrementing mPointer to prepare for the next
|
|
* call to this method
|
|
*
|
|
* @param value
|
|
* @throws BitSetFullException - if the size specified at construction is
|
|
* exceeded. Invoke full() to determine if the bitset is full either before
|
|
* adding a new bit, or after adding a bit.
|
|
*/
|
|
public void add(boolean value) throws BitSetFullException
|
|
{
|
|
if(!isFull())
|
|
{
|
|
this.set(mPointer++, value);
|
|
}
|
|
else
|
|
{
|
|
throw new BitSetFullException("bitset is full -- contains [" + mPointer + "/" + size() + "] bits");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds bit1 and bit2 to this bitset.
|
|
*
|
|
* Note: this method does not provide overfill protection.
|
|
*
|
|
* @param bit1 to add.
|
|
* @param bit2 to add.
|
|
*/
|
|
public void add(boolean bit1, boolean bit2)
|
|
{
|
|
set(mPointer++, bit1);
|
|
set(mPointer++, bit2);
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return toHexString();
|
|
}
|
|
|
|
/**
|
|
* Binary representation of the message
|
|
* @return message in binary 0/1 format
|
|
*/
|
|
public String toBinaryString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for(int x = 0; x < mSize; x++)
|
|
{
|
|
sb.append((this.get(x) ? "1" : "0"));
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Hex string representation of the message
|
|
* @return message in hex
|
|
*/
|
|
public String toHexString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for(int x = 0; x < size(); x += 4)
|
|
{
|
|
sb.append(getNibbleAsHex(x));
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Overrides the parent method which returns a BitSet so that we can return a BinaryMessage
|
|
* @param from inclusive
|
|
* @param to exclusive
|
|
* @return
|
|
*/
|
|
@Override
|
|
public BinaryMessage get(int from, int to)
|
|
{
|
|
return new BinaryMessage(super.get(from, to), (to - from));
|
|
}
|
|
|
|
/**
|
|
* Returns this bitset as an array of integer ones and zeros
|
|
*/
|
|
public int[] toIntegerArray()
|
|
{
|
|
int[] values = new int[mSize];
|
|
|
|
for(int i = nextSetBit(0); i >= 0 && i < mSize; i = nextSetBit(i + 1))
|
|
{
|
|
values[i] = 1;
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* Returns this message as a little endian byte array. Extra 0 bits will be padded to the end to make the overall
|
|
* length a multiple of 8.
|
|
*
|
|
* @return little endian message byte array
|
|
*/
|
|
public byte[] toByteArray()
|
|
{
|
|
int length = size() / 8;
|
|
|
|
if(length * 8 < size())
|
|
{
|
|
length++;
|
|
}
|
|
|
|
byte[] bytes = new byte[length];
|
|
|
|
int pointer = 0;
|
|
|
|
for(int x = 0; x < length; x++)
|
|
{
|
|
bytes[x] = getByte(pointer);
|
|
pointer += 8;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
/**
|
|
* Returns this bitset as a reversed bit order array of integer ones and zeros
|
|
* from the specified index range
|
|
*/
|
|
public int[] toReverseIntegerArray(int start, int end)
|
|
{
|
|
int[] values = new int[end - start + 1];
|
|
|
|
for(int i = nextSetBit(start); i >= start && i <= end; i = nextSetBit(i + 1))
|
|
{
|
|
values[end - i] = 1;
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* Returns a boolean array from startIndex to end of the bitset
|
|
*/
|
|
public boolean[] getBits(int startIndex)
|
|
{
|
|
return getBits(startIndex, mSize - 1);
|
|
}
|
|
|
|
/**
|
|
* Returns a boolean array of the right-most bitCount number of bits
|
|
*/
|
|
public boolean[] right(int bitCount)
|
|
{
|
|
return getBits(mSize - bitCount - 1);
|
|
}
|
|
|
|
/**
|
|
* Returns a boolean array representing the bits located from startIndex
|
|
* through endIndex
|
|
*/
|
|
public boolean[] getBits(int startIndex, int endIndex)
|
|
{
|
|
boolean[] returnValue = null;
|
|
|
|
if(startIndex >= 0 &&
|
|
startIndex < endIndex &&
|
|
endIndex < mSize)
|
|
{
|
|
returnValue = new boolean[endIndex - startIndex + 1];
|
|
|
|
int bitsetPointer = startIndex;
|
|
int returnPointer = 0;
|
|
|
|
while(bitsetPointer <= endIndex)
|
|
{
|
|
returnValue[returnPointer] = this.get(bitsetPointer);
|
|
bitsetPointer++;
|
|
returnPointer++;
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/**
|
|
* Returns the integer value represented by the bit array
|
|
*
|
|
* @param bits - an array of bit positions that will be treated as if they
|
|
* were contiguous bits, with index 0 being the MSB and index
|
|
* length - 1 being the LSB
|
|
* @return - integer value of the bit array
|
|
*/
|
|
public int getInt(int[] bits)
|
|
{
|
|
if(bits.length > 32)
|
|
{
|
|
throw new IllegalArgumentException("Overflow - must be 32 bits "
|
|
+ "or less to fit into a primitive integer value");
|
|
}
|
|
|
|
int value = 0;
|
|
|
|
for(int index : bits)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns the integer value of the message field described by the field argument.
|
|
* @param intField with start and end indices (inclusive).
|
|
* @return integer field value.
|
|
*/
|
|
public int getInt(IntField intField)
|
|
{
|
|
int value = 0;
|
|
|
|
for(int index = intField.start(); index <= intField.end(); index++)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns the integer value of the message field described by the field argument.
|
|
* @param fragmentedField with an array of message indices
|
|
* @return integer field value.
|
|
*/
|
|
public int getInt(FragmentedIntField fragmentedField)
|
|
{
|
|
int value = 0;
|
|
|
|
for(int index: fragmentedField.indices())
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
* @param intField to inspect
|
|
* @return true if any of the bits are set, indicating a non-zero value.
|
|
*/
|
|
public boolean hasInt(IntField intField)
|
|
{
|
|
return nextSetBit(intField.start()) <= intField.end();
|
|
}
|
|
|
|
/**
|
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
* @param fragmentedField to inspect
|
|
* @return true if any of the bits are set, indicating a non-zero value.
|
|
*/
|
|
public boolean hasInt(FragmentedIntField fragmentedField)
|
|
{
|
|
for(int index: fragmentedField.indices())
|
|
{
|
|
if(get(index))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the integer value of the message field described by the field argument where the message start index is
|
|
* offset within this binary message.
|
|
* @param intField with start and end indices (inclusive).
|
|
* @param offset to the start of the first message index.
|
|
* @return integer field value.
|
|
*/
|
|
public int getInt(IntField intField, int offset)
|
|
{
|
|
int value = 0;
|
|
|
|
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns the integer value of the message field described by the field argument where the message start index is
|
|
* offset within this binary message.
|
|
* @param fragmentedField with an array of field indices
|
|
* @param offset to the start of the first message index.
|
|
* @return integer field value.
|
|
*/
|
|
public int getInt(FragmentedIntField fragmentedField, int offset)
|
|
{
|
|
int value = 0;
|
|
|
|
for(int index: fragmentedField.indices())
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index + offset))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
* @param intField to inspect
|
|
* @param offset to the first bit of the message.
|
|
* @return true if any of the bits are set, indicating a non-zero value.
|
|
*/
|
|
public boolean hasInt(IntField intField, int offset)
|
|
{
|
|
return nextSetBit(intField.start() + offset) <= (intField.end() + offset);
|
|
}
|
|
|
|
/**
|
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
* @param intField to inspect
|
|
* @param offset to the first bit of the message.
|
|
* @return true if any of the bits are set, indicating a non-zero value.
|
|
*/
|
|
public boolean hasInt(FragmentedIntField fragmentedField, int offset)
|
|
{
|
|
for(int index: fragmentedField.indices())
|
|
{
|
|
if(get(index + offset))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the long value of the message field described by the field argument.
|
|
* @param intField with start and end indices (inclusive).
|
|
* @return integer field value.
|
|
*/
|
|
public long getLong(LongField intField)
|
|
{
|
|
long value = 0;
|
|
|
|
for(int index = intField.start(); index <= intField.end(); index++)
|
|
{
|
|
value = Long.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns the long value of the message field described by the field argument where the message start index is
|
|
* offset within this binary message.
|
|
* @param intField with start and end indices (inclusive).
|
|
* @param offset to the start of the first message index.
|
|
* @return field value.
|
|
*/
|
|
public long getLong(LongField intField, int offset)
|
|
{
|
|
long value = 0;
|
|
|
|
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
|
|
{
|
|
value = Long.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the integer value represented by the bit array
|
|
*
|
|
* @param bits - an array of bit positions that will be treated as if they
|
|
* were contiguous bits, with index 0 being the MSB and index
|
|
* length - 1 being the LSB
|
|
* @param offset to apply to each of the bit position in the bits array.
|
|
* @return - integer value of the bit array
|
|
*/
|
|
public int getInt(int[] bits, int offset)
|
|
{
|
|
if(bits.length > 32)
|
|
{
|
|
throw new IllegalArgumentException("Overflow - must be 32 bits "
|
|
+ "or less to fit into a primitive integer value");
|
|
}
|
|
|
|
int value = 0;
|
|
|
|
for(int index : bits)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index + offset))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public void setInt(int value, int[] indices)
|
|
{
|
|
for(int x = 0; x < indices.length; x++)
|
|
{
|
|
int mask = 1 << (indices.length - x - 1);
|
|
|
|
if((value & mask) == mask)
|
|
{
|
|
set(indices[x]);
|
|
}
|
|
else
|
|
{
|
|
clear(indices[x]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the integer value to the field described by the argument.
|
|
* @param value to set
|
|
* @param intField describing the indices of the field.
|
|
*/
|
|
public void setInt(int value, IntField intField)
|
|
{
|
|
for(int x = 0; x < intField.width(); x++)
|
|
{
|
|
int mask = 1 << (intField.width() - x - 1);
|
|
|
|
if((value & mask) == mask)
|
|
{
|
|
set(intField.start() + x);
|
|
}
|
|
else
|
|
{
|
|
clear(intField.start() + x);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the byte value represented by the bit array
|
|
*
|
|
* @param bits - an array of bit positions that will be treated as if they
|
|
* were contiguous bits, with index 0 being the MSB and index
|
|
* length - 1 being the LSB
|
|
* @return - byte value of the bit array
|
|
*/
|
|
public byte getByte(int[] bits)
|
|
{
|
|
if(bits.length != 8)
|
|
{
|
|
throw new IllegalArgumentException("Invalid - there must be 8"
|
|
+ "indexes to form a proper byte");
|
|
}
|
|
|
|
int value = 0;
|
|
|
|
for(int index : bits)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return (byte)(value & 0xFF);
|
|
}
|
|
|
|
/**
|
|
* Returns the byte value represented by the bit array
|
|
*
|
|
* @param bits - an array of bit positions that will be treated as if they
|
|
* were contiguous bits, with index 0 being the MSB and index
|
|
* length - 1 being the LSB
|
|
* @param offset to apply to each of the bit positions
|
|
* @return - byte value of the bit array
|
|
*/
|
|
public byte getByte(int[] bits, int offset)
|
|
{
|
|
if(bits.length != 8)
|
|
{
|
|
throw new IllegalArgumentException("Invalid - there must be 8"
|
|
+ "indexes to form a proper byte");
|
|
}
|
|
|
|
int value = 0;
|
|
|
|
for(int index : bits)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(index + offset))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return (byte)(value & 0xFF);
|
|
}
|
|
|
|
/**
|
|
* Returns the byte value contained between index and index + 7 bit positions. If the length of this message is
|
|
* shorter than index + 7, then the least significant bits are set to zero in the returned value.
|
|
*
|
|
* @param startIndex specifying the start of the byte value
|
|
* @return byte value contained at index <> index + 7 bit positions
|
|
*/
|
|
public byte getByte(int startIndex)
|
|
{
|
|
int value = 0;
|
|
|
|
for(int x = 0; x < 8; x++)
|
|
{
|
|
value <<= 1;
|
|
|
|
int index = startIndex + x;
|
|
|
|
if(index <= size() && get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return (byte)value;
|
|
}
|
|
|
|
/**
|
|
* Converts the message to a byte array.
|
|
*
|
|
* Note: BitSet.toByteArray() outputs the bytes in big-endian (inverted) order. This method retains the correct
|
|
* endianness where bit 0 is the most significant bit of the first byte.
|
|
*/
|
|
public byte[] getBytes()
|
|
{
|
|
byte[] bytes = new byte[(int)FastMath.ceil((double)size() / 8.0)];
|
|
|
|
for(int x = 0; x < bytes.length; x++)
|
|
{
|
|
bytes[x] = getByte(x * 8);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
/**
|
|
* Returns the 4-bit nibble value contained between index and index + 3 bit positions. If the length of this
|
|
* message is shorter than index + 3, then the least significant bits are set to zero in the returned value.
|
|
*
|
|
* @param startIndex specifying the start of the byte value
|
|
* @return nibble value contained at index <> index + 3 bit positions
|
|
*/
|
|
public int getNibble(int startIndex)
|
|
{
|
|
int value = 0;
|
|
|
|
for(int x = 0; x < 4; x++)
|
|
{
|
|
value <<= 1;
|
|
|
|
int index = startIndex + x;
|
|
|
|
if(index <= size() && get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Sets the byte value at index position through index + 7 position.
|
|
*
|
|
* @param index bit position where to write the MSB of the byte value
|
|
* @param value to write at the index through index + 7 bit positions
|
|
*/
|
|
public void setByte(int index, byte value)
|
|
{
|
|
Validate.isTrue((index + 8) <= size());
|
|
|
|
int mask = 0x80;
|
|
|
|
for(int x = 0; x < 8; x++)
|
|
{
|
|
if((mask & value) == mask)
|
|
{
|
|
set(index + x);
|
|
}
|
|
else
|
|
{
|
|
clear(index + x);
|
|
}
|
|
|
|
mask = mask >> 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the long value represented by the bit array
|
|
*
|
|
* @param bits - an array of bit positions that will be treated as if they
|
|
* were contiguous bits, with index 0 being the MSB and index
|
|
* length - 1 being the LSB
|
|
* @return - integer value of the bit array
|
|
*/
|
|
public long getLong(int[] bits)
|
|
{
|
|
if(bits.length > 64)
|
|
{
|
|
throw new IllegalArgumentException("Overflow - must be 64 bits "
|
|
+ "or less to fit into a primitive long value");
|
|
}
|
|
|
|
long value = 0;
|
|
|
|
for(int index : bits)
|
|
{
|
|
value = Long.rotateLeft(value, 1);
|
|
|
|
if(get(index))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns the long value represented by the bit array
|
|
*
|
|
* @param bits - an array of bit positions that will be treated as if they
|
|
* were contiguous bits, with index 0 being the MSB and index
|
|
* length - 1 being the LSB
|
|
* @param offset to apply to each of the bits indices
|
|
* @return - integer value of the bit array
|
|
*/
|
|
public long getLong(int[] bits, int offset)
|
|
{
|
|
if(bits.length > 64)
|
|
{
|
|
throw new IllegalArgumentException("Overflow - must be 64 bits "
|
|
+ "or less to fit into a primitive long value");
|
|
}
|
|
|
|
long value = 0;
|
|
|
|
for(int index : bits)
|
|
{
|
|
value = Long.rotateLeft(value, 1);
|
|
|
|
if(get(index + offset))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns the bit values between start and end (inclusive) bit indices. If the overall length of the bit sequence
|
|
* is not a multiple of 8 bits, the value is zero padded with least significant bits to make it a multiple of 8.
|
|
* @param start index
|
|
* @param end index
|
|
* @return hexadecimal representation of the bit sequence
|
|
*/
|
|
public String getHex(int start, int end)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for(int x = start; x <= end; x += 4)
|
|
{
|
|
sb.append(getNibbleAsHex(x));
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Returns the integer field formatted as a hex value using zero prefixes to pad the hex character count to fully
|
|
* represent the size (width) of the field.
|
|
* @param field to parse as hex
|
|
* @return hex value.
|
|
*/
|
|
public String getHex(IntField field)
|
|
{
|
|
int width = Math.ceilDiv(field.width(), 4);
|
|
return String.format("%0" + width + "X", getInt(field));
|
|
}
|
|
|
|
/**
|
|
* Returns the long field formatted as a hex value using zero prefixes to pad the hex character count to fully
|
|
* represent the size (width) of the field.
|
|
* @param field to parse as hex
|
|
* @return hex value.
|
|
*/
|
|
public String getHex(LongField field)
|
|
{
|
|
int width = Math.ceilDiv(field.width(), 4);
|
|
return String.format("%0" + width + "X", getLong(field));
|
|
}
|
|
|
|
/**
|
|
* Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less
|
|
* than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in
|
|
* the least significant bits to make up the 8 bit value.
|
|
* @param startIndex of the byte.
|
|
* @return hexadecimal representation of the byte value
|
|
*/
|
|
public String getNibbleAsHex(int startIndex)
|
|
{
|
|
int value = getNibble(startIndex);
|
|
return Integer.toHexString(value).toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* Converts up to 63 bits from the bit array into an integer and then
|
|
* formats the value into hexadecimal, prefixing the value with zeros to
|
|
* provide a total length of digitDisplayCount;
|
|
*
|
|
* @param bits
|
|
* @param digitDisplayCount
|
|
* @return
|
|
*/
|
|
public String getHex(int[] bits, int digitDisplayCount)
|
|
{
|
|
if(bits.length <= 32)
|
|
{
|
|
int value = getInt(bits);
|
|
|
|
return String.format("%0" + digitDisplayCount + "X", value);
|
|
}
|
|
else if(bits.length <= 64)
|
|
{
|
|
long value = getLong(bits);
|
|
|
|
return String.format("%0" + digitDisplayCount + "X", value);
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException("BitSetBuffer.getHex() "
|
|
+ "maximum array length is 63 bits");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the int value represented by the bit range. This method will
|
|
* parse the bits in big endian or little endian format. The start value
|
|
* represents the MSB and the end value represents the LSB of the value.
|
|
*
|
|
* start < end: little endian interpretation
|
|
* end < start: big endian interpretation
|
|
*
|
|
* @param start - MSB of the value
|
|
* @param end - LSB of the value
|
|
* @return - int value of the bit range
|
|
*/
|
|
public int getInt(int start, int end)
|
|
{
|
|
if(FastMath.abs(end - start) > 32)
|
|
{
|
|
throw new IllegalArgumentException("Overflow - must be 32 bits "
|
|
+ "or less to fit into a primitive integer value");
|
|
}
|
|
|
|
int value = 0;
|
|
|
|
if(start < end)
|
|
{
|
|
for(int x = start; x <= end; x++)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(x))
|
|
{
|
|
value++;
|
|
;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int x = end; x >= start; x--)
|
|
{
|
|
value = Integer.rotateLeft(value, 1);
|
|
|
|
if(get(x))
|
|
{
|
|
value++;
|
|
;
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns the twos-complement value of the bits between start and end, inclusive
|
|
* @param start bit position
|
|
* @param end bit position
|
|
* @return twos complement value
|
|
*/
|
|
public int getTwosComplement(int start, int end)
|
|
{
|
|
if(get(start))
|
|
{
|
|
//Negative value - flip and add one
|
|
BinaryMessage fragment = getSubMessage(start, end + 1);
|
|
fragment.flip(0, fragment.size());
|
|
return -(fragment.getInt(0, fragment.size() - 1) + 1);
|
|
}
|
|
else
|
|
{
|
|
//Positive value - return the contents.
|
|
return getInt(start, end);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the long value represented by the bit range. This method will
|
|
* parse the bits in big endian or little endian format. The start value
|
|
* represents the MSB and the end value represents the LSB of the value.
|
|
*
|
|
* start < end: little endian interpretation
|
|
* end < start: big endian interpretation
|
|
*
|
|
* @param start - MSB of the value
|
|
* @param end - LSB of the value
|
|
* @return - long value of the bit range
|
|
*/
|
|
public long getLong(int start, int end)
|
|
{
|
|
if(FastMath.abs(end - start) > 64)
|
|
{
|
|
throw new IllegalArgumentException("Overflow - must be 64 bits "
|
|
+ "or less to fit into a primitive long value");
|
|
}
|
|
|
|
long value = 0;
|
|
|
|
if(start < end)
|
|
{
|
|
for(int x = start; x <= end; x++)
|
|
{
|
|
value = Long.rotateLeft(value, 1);
|
|
|
|
if(get(x))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int x = end; x >= start; x--)
|
|
{
|
|
value = Long.rotateLeft(value, 1);
|
|
|
|
if(get(x))
|
|
{
|
|
value++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Creates a buffer of size=width and fills the buffer with the fill value
|
|
*
|
|
* @param width - size of the buffer
|
|
* @param fill - initial fill value
|
|
* @return - filled buffer
|
|
*/
|
|
public static BinaryMessage getBuffer(int width, long fill)
|
|
{
|
|
BinaryMessage buffer = new BinaryMessage(width);
|
|
|
|
buffer.load(0, width, fill);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* Loads the value into the buffer starting at the offset index, and
|
|
* assuming that the value represents (width) number of bits. The MSB of
|
|
* the value will be located at the offset and the LSB of the value will
|
|
* be located at ( offset + width ).
|
|
*
|
|
* @param offset - starting bit index for the MSB of the value
|
|
* @param width - representative bit width of the value
|
|
* @param value - value to be loaded into the buffer
|
|
*/
|
|
public void load(int offset, int width, long value)
|
|
{
|
|
for(int x = 0; x < width; x++)
|
|
{
|
|
long mask = Long.rotateLeft(1, width - x - 1);
|
|
|
|
if((mask & value) == mask)
|
|
{
|
|
set(offset + x);
|
|
}
|
|
else
|
|
{
|
|
clear(offset + x);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the binary message into this message starting at the offset. Note: this method
|
|
* does not do any length checking for the message being loaded and if that message is longer
|
|
* than this message, it would be loaded outside of the size (length) boundary of this message, but will not
|
|
* produce an error.
|
|
*
|
|
* @param offset into this message to load the binary message
|
|
* @param binaryMessage to load
|
|
*/
|
|
public void load(int offset, BinaryMessage binaryMessage)
|
|
{
|
|
for(int x = 0; x < binaryMessage.size(); x++)
|
|
{
|
|
if(binaryMessage.get(x))
|
|
{
|
|
set(x + offset);
|
|
}
|
|
else
|
|
{
|
|
clear(x + offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates an array of message bit position indexes to support accessing
|
|
* a contiguous field value
|
|
*
|
|
* @param start - starting bit position of the field
|
|
* @param length - field length
|
|
* @return - array of field indexes
|
|
*/
|
|
public static int[] getFieldIndexes(int start, int length, boolean bigEndian)
|
|
{
|
|
int[] checksumIndexes = new int[length];
|
|
|
|
for(int x = 0; x < length; x++)
|
|
{
|
|
if(bigEndian)
|
|
{
|
|
checksumIndexes[length - x - 1] = start + x;
|
|
}
|
|
else
|
|
{
|
|
checksumIndexes[x] = start + x;
|
|
}
|
|
}
|
|
|
|
return checksumIndexes;
|
|
}
|
|
|
|
/**
|
|
* Creates a binary message loaded with the byte array
|
|
* @param bytes to load
|
|
* @return loaded binary message
|
|
*/
|
|
public static BinaryMessage from(byte[] bytes)
|
|
{
|
|
BinaryMessage message = new BinaryMessage(bytes.length * 8);
|
|
|
|
for(int x = 0; x < bytes.length; x++)
|
|
{
|
|
message.setByte(x * 8, bytes[x]);
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Creates a bitsetbuffer loaded from a string of zeros and ones
|
|
*
|
|
* @param message - string containing only zeros and ones
|
|
* @return - loaded buffer
|
|
*/
|
|
public static BinaryMessage load(String message)
|
|
{
|
|
if(!message.matches("[01]*"))
|
|
{
|
|
throw new IllegalArgumentException(
|
|
"Message must contain only zeros and ones");
|
|
}
|
|
|
|
BinaryMessage buffer = new BinaryMessage(message.length());
|
|
|
|
for(int x = 0; x < message.length(); x++)
|
|
{
|
|
if(message.substring(x, x + 1).contentEquals("1"))
|
|
{
|
|
buffer.set(x);
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* Loads the hexadecimal text into a binary message
|
|
* @param hex text to load from
|
|
* @return loaded binary message
|
|
*/
|
|
public static BinaryMessage loadHex(String hex)
|
|
{
|
|
if(!hex.matches("[0-9A-F]*"))
|
|
{
|
|
throw new IllegalArgumentException("Message must contain only 0-9 and A-F hexadecimal characters");
|
|
}
|
|
|
|
BinaryMessage message = new BinaryMessage(hex.length() * 4);
|
|
|
|
for(int x = 0; x < hex.length(); x++)
|
|
{
|
|
String rawHex = hex.substring(x, x + 1);
|
|
message.setInt(Integer.parseInt(rawHex, 16), IntField.length4(x * 4));
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Left rotates the bits between start and end indices, number of places.
|
|
*/
|
|
public void rotateLeft(int places, int startIndex, int endIndex)
|
|
{
|
|
for(int x = 0; x < places; x++)
|
|
{
|
|
rotateLeft(startIndex, endIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Left rotates the bits between start and end and wraps the left-most
|
|
* bit around to the end.
|
|
*/
|
|
public void rotateLeft(int startIndex, int endIndex)
|
|
{
|
|
boolean wrapBit = get(startIndex);
|
|
|
|
for(int x = startIndex; x < endIndex; x++)
|
|
{
|
|
if(get(x + 1))
|
|
{
|
|
set(x);
|
|
}
|
|
else
|
|
{
|
|
clear(x);
|
|
}
|
|
}
|
|
|
|
if(wrapBit)
|
|
{
|
|
set(endIndex);
|
|
}
|
|
else
|
|
{
|
|
clear(endIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Right rotates the bits between start and end indices, number of places.
|
|
*/
|
|
public void rotateRight(int places, int startIndex, int endIndex)
|
|
{
|
|
for(int x = 0; x < places; x++)
|
|
{
|
|
rotateRight(startIndex, endIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Right rotates the bits between start and end and wraps the right-most
|
|
* bit around to the start.
|
|
*/
|
|
public void rotateRight(int startIndex, int endIndex)
|
|
{
|
|
boolean wrapBit = get(endIndex);
|
|
|
|
for(int x = endIndex - 1; x >= startIndex; x--)
|
|
{
|
|
if(get(x))
|
|
{
|
|
set(x + 1);
|
|
}
|
|
else
|
|
{
|
|
clear(x + 1);
|
|
}
|
|
}
|
|
|
|
if(wrapBit)
|
|
{
|
|
set(startIndex);
|
|
}
|
|
else
|
|
{
|
|
clear(startIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs exclusive or of the value against this bitset starting at the
|
|
* offset position using width bits from the value.
|
|
*/
|
|
public void xor(int offset, int width, int value)
|
|
{
|
|
BinaryMessage mask = new BinaryMessage(this.size());
|
|
|
|
mask.load(offset, width, value);
|
|
|
|
this.xor(mask);
|
|
}
|
|
|
|
/**
|
|
* Parse ISO-7 bit characters from the message starting at the offset.
|
|
* @param offset where to start parsing.
|
|
* @param characterCount number of characters to parse
|
|
* @return parsed message.
|
|
*/
|
|
public String parseISO7(int offset, int characterCount)
|
|
{
|
|
byte[] bytes = new byte[characterCount];
|
|
|
|
int index;
|
|
for(int x = 0; x < characterCount; x++)
|
|
{
|
|
index = x * 7 + offset;
|
|
|
|
if(index == 0)
|
|
{
|
|
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
|
bytes[x] = (byte)((bytes[x] >> 1) & 0x7F);
|
|
}
|
|
else
|
|
{
|
|
index--;
|
|
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
|
bytes[x] &= (byte)0x7F; //mask off the high bit since we have to parse it as an 8-bit byte.
|
|
}
|
|
}
|
|
|
|
return new String(bytes);
|
|
}
|
|
|
|
/**
|
|
* Parse ISO-8 bit characters from the message starting at the offset.
|
|
* @param offset where to start parsing.
|
|
* @param characterCount number of characters to parse
|
|
* @return parsed message.
|
|
*/
|
|
public String parseISO8(int offset, int characterCount)
|
|
{
|
|
byte[] bytes = new byte[characterCount];
|
|
|
|
for(int x = 0; x < characterCount; x++)
|
|
{
|
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
|
}
|
|
|
|
return new String(bytes);
|
|
}
|
|
|
|
/**
|
|
* Parse UTF-8 characters from the message starting at the offset.
|
|
* @param offset where to start parsing.
|
|
* @param characterCount number of characters to parse
|
|
* @return parsed message.
|
|
* @throws UnsupportedEncodingException if UTF-8 encoding is not supported.
|
|
*/
|
|
public String parseUTF8(int offset, int characterCount) throws UnsupportedEncodingException
|
|
{
|
|
byte[] bytes = new byte[characterCount];
|
|
|
|
for(int x = 0; x < characterCount; x++)
|
|
{
|
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
|
}
|
|
|
|
return new String(bytes, UTF_8);
|
|
}
|
|
|
|
/**
|
|
* Parse GB2312 Simplified Chinese Characters from the message starting at the offset.
|
|
* @param offset where to start parsing.
|
|
* @param characterCount number of characters to parse
|
|
* @return parsed message.
|
|
* @throws UnsupportedEncodingException if GB2312 encoding is not supported.
|
|
*/
|
|
public String parseGB2312(int offset, int characterCount) throws UnsupportedEncodingException
|
|
{
|
|
int length = characterCount * 2;
|
|
byte[] bytes = new byte[length];
|
|
|
|
for(int x = 0; x < length; x++)
|
|
{
|
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
|
}
|
|
|
|
return new String(bytes, GB2312);
|
|
}
|
|
|
|
/**
|
|
* Parse 16-bit unicode characters from the message starting at the offset.
|
|
* @param offset where to start parsing.
|
|
* @param characterCount number of characters to parse
|
|
* @return parsed message.
|
|
*/
|
|
public String parseUnicode(int offset, int characterCount)
|
|
{
|
|
int length = characterCount * 2;
|
|
byte[] bytes = new byte[length];
|
|
|
|
for(int x = 0; x < length; x++)
|
|
{
|
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
|
}
|
|
|
|
return new String(bytes, Charset.forName("UTF-16"));
|
|
}
|
|
|
|
/**
|
|
* Parse 4-bit Binary Coded Decimal (BCD) characters from the message starting at the offset.
|
|
* @param offset where to start parsing.
|
|
* @param characterCount number of characters to parse
|
|
* @return parsed message.
|
|
*/
|
|
public String parseBCD4(int offset, int characterCount)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
int count = 0;
|
|
|
|
while(count < characterCount)
|
|
{
|
|
int nibble = getNibble(offset);
|
|
//This will parse digits 0-9 and anything higher will parse as hexadecimal A-F
|
|
sb.append(Integer.toHexString(nibble).toUpperCase());
|
|
offset += 4;
|
|
count++;
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
}
|