All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.vtn.manager.packet.Packet Maven / Gradle / Ivy

There is a newer version: 0.8.0
Show newest version
/*
 * Copyright (c) 2013, 2015 Cisco Systems, Inc. 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
 */

package org.opendaylight.vtn.manager.packet;

import static org.opendaylight.vtn.manager.util.NumberUtils.HASH_PRIME;
import static org.opendaylight.vtn.manager.util.NumberUtils.toInteger;
import static org.opendaylight.vtn.manager.util.NumberUtils.toShort;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;

import org.opendaylight.vtn.manager.util.ByteUtils;
import org.opendaylight.vtn.manager.util.Ip4Network;

/**
 * {@code Packet} is an abstract class which represents the generic network
 * packet object.
 *
 * 

* It provides the basic methods which are common for all the packets, * like serialize and deserialize. *

*

* This class is provided only for VTN internal use. * This class may be changed without any notice. *

* * @since Beryllium */ public abstract class Packet implements Cloneable { /** * Logger instance. */ private static final Logger LOG = LoggerFactory.getLogger(Packet.class); /** * Determine whether this packet is corrupted or not. */ private boolean corrupted; /** * The packet encapsulated by this packet. */ private Packet payload; /** * The unparsed raw payload carried by this packet. */ private byte[] rawPayload; /** * Header fields values. */ private Map hdrFieldsMap = new HashMap<>(); /** * The class of the encapsulated packet object. */ private Class payloadClass; /** * {@code HeaderField} describes the location of the header field in the * packet header. */ protected static final class HeaderField { /** * The start bit offset for the header field. */ private final int offset; /** * The number of bits in the header field. */ private final int numBits; /** * The number of bytes in the buffer used to keep the value. */ private final int size; /** * Construct a new instance. * * @param off The start bit offset for the header field. * @param nbits The number of bits in the header field. * @param nbytes The number of bytes in the buffer used to keep * the value. */ private HeaderField(int off, int nbits, int nbytes) { offset = off; numBits = nbits; size = nbytes; } /** * Return the start bit offset for the header field. * * @return The start offset bit for the header field. */ int getOffset() { return offset; } /** * Return the number of bits in the header field. * * @return The number of bits in the header field. */ int getNumBits() { return numBits; } /** * Return the size of the buffer for the header field. * * @return The number of bytes in the buffer used to keep the value. */ int getSize() { return size; } /** * Return the hash code of the given value. * * @param value A value associated with this header field. * @return The hash code of the given value. */ int hash(byte[] value) { byte[] v = value; assert v == null || v.length == size; if (v == null && size != 0) { // Compute the hash code of the default value. v = new byte[size]; } return Arrays.hashCode(v); } } /** * Internal utility class to generate header format map. * *

* The order of the header fields iteration is the order in which its * entries were added. *

*/ static final class HeaderMapBuilder { /** * A immutable map builder. */ private final ImmutableMap.Builder builder = ImmutableMap.builder(); /** * The bit offset fot the next field. */ private int offset; /** * Add a new header field that keeps a byte array. * * @param name The name of the header field. * @param nbits The number of bits in the header field. * @return This instance. */ HeaderMapBuilder addByte(String name, int nbits) { int size = (nbits + Byte.SIZE - 1) / Byte.SIZE; return add(name, nbits, size); } /** * Add a new header field that keeps a number. * * @param name The name of the header field. * @param nbits The number of bits in the header field. * @return This instance. */ HeaderMapBuilder addNumber(String name, int nbits) { int size = (nbits + Byte.SIZE - 1) / Byte.SIZE; if (size != 0) { // Round up the size to a power of 2. int leading = Integer.numberOfLeadingZeros(size); int trailing = Integer.numberOfTrailingZeros(size); if (leading + trailing < Integer.SIZE - 1) { size = 1 << (Integer.SIZE - leading); } } return add(name, nbits, size); } /** * Add a new header field. * * @param name The name of the header field. * @param nbits The number of bits in the header field. * @param size The number of bytes in the buffer used to keep the * value. * @return This instance. */ private HeaderMapBuilder add(String name, int nbits, int size) { HeaderField field = new HeaderField(offset, nbits, size); builder.put(name, field); offset += nbits; return this; } /** * Create a new header field map. * * @return A map that determines the format of the header fields. */ Map build() { return builder.build(); } } /** * Construct a new instance. */ Packet() { } /** * Return the packet encapsulated by this packet. * * @return The packet encapsulated by this packet if present. * {@code null} if not present. */ public final Packet getPayload() { return payload; } /** * Set the packet encapsulated by this packet. * * @param p The packet encapsulated by this packet. */ public void setPayload(Packet p) { payload = p; } /** * This method deserializes the data bits obtained from the wire into the * respective header and payload which are of type Packet. * * @param data Data from wire to deserialize. * @param bitOffset Bit position where packet header starts in data array. * @param size Size of packet in bits. * @return This instance. * @throws PacketException An error occurred. */ public Packet deserialize(byte[] data, int bitOffset, int size) throws PacketException { // Deserialize the header fields one by one. int startOffset = 0; int numBits = 0; payloadClass = null; Map fmtMap = getHeaderFormat(); for (Entry entry: fmtMap.entrySet()) { String hdrField = entry.getKey(); HeaderField hent = entry.getValue(); startOffset = bitOffset + getFieldOffset(hdrField, hent); numBits = getFieldNumBits(hdrField, hent); byte[] hdrFieldBytes; try { hdrFieldBytes = ByteUtils.getBits(data, startOffset, numBits); } catch (RuntimeException e) { String msg = "Failed to deserialize field: " + hdrField + ": off=" + startOffset + ", size=" + numBits + ", total=" + size; throw new PacketException(msg, e); } /* * Store the raw read value, checks the payload type and set the * payloadClass accordingly */ setHeaderField(hdrField, hdrFieldBytes); if (LOG.isTraceEnabled()) { LOG.trace("Deserializing: {}: {}: {} (offset {} bitsize {})", getClass().getSimpleName(), hdrField, ByteUtils.toHexString(hdrFieldBytes), startOffset, numBits); } } // Deserialize the payload now int payloadStart = startOffset + numBits; int payloadSize = data.length * Byte.SIZE - payloadStart; if (payloadClass != null) { try { payload = payloadClass.newInstance(); } catch (Exception e) { throw new PacketException( "Error parsing payload for Ethernet packet", e); } payload.deserialize(data, payloadStart, payloadSize); } else if (payloadSize > 0) { /* * The payload class was not set, it means no class for parsing * this payload is present. Let's store the raw payload if any. */ int start = payloadStart / Byte.SIZE; int stop = start + payloadSize / Byte.SIZE; rawPayload = Arrays.copyOfRange(data, start, stop); } // Take care of computation that can be done only after deserialization postDeserializeCustomOperation(data, payloadStart - getHeaderSize()); return this; } /** * This method serializes the header and payload from the respective * packet class, into a single stream of bytes to be sent on the wire. * * @return The byte array representing the serialized Packet. * @throws PacketException An error occurred. */ public byte[] serialize() throws PacketException { // Acquire or compute the serialized payload byte[] payloadBytes = null; if (payload != null) { payloadBytes = payload.serialize(); } else if (rawPayload != null) { payloadBytes = rawPayload; } int payloadSize = (payloadBytes == null) ? 0 : payloadBytes.length; // Allocate the buffer to contain the full (header + payload) packet int headerSize = getHeaderSize() / Byte.SIZE; byte[] packetBytes = new byte[headerSize + payloadSize]; if (payloadSize != 0) { System.arraycopy(payloadBytes, 0, packetBytes, headerSize, payloadSize); } // Serialize this packet header, field by field Map fmtMap = getHeaderFormat(); for (Entry entry: fmtMap.entrySet()) { String field = entry.getKey(); HeaderField hent = entry.getValue(); byte[] fieldBytes = hdrFieldsMap.get(field); // Let's skip optional fields when not set if (fieldBytes != null) { int off = getFieldOffset(field, hent); int nbits = getFieldNumBits(field, hent); try { ByteUtils.setBits(packetBytes, fieldBytes, off, nbits); } catch (RuntimeException e) { String msg = "Failed to serialize field: " + field + ": off=" + off + ", size=" + nbits + ", total=" + fieldBytes.length; throw new PacketException(msg, e); } } } // Perform post serialize operations (like checksum computation) postSerializeCustomOperation(packetBytes); if (LOG.isTraceEnabled()) { LOG.trace("Serialized: {}: {}", getClass().getSimpleName(), ByteUtils.toHexString(packetBytes)); } return packetBytes; } /** * This method gets called at the end of the serialization process. * * It is intended for the child packets to insert some custom data into the * output byte stream which cannot be done or cannot be done efficiently * during the normal Packet.serialize() path. An example is the checksum * computation for IPv4. * * @param myBytes Serialized bytes. * @throws PacketException An error occurred. */ protected void postSerializeCustomOperation(byte[] myBytes) throws PacketException { } /** * This method re-computes the checksum of the bits received on the wire * and validates it with the checksum in the bits received. * * Since the computation of checksum varies based on the protocol, * this method is overridden. Currently only IPv4 and ICMP do checksum * computation and validation. TCP and UDP need to implement these if * required. * * @param data The byte stream representing the Ethernet frame. * @param offset The bit offset from where the byte array corresponding to * this Packet starts in the frame * @throws PacketException An error occurred. */ protected void postDeserializeCustomOperation(byte[] data, int offset) throws PacketException { } /** * Set a class that specifies the type of packets encapsulated by this * packet. * * @param cls A class that specifies the type of the payload packet. */ protected final void setPayloadClass(Class cls) { payloadClass = cls; } /** * Associate the specified value with the specified header field. * * @param name The name of the header field. * @param value The value to be associated with the specified header * field. {@code null} cannot be specified. */ protected void setHeaderField(String name, byte[] value) { hdrFieldsMap.put(name, value); } /** * Return a map that keeps packet header fields. * * @return A map that keeps packet header fields. */ protected final Map getHeaderFieldMap() { return hdrFieldsMap; } /** * Gets the header length in bits. * * @return The header length in bits. */ public int getHeaderSize() { int size = 0; /* * We need to iterate over the fields that were read in the frame * (hdrFieldsMap) not all the possible ones described in the map * returned by getHeaderFormat(). * For ex, 802.1Q may or may not be there */ Map fmtMap = getHeaderFormat(); for (Entry fieldEntry: hdrFieldsMap.entrySet()) { if (fieldEntry.getValue() != null) { String field = fieldEntry.getKey(); size += getFieldNumBits(field, fmtMap.get(field)); } } return size; } /** * This method fetches the start bit offset for header field specified by * {@code name}. * * @param name The name of the header field. * @return The offset of the requested field. */ public final int getFieldOffset(String name) { return getFieldOffset(name, getHeaderFormat().get(name)); } /** * This method fetches the number of bits for header field specified by * {@code name}. * * @param name The name of the header field. * @return The number of bits of the requested field. */ public final int getFieldNumBits(String name) { return getFieldNumBits(name, getHeaderFormat().get(name)); } /** * Returns the raw payload carried by this packet in case payload was not * parsed. Caller can call this function in case the getPaylod() returns * {@code null}. * * @return The raw payload if not parsable as an array of bytes. * {@code null} otherwise */ public byte[] getRawPayload() { return (rawPayload == null) ? null : rawPayload.clone(); } /** * Set a raw payload in the packet class. * * @param bytes The raw payload as byte array. */ public void setRawPayload(byte[] bytes) { rawPayload = (bytes == null || bytes.length == 0) ? null : bytes.clone(); } /** * Return whether the deserialized packet is to be considered corrupted. * This is the case when the checksum computed after reconstructing the * packet received from wire is not equal to the checksum read from the * stream. For the Packet class which do not have a checksum field, this * function will always return false. * * @return true if the deserialized packet's recomputed checksum is not * equal to the packet carried checksum */ public final boolean isCorrupted() { return corrupted; } /** * Set a boolean value which determines whether this packet is corrupted * or not. * * @param c {@code true} indicates that this packet is corrupted. */ protected final void setCorrupted(boolean c) { corrupted = c; } /** * Return the size of the raw payload in this packet. * * @return The number of bytes in the raw payload. */ protected final int getRawPayloadSize() { return (rawPayload == null) ? 0 : rawPayload.length; } /** * Return the offset of the header field configured in the given instance. * * @param name The name of the header field. * @param entry The header field entry associated with {@code name}. * @return The offset of the requested field. */ protected int getFieldOffset(String name, HeaderField entry) { return entry.getOffset(); } /** * Return the number of bits in the header field configured in the given * instance. * * @param name The name of the header field. * @param entry The header field entry associated with {@code name}. * @return The number of bits of the requested field. */ protected int getFieldNumBits(String name, HeaderField entry) { return entry.getNumBits(); } /** * Compare the given header field values. * * @param name The name of the header field. * @param value1 The first header field value to be compared. * @param value2 The second header field value to be compared. * @return {@code true} only if the given two header field values are * identical. */ protected boolean equalsField(String name, byte[] value1, byte[] value2) { boolean ret = (value1 == value2); if (!ret) { // null is identical to zero. byte[] v1 = value1; byte[] v2 = value2; if (v1 == null) { v1 = new byte[v2.length]; } else if (v2 == null) { v2 = new byte[v1.length]; } ret = Arrays.equals(v1, v2); } return ret; } /** * Return the value of the specified field as a byte array. * * @param name The name of the field. * @return A byte array. */ protected final byte[] getBytes(String name) { byte[] value = hdrFieldsMap.get(name); return (value == null) ? null : value.clone(); } /** * Return the value of the specified field as a byte. * * @param name The name of the field. * @return A byte value. */ protected final byte getByte(String name) { byte[] value = hdrFieldsMap.get(name); return (value == null) ? 0 : value[0]; } /** * Return the value of the specified field as an integer. * * @param name The name of the field. * @return An integer value. */ protected final int getInt(String name) { byte[] value = hdrFieldsMap.get(name); return (value == null) ? 0 : toInteger(value); } /** * Return the value of the specified field as a short integer. * * @param name The name of the field. * @return A short integer value. */ protected final short getShort(String name) { byte[] value = hdrFieldsMap.get(name); return (value == null) ? 0 : toShort(value); } /** * Return the value of the specified field as an IPv4 address. * * @param name The name of the field. * @return An {@link Ip4Network} instance. */ protected final Ip4Network getIp4Network(String name) { byte[] value = hdrFieldsMap.get(name); return (value == null) ? new Ip4Network(0) : new Ip4Network(value); } /** * Determine whether the given value represents a zero short value or not. * * @param value A byte array that contains a short integer. * @return {@code true} only if the given value represents a zero. */ protected final boolean isZeroShort(byte[] value) { return (value == null || toShort(value) == 0); } /** * Return a map that determines the format of the packet. * * @return A map that determines the format of the packet. */ protected abstract Map getHeaderFormat(); /** * Compute the hash code of the header field map. * * @return The hash code of the header field map. */ private int headerHashCode() { int h = 0; Map fmtMap = getHeaderFormat(); for (Entry entry: fmtMap.entrySet()) { String field = entry.getKey(); HeaderField hf = entry.getValue(); h += (field.hashCode() ^ hf.hash(hdrFieldsMap.get(field))); } return h; } /** * Determine whether the given header field map is equal to the map * configured in this instance or not. * * @param map The header field map to be compared. * Specifying {@code null} results in undefined behavior. * @return {@code true} only if the given map is equal to the header * field map in this instance. */ private boolean headerEquals(Map map) { for (String field: getHeaderFormat().keySet()) { byte[] myData = hdrFieldsMap.get(field); byte[] data = map.get(field); if (!equalsField(field, myData, data)) { return false; } } return true; } /** * Create a deep copy of the header field map in this packet. * * @return A deep copy of the header field map in this packet. */ private Map headerClone() { Map copy = new HashMap<>(); for (Entry entry: hdrFieldsMap.entrySet()) { String field = entry.getKey(); byte[] data = entry.getValue(); copy.put(field, data.clone()); } return copy; } // Object /** * Return the hash code of this object. * *

* Note that this method never uses the payload to compute the hash code. *

* * @return The hash code. */ @Override public final int hashCode() { return getClass().hashCode() * HASH_PRIME + headerHashCode(); } /** * Determine whether the given object is identical to this object. * *

* Note that this method never compares the payload encapsulated by * the packet. *

* * @param o An object to be compared. * @return {@code true} if identical. Otherwise {@code false}. */ @Override public final boolean equals(Object o) { boolean ret = (o == this); if (!ret && o != null && getClass().equals(o.getClass())) { Packet pkt = (Packet)o; ret = headerEquals(pkt.hdrFieldsMap); } return ret; } /** * Return a string representation of this object. * * @return A string representation of this object. */ @Override public final String toString() { StringBuilder builder = new StringBuilder(). append(getClass().getSimpleName()).append('{'); String sep = ""; for (String field: getHeaderFormat().keySet()) { byte[] value = hdrFieldsMap.get(field); builder.append(sep).append(field).append('='). append(ByteUtils.toHexString(value)); sep = ", "; } return builder.append('}').toString(); } /** * Return a deep copy of this packet. * * @return A deep copy of this packet. */ @Override public Packet clone() { try { Packet copy = (Packet)super.clone(); copy.hdrFieldsMap = headerClone(); // Copy the payload. // Note that we don't need to copy raw payload because it is // never modified. if (payload != null) { copy.payload = payload.clone(); } return copy; } catch (CloneNotSupportedException e) { throw new IllegalStateException("clone() failed", e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy