org.jnode.net.SocketBuffer.java Source code

Java tutorial

Introduction

Here is the source code for org.jnode.net.SocketBuffer.java

Source

/*
 * $Id: SocketBuffer.java 4213 2008-06-08 02:02:10Z crawley $
 *
 * JNode.org
 * Copyright (C) 2003-2006 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.
 */
/*
 * 2008 - 2010 (c) Waterford Institute of Technology
 *         TSSG, EU ICT 4WARD
 *
 * 2010 (c) Pouzin Society
 *   - Forked from EU ICT 4WARD Open Source Distribution.
 *   - Organisation Strings updated to reflect fork.
 *
 *
 * Author        : pphelan(at)tssg.org
 *
 * Modifications : Changes to port JNode code base to OSGi platform.
 *                 - log4j -> apache commons.
 */
package org.jnode.net;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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 Log log = LogFactory.getLog(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;
    }

}