/*******************************************************************************. * Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal and others * * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Matthew Khouzam - Initial Design and implementation + overhaul * Francis Giraldeau - Initial API and implementation * Philippe Proulx - Some refinement and optimization * Etienne Bergeron <Etienne.Bergeron@gmail.com> - fix zero size read + cleanup *******************************************************************************/ package org.eclipse.tracecompass.ctf.core.event.io; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.tracecompass.ctf.core.CTFException; /** * <b><u>BitBuffer</u></b> * <p> * A bitwise buffer capable of accessing fields with bit offsets. */ public final class BitBuffer { // ------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------ /* default bit width */ private static final int BIT_CHAR = Byte.SIZE; // yum private static final int BYTE_MASK = (1 << BIT_CHAR) - 1; private static final int BIT_SHORT = Short.SIZE; private static final int SHORT_MASK = (1 << BIT_SHORT) - 1; private static final int BIT_INT = Integer.SIZE; private static final long INT_MASK = (1L << BIT_INT) - 1; private static final int BIT_LONG = Long.SIZE; // ------------------------------------------------------------------------ // Attributes // ------------------------------------------------------------------------ private final @NonNull ByteBuffer fBuffer; private final long fBitCapacity; /** * Bit-buffer's position, maximum value = Integer.MAX_VALUE * 8 */ private long fPosition; private ByteOrder fByteOrder; // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Default constructor, makes a big-endian buffer */ public BitBuffer() { this(ByteBuffer.allocateDirect(0), ByteOrder.BIG_ENDIAN); } /** * Constructor, makes a big-endian buffer * * @param buf * the bytebuffer to read */ public BitBuffer(@NonNull ByteBuffer buf) { this(buf, ByteOrder.BIG_ENDIAN); } /** * Constructor that is fully parameterizable * * @param buf * the buffer to read * @param order * the byte order (big-endian, little-endian, network?) */ public BitBuffer(@NonNull ByteBuffer buf, ByteOrder order) { fBuffer = buf; setByteOrder(order); resetPosition(); fBitCapacity = (long) fBuffer.capacity() * BIT_CHAR; } private void resetPosition() { fPosition = 0; } // ------------------------------------------------------------------------ // 'Get' operations on buffer // ------------------------------------------------------------------------ /** * Relative <i>get</i> method for reading 32-bit integer. * * Reads next four bytes from the current bit position according to current * byte order. * * @return The int value (signed) read from the buffer * @throws CTFException * An error occurred reading the long. This exception can be * raised if the buffer tries to read out of bounds */ public int getInt() throws CTFException { return getInt(BIT_INT, true); } /** * Relative <i>get</i> method for reading 64-bit integer. * * Reads next eight bytes from the current bit position according to current * byte order. * * @return The long value (signed) read from the buffer * @throws CTFException * An error occurred reading the long. This exception can be * raised if the buffer tries to read out of bounds */ public long getLong() throws CTFException { return get(BIT_LONG, true); } /** * Relative <i>get</i> method for reading long of <i>length</i> bits. * * Reads <i>length</i> bits starting at the current position. The result is * signed extended if <i>signed</i> is true. The current position is * increased of <i>length</i> bits. * * @param length * The length in bits of this integer * @param signed * The sign extended flag * @return The long value read from the buffer * @throws CTFException * An error occurred reading the data. If more than 64 bits at a * time are read, or the buffer is read beyond its end, this * exception will be raised. */ public long get(int length, boolean signed) throws CTFException { if (length > BIT_LONG) { throw new CTFException("Cannot read a long longer than 64 bits. Rquested: " + length); //$NON-NLS-1$ } if (length > BIT_INT) { final int highShift = length - BIT_INT; long a = getInt(); long b = getInt(highShift, false); long retVal; /* Cast the signed-extended int into a unsigned int. */ a &= INT_MASK; b &= (1L << highShift) - 1L; retVal = (fByteOrder == ByteOrder.BIG_ENDIAN) ? ((a << highShift) | b) : ((b << BIT_INT) | a); /* sign extend */ if (signed) { int signExtendBits = BIT_LONG - length; retVal = (retVal << signExtendBits) >> signExtendBits; } return retVal; } long retVal = getInt(length, signed); return (signed ? retVal : (retVal & INT_MASK)); } /** * Relative bulk <i>get</i> method. * * <p> * This method transfers <strong>bytes</strong> from this buffer into the * given destination array. This method currently only supports reads * aligned to 8 bytes. It is up to the developer to shift the bits in * post-processing to do unaligned reads. * * @param dst * the bytes to write to * @throws BufferUnderflowException * - If there are fewer than length bytes remaining in this * buffer */ public void get(byte @NonNull [] dst) { fBuffer.position((int) (fPosition / BIT_CHAR)); fBuffer.get(dst); fPosition += dst.length * BIT_CHAR; } /** * Relative <i>get</i> method for reading integer of <i>length</i> bits. * * Reads <i>length</i> bits starting at the current position. The result is * signed extended if <i>signed</i> is true. The current position is * increased of <i>length</i> bits. * * @param length * The length in bits of this integer * @param signed * The sign extended flag * @return The int value read from the buffer * @throws CTFException * An error occurred reading the data. When the buffer is read * beyond its end, this exception will be raised. */ private int getInt(int length, boolean signed) throws CTFException { /* Nothing to read. */ if (length == 0) { return 0; } /* Validate that the buffer has enough bits. */ if (!canRead(length)) { throw new CTFException("Cannot read the integer, " + //$NON-NLS-1$ "the buffer does not have enough remaining space. " + //$NON-NLS-1$ "Requested:" + length + " Available:" + (fBitCapacity - fPosition)); //$NON-NLS-1$ //$NON-NLS-2$ } /* Get the value from the byte buffer. */ int val = 0; boolean gotIt = false; /* * Try a fast read when the position is byte-aligned by using * java.nio.ByteBuffer's native methods */ /* * A faster alignment detection as the compiler cannot guaranty that pos * is always positive. */ if ((fPosition & (BitBuffer.BIT_CHAR - 1)) == 0) { switch (length) { case BitBuffer.BIT_CHAR: // Byte val = fBuffer.get((int) (fPosition / BIT_CHAR)); if (!signed) { val = val & BYTE_MASK; } gotIt = true; break; case BitBuffer.BIT_SHORT: // Word val = fBuffer.getShort((int) (fPosition / BIT_CHAR)); if (!signed) { val = val & SHORT_MASK; } gotIt = true; break; case BitBuffer.BIT_INT: // Double word val = fBuffer.getInt((int) (fPosition / BIT_CHAR)); gotIt = true; break; default: break; } } /* When not byte-aligned, fall-back to a general decoder. */ if (!gotIt) { // Nothing read yet: use longer methods if (fByteOrder == ByteOrder.LITTLE_ENDIAN) { val = getIntLE(fPosition, length, signed); } else { val = getIntBE(fPosition, length, signed); } } fPosition += length; return val; } private int getIntBE(long index, int length, boolean signed) { if ((length <= 0) || (length > BIT_INT)) { throw new IllegalArgumentException("Length must be between 1-32 bits"); //$NON-NLS-1$ } long end = index + length; int startByte = (int) (index / BIT_CHAR); int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR); int currByte, lshift, cshift, mask, cmask, cache; int value = 0; currByte = startByte; cache = fBuffer.get(currByte) & BYTE_MASK; boolean isNeg = (cache & (1 << (BIT_CHAR - (index % BIT_CHAR) - 1))) != 0; if (signed && isNeg) { value = ~0; } if (startByte == (endByte - 1)) { cmask = cache >>> ((BIT_CHAR - (end % BIT_CHAR)) % BIT_CHAR); if (((length) % BIT_CHAR) > 0) { mask = ~((~0) << length); cmask &= mask; } value <<= length; value |= cmask; return value; } cshift = (int) (index % BIT_CHAR); if (cshift > 0) { mask = ~((~0) << (BIT_CHAR - cshift)); cmask = cache & mask; lshift = BIT_CHAR - cshift; value <<= lshift; value |= cmask; currByte++; } for (; currByte < (endByte - 1); currByte++) { value <<= BIT_CHAR; value |= fBuffer.get(currByte) & BYTE_MASK; } lshift = (int) (end % BIT_CHAR); if (lshift > 0) { mask = ~((~0) << lshift); cmask = fBuffer.get(currByte) & BYTE_MASK; cmask >>>= BIT_CHAR - lshift; cmask &= mask; value <<= lshift; value |= cmask; } else { value <<= BIT_CHAR; value |= fBuffer.get(currByte) & BYTE_MASK; } return value; } private int getIntLE(long index, int length, boolean signed) { if ((length <= 0) || (length > BIT_INT)) { throw new IllegalArgumentException("Length must be between 1-32 bits"); //$NON-NLS-1$ } long end = index + length; int startByte = (int) (index / BIT_CHAR); int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR); int currByte, lshift, cshift, mask, cmask, cache, mod; int value = 0; currByte = endByte - 1; cache = fBuffer.get(currByte) & BYTE_MASK; mod = (int) (end % BIT_CHAR); lshift = (mod > 0) ? mod : BIT_CHAR; boolean isNeg = (cache & (1 << (lshift - 1))) != 0; if (signed && isNeg) { value = ~0; } if (startByte == (endByte - 1)) { cmask = cache >>> (index % BIT_CHAR); if (((length) % BIT_CHAR) > 0) { mask = ~((~0) << length); cmask &= mask; } value <<= length; value |= cmask; return value; } cshift = (int) (end % BIT_CHAR); if (cshift > 0) { mask = ~((~0) << cshift); cmask = cache & mask; value <<= cshift; value |= cmask; currByte--; } for (; currByte >= (startByte + 1); currByte--) { value <<= BIT_CHAR; value |= fBuffer.get(currByte) & BYTE_MASK; } lshift = (int) (index % BIT_CHAR); if (lshift > 0) { mask = ~((~0) << (BIT_CHAR - lshift)); cmask = fBuffer.get(currByte) & BYTE_MASK; cmask >>>= lshift; cmask &= mask; value <<= (BIT_CHAR - lshift); value |= cmask; } else { value <<= BIT_CHAR; value |= fBuffer.get(currByte) & BYTE_MASK; } return value; } // ------------------------------------------------------------------------ // 'Put' operations on buffer // ------------------------------------------------------------------------ /** * Relative <i>put</i> method to write signed 32-bit integer. * * Write four bytes starting from current bit position in the buffer * according to the current byte order. The current position is increased of * <i>length</i> bits. * * @param value * The int value to write * @throws CTFException * An error occurred writing the data. If the buffer is written * beyond its end, this exception will be raised. */ public void putInt(int value) throws CTFException { putInt(BIT_INT, value); } /** * Relative <i>put</i> method to write <i>length</i> bits integer. * * Writes <i>length</i> lower-order bits from the provided <i>value</i>, * starting from current bit position in the buffer. Sequential bytes are * written according to the current byte order. The sign bit is carried to * the MSB if signed is true. The sign bit is included in <i>length</i>. The * current position is increased of <i>length</i>. * * @param length * The number of bits to write * @param value * The value to write * @throws CTFException * An error occurred writing the data. If the buffer is written * beyond its end, this exception will be raised. */ public void putInt(int length, int value) throws CTFException { final long curPos = fPosition; if (!canRead(length)) { throw new CTFException("Cannot write to bitbuffer, " //$NON-NLS-1$ + "insufficient space. Requested: " + length); //$NON-NLS-1$ } if (length == 0) { return; } if (fByteOrder == ByteOrder.LITTLE_ENDIAN) { putIntLE(curPos, length, value); } else { putIntBE(curPos, length, value); } fPosition += length; } private void putIntBE(long index, int length, int value) { if ((length <= 0) || (length > BIT_INT)) { throw new IllegalArgumentException("Length must be between 1-32 bits"); //$NON-NLS-1$ } long end = index + length; int startByte = (int) (index / BIT_CHAR); int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR); int currByte, lshift, cshift, mask, cmask; int correctedValue = value; /* * mask v high bits. Works for unsigned and two complement signed * numbers which value do not overflow on length bits. */ if (length < BIT_INT) { correctedValue &= ~(~0 << length); } /* sub byte */ if (startByte == (endByte - 1)) { lshift = (int) ((BIT_CHAR - (end % BIT_CHAR)) % BIT_CHAR); mask = ~((~0) << lshift); if ((index % BIT_CHAR) > 0) { mask |= (~(0)) << (BIT_CHAR - (index % BIT_CHAR)); } cmask = correctedValue << lshift; /* * low bits are cleared because of left-shift and high bits are * already cleared */ cmask &= ~mask; int b = fBuffer.get(startByte) & BYTE_MASK; fBuffer.put(startByte, (byte) ((b & mask) | cmask)); return; } /* head byte contains MSB */ currByte = endByte - 1; cshift = (int) (end % BIT_CHAR); if (cshift > 0) { lshift = BIT_CHAR - cshift; mask = ~((~0) << lshift); cmask = correctedValue << lshift; cmask &= ~mask; int b = fBuffer.get(currByte) & BYTE_MASK; fBuffer.put(currByte, (byte) ((b & mask) | cmask)); correctedValue >>>= cshift; currByte--; } /* middle byte(s) */ for (; currByte >= (startByte + 1); currByte--) { fBuffer.put(currByte, (byte) correctedValue); correctedValue >>>= BIT_CHAR; } /* end byte contains LSB */ if ((index % BIT_CHAR) > 0) { mask = (~0) << (BIT_CHAR - (index % BIT_CHAR)); cmask = correctedValue & ~mask; int b = fBuffer.get(currByte) & BYTE_MASK; fBuffer.put(currByte, (byte) ((b & mask) | cmask)); } else { fBuffer.put(currByte, (byte) correctedValue); } } private void putIntLE(long index, int length, int value) { if ((length <= 0) || (length > BIT_INT)) { throw new IllegalArgumentException("Length must be between 1-32 bits"); //$NON-NLS-1$ } long end = index + length; int startByte = (int) (index / BIT_CHAR); int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR); int currByte, lshift, cshift, mask, cmask; int correctedValue = value; /* * mask v high bits. Works for unsigned and two complement signed * numbers which value do not overflow on length bits. */ if (length < BIT_INT) { correctedValue &= ~(~0 << length); } /* sub byte */ if (startByte == (endByte - 1)) { lshift = (int) (index % BIT_CHAR); mask = ~((~0) << lshift); if ((end % BIT_CHAR) > 0) { mask |= (~(0)) << (end % BIT_CHAR); } cmask = correctedValue << lshift; /* * low bits are cleared because of left-shift and high bits are * already cleared */ cmask &= ~mask; int b = fBuffer.get(startByte) & BYTE_MASK; fBuffer.put(startByte, (byte) ((b & mask) | cmask)); return; } /* head byte */ currByte = startByte; cshift = (int) (index % BIT_CHAR); if (cshift > 0) { mask = ~((~0) << cshift); cmask = correctedValue << cshift; cmask &= ~mask; int b = fBuffer.get(currByte) & BYTE_MASK; fBuffer.put(currByte, (byte) ((b & mask) | cmask)); correctedValue >>>= BIT_CHAR - cshift; currByte++; } /* middle byte(s) */ for (; currByte < (endByte - 1); currByte++) { fBuffer.put(currByte, (byte) correctedValue); correctedValue >>>= BIT_CHAR; } /* end byte */ if ((end % BIT_CHAR) > 0) { mask = (~0) << (end % BIT_CHAR); cmask = correctedValue & ~mask; int b = fBuffer.get(currByte) & BYTE_MASK; fBuffer.put(currByte, (byte) ((b & mask) | cmask)); } else { fBuffer.put(currByte, (byte) correctedValue); } } // ------------------------------------------------------------------------ // Buffer attributes handling // ------------------------------------------------------------------------ /** * Can this buffer be read for thus amount of bits? * * @param length * the length in bits to read * @return does the buffer have enough room to read the next "length" */ public boolean canRead(int length) { return ((fPosition + length) <= fBitCapacity); } /** * Sets the order of the buffer. * * @param order * The order of the buffer. */ public void setByteOrder(ByteOrder order) { if (!order.equals(fByteOrder)) { fByteOrder = order; fBuffer.order(order); } } /** * Sets the order of the buffer. * * @return The order of the buffer. */ public ByteOrder getByteOrder() { return fByteOrder; } /** * Sets the position in the buffer. * * @param newPosition * The new position of the buffer. * @throws CTFException * Thrown on out of bounds exceptions */ public void position(long newPosition) throws CTFException { if (newPosition > fBitCapacity) { throw new CTFException("Out of bounds exception on a position move, attempting to access position: " + newPosition); //$NON-NLS-1$ } fPosition = newPosition; } /** * * Sets the position in the buffer. * * @return order The position of the buffer. */ public long position() { return fPosition; } /** * Gets the byte buffer * * @return The byte buffer */ public ByteBuffer getByteBuffer() { return fBuffer; } /** * Resets the bitbuffer. */ public void clear() { resetPosition(); fBuffer.clear(); } }