/*
* ByteBuffer.java - This file is part of the Jakstab project.
*
* Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
* Copyright (C) 2003 The University of Arizona
*
* The original code for this class was taken from "MBEL: The Microsoft
* Bytecode Engineering Library" and modified for use with Jakstab.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
*/
package org.jakstab.loader.pe;
/** This class is used for temporary storage and manipulation of a stream
* of bytes. All of the signature classes parse themselves out of ByteBuffers,
* and the emitter makes large use of them for storing intermediate chunks of
* the final module file. This class is vaguely modelled after java.nio.ByteBuffer,
* but it has some extra functionality that makes it specifically useful to MBEL.
* The concept behind a ByteBuffer is that it is an infinitely large 0-based array
* of bytes. There is a current byte index pointer given by getPosition(). The user can
* also set the current position using setPosition(int). Setting the position to a
* very high value will not grow the underlying array, but writing to it will (i.e.
* space is not allocated until needed). Both reading and writing advance the current
* position pointer by the specified amount. Since one main use of the ByteBuffer
* is as a convenient way to write an array of bytes, the ByteBuffer maintains the
* index of the greatest position that has been written to. Everything up to this point is
* returned when you call toByteArray().
* @author Michael Stepp
*/
public class ByteBuffer{
private static final int SIZE = 1000;
private byte[][] buffer;
private int position;
private int last;
/** Makes a ByteBuffer that is initially intended to be read from, not written to.
* This constructor sets the current position to 0.
* @param init the initial bytes to put into the array, starting from index 0 (if null, and empty ByteBuffer is created)
*/
public ByteBuffer(byte[] init){
if (init==null || init.length==0){
buffer = new byte[1][SIZE];
last = -1;
}else{
buffer = new byte[init.length/SIZE+1][SIZE];
for (int i=0;i<init.length;i++)
buffer[i/SIZE][i%SIZE] = init[i];
last = init.length-1;
}
position = 0;
}
/** Makes a ByteBuffer that is initially intended for writing.
* The current position pointer is set to 0.
* @param capacity the minimum capacity of this ByteBuffer (for convenience)
*/
public ByteBuffer(int capacity){
buffer = new byte[capacity/SIZE+1][SIZE];
position = 0;
last = -1;
}
/** Returns the current maximum capacity of the underlying array.
* This value is not an absolute maximum, because ByteBuffers grow as needed.
*/
public int getCapacity(){
return (buffer.length*SIZE);
}
/** Returns the index of the current position in the ByteBuffer.
* This value will be >=0.
*/
public int getPosition(){
return position;
}
/** Returns the index of the last position written to (initially -1).
*/
public int getLast(){
return last;
}
/** Returns the byte stored in this ByteBuffer at the current position.
* Advances the current position by 1.
* (note: if the current position is past the end of any user-written data, this will return 0)
*/
public byte get(){
if (position>=getCapacity()){
position++;
return 0;
}
byte value = buffer[position/SIZE][position%SIZE];
position++;
return value;
}
/** Returns a 2-byte unsigned little-endian integer, starting at the current position.
* Advances the current position by 2 (see get())
*/
public int getWORD(){
int value = (get()&0xFF);
value |= (get()&0xFF)<<8;
value &= 0xFFFF;
return value;
}
/** Returns a 4-byte unsigned little-endian integer, starting at the current position.
* Advances the current position by 4 (see get())
*/
public long getDWORD(){
long value = (get()&0xFFL);
value |= (get()&0xFFL)<<8;
value |= (get()&0xFFL)<<16;
value |= (get()&0xFFL)<<24;
value &= 0xFFFFFFFFL;
return value;
}
/** Returns the byte stored in this ByteBuffer at the current position.
* This method does not advance the current position.
* (note: if the current position is past the end of any user-written data, this will return 0)
*/
public byte peek(){
if (position>=getCapacity())
return 0;
return buffer[position/SIZE][position%SIZE];
}
/** Sets the current position of this ByteBuffer.
* @param pos the new position (if <0, ignored)
*/
public void setPosition(int pos){
if (pos>=0)
position = pos;
}
/** Decrements the current position by 1 (if >0).
*/
public void back(){
// moves the position back by one
if (position>0)
position--;
}
/** Returns the contents of this ByteBuffer as a single contiguous byte array.
* This returns everything from byte 0 to byte getLast(), inclusive.
*/
public byte[] toByteArray(){
// gets everything up to 'last'
byte[] newarr = new byte[last+1];
for (int i=0;i<newarr.length;i++)
newarr[i] = buffer[i/SIZE][i%SIZE];
return newarr;
}
/** Pads this ByteBuffer with 0s up to the next multiple of 'align'.
* @param align the value to align to (if <=0, ignored)
*/
public void pad(int align){
if (align<=0)
return;
while((position%align)!=0)
put(0);
}
/** Writes the lowest 8 bits of the given int to this ByteBuffer, at the current position.
* Advances the current position by 1.
*/
public void put(int b){
put((byte)(b&0xFF));
}
/** Writes a single byte to this ByteBuffer, at the current position.
* Advances the current position by 1.
*/
public void put(byte data){
if (position>=getCapacity()){
byte[][] newbuf = new byte[position/SIZE+2][];
for (int i=0;i<buffer.length;i++)
newbuf[i] = buffer[i];
for (int i=buffer.length;i<newbuf.length;i++)
newbuf[i] = new byte[SIZE];
buffer = newbuf;
}
buffer[position/SIZE][position%SIZE] = data;
last = Math.max(last,(position++));
}
/** Writes the given byte array to this ByteBuffer, starting at the current position.
* Advances the current position by data.length.
* @param data the bytes to write in this buffer (if null, writes nothing)
*/
public void put(byte[] data){
if (data==null)
return;
if (data.length+position >= getCapacity()){
byte[][] newbuf = new byte[(data.length+position)/SIZE+2][];
for (int i=0;i<buffer.length;i++)
newbuf[i] = buffer[i];
for (int i=buffer.length;i<newbuf.length;i++)
newbuf[i] = new byte[SIZE];
buffer = newbuf;
}
for (int i=0;i<data.length;i++){
buffer[position/SIZE][position%SIZE] = data[i];
last = Math.max(last,(position++));
}
}
/** Writes a signed little-endian int16 to this ByteBuffer, starting at the current position.
* Advances the current position by 2.
*/
public void putINT16(int int16){
put((byte)(int16&0xFF));
put((byte)((int16>>8)&0xFF));
}
/** Writes an unsigned little-endian int16 to this ByteBuffer, starting at the current position.
* Advances the current position by 2.
*/
public void putWORD(int uint16){
put((byte)(uint16&0xFF));
put((byte)((uint16>>8)&0xFF));
}
/** Writes a signed little-endian int32 to this ByteBuffer, starting at the current position.
* Advances the current position by 4.
*/
public void putINT32(int int32){
put((byte)(int32&0xFF));
put((byte)((int32>>8)&0xFF));
put((byte)((int32>>16)&0xFF));
put((byte)((int32>>24)&0xFF));
}
/** Writes an unsigned little-endian int32 to this ByteBuffer, starting at the current position.
* Advances the current position by 4.
*/
public void putDWORD(long uint32){
put((byte)(uint32&0xFF));
put((byte)((uint32>>8)&0xFF));
put((byte)((uint32>>16)&0xFF));
put((byte)((uint32>>24)&0xFF));
}
/** Writes metadata token encoded in a long to this ByteBuffer, starting at the current position.
* Advances the current position by 4.
*/
public void putTOKEN(long token){
putDWORD(token);
}
/** Writes a signed little-endian int64 to this ByteBuffer, starting at the current position.
* Advances the current position by 8.
*/
public void putINT64(long int64){
put((byte)(int64&0xFF));
put((byte)((int64>>8)&0xFF));
put((byte)((int64>>16)&0xFF));
put((byte)((int64>>24)&0xFF));
put((byte)((int64>>32)&0xFF));
put((byte)((int64>>40)&0xFF));
put((byte)((int64>>48)&0xFF));
put((byte)((int64>>56)&0xFF));
}
/** Writes a float32 to this ByteBuffer, starting at the current position.
* Advances the current position by 4.
*/
public void putR4(float r4){
int bits = Float.floatToIntBits(r4);
putINT32(bits);
}
/** Writes a float64 to this ByteBuffer, starting at the current position.
* Advances the current position by 8.
*/
public void putR8(double r8){
long bits = Double.doubleToLongBits(r8);
putINT64(bits);
}
/** Concatenates the given ByteBuffer to the end of this ByteBuffer,
* starting from 0 and ending at buf.getLast(), inclusive.
* @param buf the ByteBuffer to concatenate to the end of this one (if null, ignored)
*/
public void concat(ByteBuffer buf){
if (buf==null)
return;
for (int i=0;i<=buf.last;i++){
put(buf.buffer[i/SIZE][i%SIZE]);
}
}
}