/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.net;
import org.apache.log4j.Logger;
import org.jnode.driver.Device;
/**
* A SocketBuffer is container of a network packet. It enables efficient storage
* even when various network layers prefix and/or postfix header/footers. It
* also contains other information of a network packet, such as the headers of
* the various network layers.
*
* All numbers larger then a single byte written into this class are converted
* to network byte order.
*
* All numbers larger then a single byte read from this class are converted from
* network byte order.
*
* @author epr
*/
public class SocketBuffer {
private static final int INITIAL_SIZE = 256;
/** My logger */
private static final Logger log = Logger.getLogger(SocketBuffer.class);
/** Actual data */
private byte[] data;
/** Size of the buffer that is in use */
private int size;
/** Start offset in data */
private int start;
/** Next buffer, that is concatenated with this one */
private SocketBuffer next;
/** The network device who will be sending, or has received this buffer */
private Device device;
/** Identifying type of the packettype */
private int protocolID;
/** Link layer header (if any) */
private LinkLayerHeader linkLayerHeader;
/** Network layer header (if any) */
private NetworkLayerHeader networkLayerHeader;
/** Transport layer header (if any) */
private TransportLayerHeader transportLayerHeader;
/**
* Create a new instance
*/
public SocketBuffer() {
}
/**
* Create a new instance with a buffer of a given capacity
*/
public SocketBuffer(int initialCapacity) {
this(initialCapacity, 0);
}
/**
* Create a new instance with a buffer of a given capacity
*/
public SocketBuffer(int initialCapacity, int initialStart) {
this.data = new byte[initialCapacity];
}
/**
* Create a clone of the data of src. Other attributes are not cloned!.
*
* @param src
*/
public SocketBuffer(SocketBuffer src) {
this.start = 0;
this.size = src.getSize();
this.data = src.toByteArray();
this.next = null;
}
/**
* Create a new instance, using the given byte array as data. No copy of the
* data is made!
*
* @param data
* @param offset
* @param length
*/
public SocketBuffer(byte[] data, int offset, int length) {
this.data = new byte[data.length];
System.arraycopy(data, 0, this.data, 0, data.length);
this.start = offset;
this.size = length;
testBuffer();
}
/**
* Gets the network device who will be sending, or has received this buffer
*/
public Device getDevice() {
return device;
}
/**
* Sets the network device who will be sending, or has received this buffer
*
* @param device
*/
public void setDevice(Device device) {
this.device = device;
}
/**
* Gets the identifying type of the packettype.
*/
public int getProtocolID() {
return protocolID;
}
/**
* Sets the identifying type of the packettype.
*
* @param i
*/
public void setProtocolID(int i) {
protocolID = i;
}
/**
* Clear this buffer, so it can be used for another purpose
*
*/
public void clear() {
size = 0;
start = 0;
next = null;
protocolID = 0;
linkLayerHeader = null;
networkLayerHeader = null;
transportLayerHeader = null;
device = null;
// preserve data (if set), we can used it again
}
/**
* Insert a given number of bytes to the front of the buffer. The inserted
* bytes are cleaned with a value of <code>(byte)0</code>.
*
* @param count
*/
public void insert(int count) {
if (start >= count) {
start -= count;
size += count;
} else {
setSize(size + count);
for (int i = size - 1; i >= count; i--) {
data[start + i] = data[start + i - count];
}
}
for (int i = 0; i < count; i++) {
data[start + i] = 0;
}
testBuffer();
}
/**
* Remove a given number of bytes from the front of the buffer
*
* @param count
*/
public void pull(int count) {
if (count > size) {
if (next != null) {
// Pull a bit of myself and the rest of next
count -= size;
start = size;
size = 0;
next.pull(count);
} else {
throw new IllegalArgumentException("Cannot pull " + count + " bytes (" + start +
',' + size + ')');
}
} else {
start += count;
size -= count;
}
testBuffer();
}
/**
* Undo a pull action. This method is different from insert, as this method
* can only unpull that what has been removed by an earlier call to pull,
* insert will actually make new room at the head on the buffer.
*
* @param count
* @throws IllegalArgumentException It is not possible to unpull count
* bytes.
*/
public void unpull(int count) {
if (start >= count) {
start -= count;
size += count;
} else {
if (next != null) {
// Unpull most of next and that what I can from me
final int remaining = (count - start);
size += start;
start = 0;
next.unpull(remaining);
} else {
throw new IllegalArgumentException("Cannot unpull " + count + " bytes (" + start +
',' + size + ')');
}
}
testBuffer();
}
/**
* Remove data from the tail of the buffer, until size <= length. If the
* current size < length, nothing happens.
*
* @param length
*/
public void trim(int length) {
if (length < size) {
// Cut the tail of myself and remove any next buffer
size = length;
next = null;
} else if (length == size) {
// Remove any next buffer
next = null;
} else {
// Length > size
if (next != null) {
next.trim(length - size);
}
}
}
/**
* Insert a given number of bytes to the back of the buffer
*
* @param count
*/
public void append(int count) {
if (next != null) {
next.append(count);
} else {
setSize(size + count);
}
testBuffer();
}
/**
* Insert a given number of bytes to the front of the buffer
*
* @param src
* @param srcOffset
* @param length
*/
public void append(byte[] src, int srcOffset, int length) {
if (next != null) {
next.append(src, srcOffset, length);
} else {
final int dstOffset = start + size;
setSize(size + length);
System.arraycopy(src, srcOffset, data, dstOffset, length);
}
testBuffer();
}
/**
* Append a complete buffer to the end of this buffer.
*
* @param skbuf
*/
public void append(SocketBuffer skbuf) {
if (next != null) {
next.append(skbuf);
} else {
next = skbuf;
}
testBuffer();
}
/**
* Append a buffer to the end of this buffer starting at the given offset in
* the appended buffer.
*
* @param skbufOffset
* @param skbuf
*/
public void append(int skbufOffset, SocketBuffer skbuf) {
final byte[] src = skbuf.toByteArray();
append(src, skbufOffset, src.length - skbufOffset);
}
/**
* Append a buffer to the end of this buffer with only a given amount of
* bytes. The given buffer must not contain a next buffer and must have a
* size greater or equal to length
*
* @param skbuf
*/
public void append(SocketBuffer skbuf, int length) throws IllegalArgumentException {
if (length == 0) {
return;
}
if (next != null) {
next.append(skbuf, length);
} else {
if (length < 0) {
throw new IllegalArgumentException("Length < 0");
}
if (skbuf.next != null) {
throw new IllegalArgumentException("skbuf.next != null");
}
if (skbuf.size < length) {
throw new IllegalArgumentException("skbuf.size < length");
}
next = skbuf;
skbuf.size = length;
}
testBuffer();
}
/**
* Gets a byte in the buffer
*
* @param index
*/
public int get(int index) {
if (index >= size) {
if (next != null) {
return next.get(index - size);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else {
return data[start + index] & 0xFF;
}
}
/**
* Gets a 16-bit word from the buffer
*
* @param index
*/
public int get16(int index) {
if (index >= size) {
// Index is beyond my data
if (next != null) {
return next.get16(index - size);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else if (index + 1 < size) {
// Both bytes are within my data
final int b0 = data[start + index + 0] & 0xFF;
final int b1 = data[start + index + 1] & 0xFF;
return (b0 << 8) | b1;
} else {
// First byte is within my data, second is not
final int b0 = get(index + 0);
final int b1 = get(index + 1);
return (b0 << 8) | b1;
}
}
/**
* Gets a 32-bit word from the buffer
*
* @param index
*/
public int get32(int index) {
if (index >= size) {
// Index is beyond my data
if (next != null) {
return next.get32(index - size);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else if (index + 3 < size) {
// Both bytes are within my data
final int b0 = data[start + index + 0] & 0xFF;
final int b1 = data[start + index + 1] & 0xFF;
final int b2 = data[start + index + 2] & 0xFF;
final int b3 = data[start + index + 3] & 0xFF;
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
} else {
// First byte is within my data, second is not
final int b0 = get(index + 0);
final int b1 = get(index + 1);
final int b2 = get(index + 1);
final int b3 = get(index + 1);
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
}
/**
* Sets a byte in the buffer
*
* @param index
*/
public void set(int index, int value) {
if (index >= size) {
if (next != null) {
next.set(index - size, value);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else {
data[start + index] = (byte) value;
}
}
/**
* Sets a 16-bit word in the buffer
*
* @param index
*/
public void set16(int index, int value) {
if (index >= size) {
// Index is beyond my data
if (next != null) {
next.set16(index - size, value);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else if (index + 1 < size) {
// Both bytes are within my data
data[start + index + 0] = (byte) ((value >> 8) & 0xFF);
data[start + index + 1] = (byte) (value & 0xFF);
} else {
// First byte is within my data, second is not
set(index + 0, ((value >> 8) & 0xFF));
set(index + 1, (value & 0xFF));
}
}
/**
* Sets a 32-bit word in the buffer
*
* @param index
*/
public void set32(int index, int value) {
if (index >= size) {
// Index is beyond my data
if (next != null) {
next.set32(index - size, value);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else if (index + 3 < size) {
// All bytes are within my data
data[start + index + 0] = (byte) ((value >> 24) & 0xFF);
data[start + index + 1] = (byte) ((value >> 16) & 0xFF);
data[start + index + 2] = (byte) ((value >> 8) & 0xFF);
data[start + index + 3] = (byte) (value & 0xFF);
} else {
// First byte is within my data, last is not
set(index + 0, ((value >> 24) & 0xFF));
set(index + 1, ((value >> 16) & 0xFF));
set(index + 2, ((value >> 8) & 0xFF));
set(index + 3, (value & 0xFF));
}
}
/**
* Sets a byte-array in the buffer
*
* @param index
*/
public void set(int index, byte[] src, int srcOffset, int length) {
if (index >= size) {
// Index is beyond my data
if (next != null) {
next.set(index - size, src, srcOffset, length);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else if (index + length <= size) {
// All bytes are within my data
System.arraycopy(src, srcOffset, data, start + index, length);
} else {
// First byte is within my data, last is not
if (next != null) {
final int myLength = size - index;
System.arraycopy(src, srcOffset, data, start + index, myLength);
next.set(index - myLength, src, srcOffset + myLength, length - myLength);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
}
}
/**
* Gets a byte-array in the buffer
*
* @param index
*/
public void get(byte[] dst, int dstOffset, int index, int length) {
try {
if (index >= size) {
// Index is beyond my data
if (next != null) {
next.get(dst, dstOffset, index - size, length);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
} else if (index + length <= size) {
// All bytes are within my data
System.arraycopy(data, start + index, dst, dstOffset, length);
} else {
// First byte is within my data, last is not
if (next != null) {
final int myLength = size - index;
System.arraycopy(data, start + index, dst, dstOffset, myLength);
next.get(dst, dstOffset + myLength, Math.max(0, index - myLength), length -
myLength);
} else {
throw new IndexOutOfBoundsException("at index " + index);
}
}
} catch (IndexOutOfBoundsException ex) {
log.debug("get(dst, " + dstOffset + ", " + index + ", " + length + ") start=" + start +
", size=" + size);
throw new IndexOutOfBoundsException(ex.getMessage());
}
}
/**
* Gets the contents of this buffer as a single bytearray. Please note that
* on concatenated buffers, this can be an expensive function!
*
* @return The contents of this buffer
*/
public byte[] toByteArray() {
final byte[] result = new byte[getSize()];
int ofs = 0;
SocketBuffer skbuf = this;
do {
System.arraycopy(skbuf.data, skbuf.start, result, ofs, skbuf.size);
ofs += skbuf.size;
skbuf = skbuf.next;
} while (skbuf != null);
return result;
}
/**
* Gets the used number of bytes in the buffer (and any appended buffers)
*/
public int getSize() {
if (next != null) {
return size + next.getSize();
} else {
return size;
}
}
/**
* Set the new buffer size
* @param newSize
*/
private void setSize(int newSize) {
if (data == null) {
if (newSize > 0) {
// There is no buffer, create one
data = new byte[alignSize(Math.max(newSize, INITIAL_SIZE))];
size = newSize;
}
} else if (data.length < start + newSize) {
// Enlarge the buffer
final byte[] newData = new byte[alignSize(start + newSize)];
System.arraycopy(data, start, newData, start, size);
this.data = newData;
this.size = newSize;
} else {
// The buffer is large enough, update size
this.size = newSize;
}
testBuffer();
}
private final int alignSize(int size) {
return (size + (INITIAL_SIZE - 1)) & ~(INITIAL_SIZE - 1);
}
/**
* Test the parameters of this buffer for illegal combinations.
*/
private final void testBuffer() {
if (data == null) {
if (size != 0) {
throw new RuntimeException("size(" + size + ") must be 0 when data is null");
}
if (start != 0) {
throw new RuntimeException("start(" + start + ") must be 0 when data is null");
}
} else {
if (size < 0) {
throw new RuntimeException("size(" + size + ") must be >= 0");
}
if (start < 0) {
throw new RuntimeException("start(" + start + ") must be >= 0");
}
if (start + size > data.length) {
throw new RuntimeException("start(" + start + ")+size(" + size +
") must be <= data.length(" + data.length + ')');
}
}
}
/**
* Gets the header of the linklayer
*/
public LinkLayerHeader getLinkLayerHeader() {
if (linkLayerHeader != null) {
return linkLayerHeader;
} else if (next != null) {
return next.getLinkLayerHeader();
} else {
return null;
}
}
/**
* Gets the header of the networklayer
*/
public NetworkLayerHeader getNetworkLayerHeader() {
if (networkLayerHeader != null) {
return networkLayerHeader;
} else if (next != null) {
return next.getNetworkLayerHeader();
} else {
return null;
}
}
/**
* Gets the header of the transportlayer
*/
public TransportLayerHeader getTransportLayerHeader() {
if (transportLayerHeader != null) {
return transportLayerHeader;
} else if (next != null) {
return next.getTransportLayerHeader();
} else {
return null;
}
}
/**
* Sets the header of the linklayer
* @param header
*/
public void setLinkLayerHeader(LinkLayerHeader header) {
linkLayerHeader = header;
}
/**
* Sets the header of the networklayer
* @param header
*/
public void setNetworkLayerHeader(NetworkLayerHeader header) {
networkLayerHeader = header;
}
/**
* Sets the header of the transportlayer
* @param header
*/
public void setTransportLayerHeader(TransportLayerHeader header) {
transportLayerHeader = header;
}
}