package com.netifera.platform.net.packets;
import java.nio.ByteBuffer;
import com.netifera.platform.net.packets.util.PacketChecksum;
/**
*
*
*
*/
public abstract class AbstractPacket implements IPacketHeader {
/*
* Used in persist() to invalidate headerBuffer. Easier to diagnose if headerBuffer is
* accessed after persist() is called.
*/
private final static ByteBuffer nullBuffer = ByteBuffer.allocate(0).asReadOnlyBuffer();
/* The next nested header, or null if this is the last header in the chain */
private IPacketHeader nextHeader;
private IPacketHeader previousHeader;
/* The buffer this header was unpacked from or packed into. null if this header is not yet packed */
private ByteBuffer headerBuffer;
protected AbstractPacket() {}
protected AbstractPacket(IPacketHeader next) {
this.nextHeader = next;
}
protected AbstractPacket(IPacketHeader prev, IPacketHeader next) {
this.previousHeader = prev;
this.nextHeader = next;
}
/*
* (non-Javadoc)
* @see com.netifera.platform.net.packets.IPacketHeader#getNextHeader()
*/
final public IPacketHeader getNextHeader() {
return nextHeader;
}
final public IPacketHeader getPreviousHeader() {
return previousHeader;
}
final public int getNextProtocol() {
return nextProtocol();
}
/**
* Subclasses which encapsulate other protocol headers must override this
* method to return an appropriate protocol constant for the next nested header.
*
* The default implementation returns -1 which indicates that this header does
* not encapsulate another protocol.
*
* @return The protocol constant for the next nested header or -1 if this header
* does not encapsulate another protocol.
*/
protected int nextProtocol() {
return -1;
}
/*
* (non-Javadoc)
* @see com.netifera.platform.net.packets.IPacketHeader#getHeaderLength()
*/
final public int getHeaderLength() {
return headerLength();
}
/**
* Return the minimum size for this type of header. This method must be implemented by
* subclasses to return the minimum size in bytes of the protocol header.
* @return Minimum size in bytes of the protocol header.
*/
protected abstract int minimumHeaderLength();
/**
* Default implementation simply delegates to minimumHeaderLength() which is the same
* value for fixed sized headers. Header implementations with variable length should override
* this method and dynamically calculate the length of the header based on field values.
* @return
*/
protected int headerLength() {
return minimumHeaderLength();
}
final protected int remaining() {
return headerBuffer.remaining();
}
/**
*
* @return
*/
final protected ByteBuffer headerBufferSlice() {
return headerBuffer.slice();
}
final protected ByteBuffer headerBufferSlice(int length) {
ByteBuffer b = headerBuffer.duplicate();
b.position(length);
return b.slice();
}
public int getLength() {
if(nextHeader != null) {
return nextHeader.getLength() + getHeaderLength();
}
return getHeaderLength();
}
// PACKING
final public boolean pack(ByteBuffer buffer) {
if(buffer.remaining() < getHeaderLength()) {
return false;
}
headerBuffer = buffer;
populateGeneratedFields();
packHeader();
/*
* We must call this first because checksums may be calculated over these bytes
*/
if(nextHeader != null) {
/*
* The header for 'this' has already been packed, so the headerBuffer 'position' points to the beginning
* of the header for nextPacket.
*/
if(!nextHeader.pack(headerBuffer.slice())) {
return false;
}
}
calculateChecksum();
return true;
}
protected void populateGeneratedFields() {
// default empty implementation
}
/**
* Subclasses must implement this method to pack the fields of the header.
*
* <ul>
* <li><p>{@link #pack8(int)} Packs a single 8 bit byte.</p></li>
* <li><p>{@link #pack16(int)} Packs a 16 bit field in network byte order.</p></li>
* <li><p>{@link #pack32(int)} Packs a 32 bit field in network byte order.</p></li>
* <li><p>{@link #packBytes(byte[])} Packs a contiguous array of bytes. </p></li>
* </ul>
*/
abstract protected void packHeader();
/**
* Store a single 8 bit byte value at the current header buffer position and increment the position by 1 byte.
*
* @param value Byte value to be stored.
*/
final protected void pack8(int value) {
headerBuffer.put((byte) value);
}
/**
* Store a 16 bit value in network byte order at the current header buffer offset and increment the position by 2 bytes.
*
* @param value 16 bit value to be stored.
*/
final protected void pack16(int value) {
headerBuffer.putShort((short) value);
}
/**
* Store a 32 bit value in network byte order at the current header buffer position and increment the position by 4 bytes.
*
* @param value
*/
final protected void pack32(int value) {
headerBuffer.putInt(value);
}
/**
* Store an array of bytes at the current header buffer position and increment the position by the length of the array. The number of bytes
* stored is equal to the length of the array.
*
* @param data Array of bytes to store.
*/
final protected void packBytes(byte[] data) {
headerBuffer.put(data);
}
/**
* Store a single 8 bit byte value at the specified absolute offset from the start of this header. The buffer position is not changed.
* @param value Byte value to be stored.
* @param offset Absolute offset in bytes from the start of this header.
*/
final protected void pack8(int value, int offset) {
headerBuffer.put(offset, (byte) value);
}
/**
* Store a 16 bit value in network byte order at the specified absolute offset from the start of this header. The buffer position is not changed.
* @param value 16 bit value to be stored.
* @param offset Absolute offset in bytes from the start of this header.
*/
final protected void pack16(int value, int offset) {
headerBuffer.putShort(offset, (short) value);
}
/**
* Store a 32 bit value in network byte order at the specified absolute offset from the start of this header. The buffer position is not changed.
* @param value 32 bit value to be stored.
* @param offset Absolute offset in bytes from the start of this header.
*/
final protected void pack32(int value, int offset) {
headerBuffer.putInt(offset, value);
}
static public int swap16(int n) {
return ((n & 0xFF00) >> 8) | ((n & 0xFF) << 8);
}
static public int swap32(int n) {
return ((n & 0xFF) << 24)
| ((n & 0xFF00) << 8)
| ((n & 0xFF0000) >> 8)
| ((n >>> 24) & 0xFF);
}
final protected static void verifyMaximum(int value, int maximum) {
if((value < 0) || (value > maximum)) {
throw new PacketFieldException(value);
}
}
// CHECKSUMS
/**
* Fill in any required checksums after packing a header. The default implementation does nothing.
* Override this method to implement checksum calculation.
*/
protected void calculateChecksum() {
}
/**
*
* @param data
* @param pseudoHeader
* @return
*/
final protected int generateChecksumWithPseudo(ByteBuffer data, ByteBuffer pseudoHeader) {
int raw = PacketChecksum.rawSum(pseudoHeader);
raw += PacketChecksum.rawSum(data);
return PacketChecksum.reduceSum(raw);
}
/**
*
* @param length
* @param pseudoHeader
* @return
*/
final protected int generateChecksumWithPseudo(int length, ByteBuffer pseudoHeader) {
ByteBuffer b = headerBuffer.duplicate();
b.limit(length);
b.rewind();
int raw = PacketChecksum.rawSum(pseudoHeader);
raw += PacketChecksum.rawSum(b);
return PacketChecksum.reduceSum(raw);
}
/**
*
* @param length
* @return
*/
final protected int generateChecksum(int length) {
ByteBuffer b = headerBuffer.duplicate();
b.limit(length);
b.rewind();
return generateChecksum(b);
}
/**
*
* @param data
* @return
*/
final protected int generateChecksum(ByteBuffer data) {
return PacketChecksum.checksum(data);
}
// UNPACKING
private boolean isUnpacked;
public void setNextPacket(IPacketHeader payload) {
if(!isUnpacked) {
// FIXME should we allow this?
}
this.nextHeader = payload;
}
public void setPreviousPacket(IPacketHeader payload) {
if(!isUnpacked) {
// FIXME should we allow this?
}
this.previousHeader = payload;
}
public boolean unpack(ByteBuffer buffer) {
if(buffer.position() != 0) {
throw new IllegalArgumentException("The unpack() method must be called with buffer.position() == 0");
}
if(buffer.remaining() < minimumHeaderLength()) {
return false;
}
headerBuffer = buffer;
unpackHeader();
if(!isValidHeader()) {
buffer.rewind();
return false;
}
if(hasPayload() && headerBuffer.hasRemaining()) {
nextHeader = new PacketPayload(headerBuffer.slice());
buffer.position(buffer.limit());
}
isUnpacked = true;
return true;
}
/**
* This method must be implemented by subclasses to unpack the header
* buffer into the fields of the packet. When this method is called,
* the unpackX()
*
* * point past the end of this he
* <ul>
* <li><p> {@link #unpack8()} </p></li>
* <li><p> {@link #unpack16()} </p></li>
* <li><p> {@link #unpack32()} </p></li>
* <li><p> {@link #unpack64()} </p></li>
* <li><p> {@link #unpackBytes(int)} </p></li>
* </ul>
*/
protected abstract void unpackHeader();
/**
* Override to validate header fields
* @return True if header is valid
*/
protected boolean isValidHeader() {
return true;
}
/**
* Override to return true for automatic creation of PacketPayload while unpacking header.
* @return
*/
protected boolean hasPayload() {
return false;
}
protected long unpack64() {
return headerBuffer.getLong();
}
protected int unpack32() {
return headerBuffer.getInt();
}
protected int unpack16() {
return headerBuffer.getShort() & 0xFFFF;
}
protected int unpack8() {
return headerBuffer.get() & 0xFF;
}
protected byte[] unpackBytes(int length) {
byte[] data = new byte[length];
headerBuffer.get(data);
return data;
}
public String print() {
if(nextHeader != null) {
return toString() + " + " + nextHeader.print();
}
return toString();
}
public final void persist() {
persistData();
headerBuffer = nullBuffer;
if(nextHeader != null) {
nextHeader.persist();
}
}
protected void persistData() {
// default empty implementation
}
public PacketPayload payload() {
return new PacketPayload(headerBuffer.slice());
}
public ByteBuffer toByteBuffer() {
return headerBuffer.duplicate();
}
public IPacketHeader findHeader(Class<? extends IPacketHeader> packetClass) {
if (packetClass.isInstance(this)) return this;
if (getNextHeader() == null) return null;
return nextHeader.findHeader(packetClass);
}
}