+/*
+ * 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.vpnservice.mdsalutil.packet;
+
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.opendaylight.controller.liblldp.BitBufferHelper;
+import org.opendaylight.controller.liblldp.BufferException;
+import org.opendaylight.controller.liblldp.NetUtils;
+import org.opendaylight.controller.liblldp.Packet;
+import org.opendaylight.controller.liblldp.PacketException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class that represents the IPv4 packet objects
+ */
+
+public class IPv4 extends Packet {
+ protected static final Logger logger = LoggerFactory
+ .getLogger(IPv4.class);
+ private static final String VERSION = "Version";
+ private static final String HEADERLENGTH = "HeaderLength";
+ private static final String DIFFSERV = "DiffServ";
+ private static final String ECN = "ECN";
+ private static final String TOTLENGTH = "TotalLength";
+ private static final String IDENTIFICATION = "Identification";
+ private static final String FLAGS = "Flags";
+ private static final String FRAGOFFSET = "FragmentOffset";
+ private static final String TTL = "TTL";
+ private static final String PROTOCOL = "Protocol";
+ private static final String CHECKSUM = "Checksum";
+ private static final String SIP = "SourceIPAddress";
+ private static final String DIP = "DestinationIPAddress";
+ private static final String OPTIONS = "Options";
+
+ private static final int UNIT_SIZE_SHIFT = 2;
+ private static final int UNIT_SIZE = (1 << UNIT_SIZE_SHIFT);
+ private static final int MIN_HEADER_SIZE = 20;
+
+ public static final Map<Byte, Class<? extends Packet>> protocolClassMap;
+ static {
+ protocolClassMap = new HashMap<Byte, Class<? extends Packet>>();
+ protocolClassMap.put(IPProtocols.ICMP.byteValue(), ICMP.class);
+ protocolClassMap.put(IPProtocols.UDP.byteValue(), UDP.class);
+ protocolClassMap.put(IPProtocols.TCP.byteValue(), TCP.class);
+ }
+ private static Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
+ private static final long serialVersionUID = 1L;
+ {
+ put(VERSION, new ImmutablePair<Integer, Integer>(0, 4));
+ put(HEADERLENGTH, new ImmutablePair<Integer, Integer>(4, 4));
+ put(DIFFSERV, new ImmutablePair<Integer, Integer>(8, 6));
+ put(ECN, new ImmutablePair<Integer, Integer>(14, 2));
+ put(TOTLENGTH, new ImmutablePair<Integer, Integer>(16, 16));
+ put(IDENTIFICATION, new ImmutablePair<Integer, Integer>(32, 16));
+ put(FLAGS, new ImmutablePair<Integer, Integer>(48, 3));
+ put(FRAGOFFSET, new ImmutablePair<Integer, Integer>(51, 13));
+ put(TTL, new ImmutablePair<Integer, Integer>(64, 8));
+ put(PROTOCOL, new ImmutablePair<Integer, Integer>(72, 8));
+ put(CHECKSUM, new ImmutablePair<Integer, Integer>(80, 16));
+ put(SIP, new ImmutablePair<Integer, Integer>(96, 32));
+ put(DIP, new ImmutablePair<Integer, Integer>(128, 32));
+ put(OPTIONS, new ImmutablePair<Integer, Integer>(160, 0));
+ }
+ };
+
+ private final Map<String, byte[]> fieldValues;
+
+
+ /**
+ * Default constructor that sets the version to 4, headerLength to 5,
+ * and flags to 2. The default value for the identification is set to a
+ * random number and the remaining fields are set to 0.
+ */
+ public IPv4() {
+ super();
+ fieldValues = new HashMap<String, byte[]>();
+ hdrFieldCoordMap = fieldCoordinates;
+ hdrFieldsMap = fieldValues;
+ corrupted = false;
+
+ setVersion((byte) 4);
+ setHeaderLength((byte) 5);
+ setDiffServ((byte) 0);
+ setECN((byte) 0);
+ setIdentification(generateId());
+ setFlags((byte) 2);
+ setFragmentOffset((short) 0);
+ }
+
+ /**
+ * The write access to the packet is set in this constructor.
+ * Constructor that sets the version to 4, headerLength to 5,
+ * and flags to 2. The default value for the identification is set to a
+ * random number and the remaining fields are set to 0.
+ * @param writeAccess - boolean
+ */
+ public IPv4(boolean writeAccess) {
+ super(writeAccess);
+ fieldValues = new HashMap<String, byte[]>();
+ hdrFieldCoordMap = fieldCoordinates;
+ hdrFieldsMap = fieldValues;
+ corrupted = false;
+
+ setVersion((byte) 4);
+ setHeaderLength((byte) 5);
+ setDiffServ((byte) 0);
+ setECN((byte) 0);
+ setIdentification(generateId());
+ setFlags((byte) 2);
+ setFragmentOffset((short) 0);
+ }
+
+ /**
+ * Gets the IP version stored
+ * @return the version
+ */
+ public byte getVersion() {
+ return (BitBufferHelper.getByte(fieldValues.get(VERSION)));
+ }
+
+ /**
+ * Gets the IP header length stored
+ * @return the headerLength in bytes
+ */
+ public int getHeaderLen() {
+ return (4 * BitBufferHelper.getByte(fieldValues.get(HEADERLENGTH)));
+ }
+
+ /**
+ * Gets the header size in bits
+ * @return The number of bits constituting the header
+ */
+ @Override
+ public int getHeaderSize() {
+ int headerLen = this.getHeaderLen();
+ if (headerLen == 0) {
+ headerLen = MIN_HEADER_SIZE;
+ }
+
+ return headerLen * NetUtils.NumBitsInAByte;
+ }
+
+ /**
+ * Gets the differential services value stored
+ * @return the diffServ
+ */
+ public byte getDiffServ() {
+ return BitBufferHelper.getByte(fieldValues.get(DIFFSERV));
+ }
+
+ /**
+ * Gets the ecn bits stored
+ * @return the ecn bits
+ */
+ public byte getECN() {
+ return BitBufferHelper.getByte(fieldValues.get(ECN));
+ }
+
+ /**
+ * Gets the total length of the IP header in bytes
+ * @return the totalLength
+ */
+ public short getTotalLength() {
+ return (BitBufferHelper.getShort(fieldValues.get(TOTLENGTH)));
+ }
+
+ /**
+ * Gets the identification value stored
+ * @return the identification
+ */
+ public short getIdentification() {
+ return (BitBufferHelper.getShort(fieldValues.get(IDENTIFICATION)));
+ }
+
+ /**
+ * Gets the flag values stored
+ * @return the flags
+ */
+ public byte getFlags() {
+ return (BitBufferHelper.getByte(fieldValues.get(FLAGS)));
+ }
+
+ /**
+ * Gets the TTL value stored
+ * @return the ttl
+ */
+ public byte getTtl() {
+ return (BitBufferHelper.getByte(fieldValues.get(TTL)));
+ }
+
+ /**
+ * Gets the protocol value stored
+ * @return the protocol
+ */
+ public byte getProtocol() {
+ return (BitBufferHelper.getByte(fieldValues.get(PROTOCOL)));
+ }
+
+ /**
+ * Gets the checksum value stored
+ * @return the checksum
+ */
+ public short getChecksum() {
+ return (BitBufferHelper.getShort(fieldValues.get(CHECKSUM)));
+ }
+
+ /**
+ * Gets the fragment offset stored
+ * @return the fragmentOffset
+ */
+ public short getFragmentOffset() {
+ return (BitBufferHelper.getShort(fieldValues.get(FRAGOFFSET)));
+ }
+
+ /**
+ * Gets the source IP address stored
+ * @return the sourceAddress
+ */
+ public int getSourceAddress() {
+ return (BitBufferHelper.getInt(fieldValues.get(SIP)));
+ }
+
+ /**
+ * Gets the destination IP address stored
+ * @return the destinationAddress
+ */
+ public int getDestinationAddress() {
+ return (BitBufferHelper.getInt(fieldValues.get(DIP)));
+ }
+
+ /**
+ * gets the Options stored
+ * @return the options
+ */
+ public byte[] getOptions() {
+ return (fieldValues.get(OPTIONS));
+ }
+
+ @Override
+ /**
+ * Stores the value of fields read from data stream
+ * Variable header value like payload protocol, is stored here
+ * @param headerField
+ * @param readValue
+ */
+ public void setHeaderField(String headerField, byte[] readValue) {
+ if (headerField.equals(PROTOCOL)) {
+ // Don't set payloadClass if framgment offset is not zero.
+ byte[] fragoff = hdrFieldsMap.get(FRAGOFFSET);
+ if (fragoff == null || BitBufferHelper.getShort(fragoff) == 0) {
+ payloadClass = protocolClassMap.get(readValue[0]);
+ }
+ } else if (headerField.equals(FRAGOFFSET)) {
+ if (readValue != null && BitBufferHelper.getShort(readValue) != 0) {
+ // Clear payloadClass because protocol header is not present
+ // in this packet.
+ payloadClass = null;
+ }
+ } else if (headerField.equals(OPTIONS) &&
+ (readValue == null || readValue.length == 0)) {
+ hdrFieldsMap.remove(headerField);
+ return;
+ }
+ hdrFieldsMap.put(headerField, readValue);
+ }
+
+ /**
+ * Stores the IP version from the header
+ * @param ipVersion the version to set
+ * @return IPv4
+ */
+ public IPv4 setVersion(byte ipVersion) {
+ byte[] version = BitBufferHelper.toByteArray(ipVersion);
+ fieldValues.put(VERSION, version);
+ return this;
+ }
+
+ /**
+ * Stores the length of IP header in words (4 bytes)
+ * @param ipheaderLength the headerLength to set
+ * @return IPv4
+ */
+ public IPv4 setHeaderLength(byte ipheaderLength) {
+ byte[] headerLength = BitBufferHelper.toByteArray(ipheaderLength);
+ fieldValues.put(HEADERLENGTH, headerLength);
+ return this;
+ }
+
+ /**
+ * Stores the differential services value from the IP header
+ * @param ipdiffServ the diffServ to set
+ * @return IPv4
+ */
+ public IPv4 setDiffServ(byte ipdiffServ) {
+ byte[] diffServ = BitBufferHelper.toByteArray(ipdiffServ);
+ fieldValues.put(DIFFSERV, diffServ);
+ return this;
+ }
+
+ /**
+ * Stores the ECN bits from the header
+ * @param ecn ECN bits to set
+ * @return IPv4
+ */
+ public IPv4 setECN(byte ecn) {
+ byte[] ecnbytes = BitBufferHelper.toByteArray(ecn);
+ fieldValues.put(ECN, ecnbytes);
+ return this;
+ }
+
+ /**
+ * Stores the total length of IP header in bytes
+ * @param iptotalLength the totalLength to set
+ * @return IPv4
+ */
+ public IPv4 setTotalLength(short iptotalLength) {
+ byte[] totalLength = BitBufferHelper.toByteArray(iptotalLength);
+ fieldValues.put(TOTLENGTH, totalLength);
+ return this;
+ }
+
+ /**
+ * Stores the identification number from the header
+ * @param ipIdentification the identification to set
+ * @return IPv4
+ */
+ public IPv4 setIdentification(short ipIdentification) {
+ byte[] identification = BitBufferHelper.toByteArray(ipIdentification);
+ fieldValues.put(IDENTIFICATION, identification);
+ return this;
+ }
+
+ /**
+ * Stores the IP flags value
+ * @param ipFlags the flags to set
+ * @return IPv4
+ */
+ public IPv4 setFlags(byte ipFlags) {
+ byte[] flags = { ipFlags };
+ fieldValues.put(FLAGS, flags);
+ return this;
+ }
+
+ /**
+ * Stores the IP fragmentation offset value
+ * @param ipFragmentOffset the fragmentOffset to set
+ * @return IPv4
+ */
+ public IPv4 setFragmentOffset(short ipFragmentOffset) {
+ byte[] fragmentOffset = BitBufferHelper.toByteArray(ipFragmentOffset);
+ fieldValues.put(FRAGOFFSET, fragmentOffset);
+ return this;
+ }
+
+ /**
+ * Stores the TTL value
+ * @param ipTtl the ttl to set
+ * @return IPv4
+ */
+ public IPv4 setTtl(byte ipTtl) {
+ byte[] ttl = BitBufferHelper.toByteArray(ipTtl);
+ fieldValues.put(TTL, ttl);
+ return this;
+ }
+
+ /**
+ * Stores the protocol value of the IP payload
+ * @param ipProtocol the protocol to set
+ * @return IPv4
+ */
+ public IPv4 setProtocol(byte ipProtocol) {
+ byte[] protocol = BitBufferHelper.toByteArray(ipProtocol);
+ fieldValues.put(PROTOCOL, protocol);
+ return this;
+ }
+
+ /**
+ * @param checksum the checksum to set
+ */
+ /*public IPv4 setChecksum() {
+ short ipChecksum = computeChecksum();
+ byte[] checksum = BitBufferHelper.toByteArray(ipChecksum);
+ fieldValues.put(CHECKSUM, checksum);
+ return this;
+ }*/
+
+ /**
+ * Stores the IP source address from the header
+ * @param ipSourceAddress the sourceAddress to set
+ * @return IPv4
+ */
+ public IPv4 setSourceAddress(InetAddress ipSourceAddress) {
+ byte[] sourceAddress = ipSourceAddress.getAddress();
+ fieldValues.put(SIP, sourceAddress);
+ return this;
+ }
+
+ /**
+ * Stores the IP destination address from the header
+ * @param ipDestinationAddress the destination Address to set
+ * @return IPv4
+ */
+ public IPv4 setDestinationAddress(InetAddress ipDestinationAddress) {
+ byte[] sourceAddress = ipDestinationAddress.getAddress();
+ fieldValues.put(DIP, sourceAddress);
+ return this;
+ }
+
+ /**
+ * Stores the IP destination address from the header
+ * @param ipDestinationAddress the destinationAddress to set
+ * @return IPv4
+ */
+ public IPv4 setDestinationAddress(int ipDestinationAddress) {
+ byte[] destinationAddress = BitBufferHelper
+ .toByteArray(ipDestinationAddress);
+ fieldValues.put(DIP, destinationAddress);
+ return this;
+ }
+
+ /**
+ * Generate a random number to set the Identification field
+ * in IPv4 Header
+ * @return short
+ */
+ private short generateId() {
+ Random randomgen = new Random();
+ return (short) (randomgen.nextInt(Short.MAX_VALUE + 1));
+ }
+
+ /**
+ * Store the options from IP header
+ * @param options - byte[]
+ * @return IPv4
+ */
+ public IPv4 setOptions(byte[] options) {
+ byte newIHL = (byte)(MIN_HEADER_SIZE >>> UNIT_SIZE_SHIFT);
+ if (options == null || options.length == 0) {
+ fieldValues.remove(OPTIONS);
+ } else {
+ int len = options.length;
+ int rlen = (len + (UNIT_SIZE - 1)) & ~(UNIT_SIZE - 1);
+ if (rlen > len) {
+ // Padding is required.
+ byte[] newopt = new byte[rlen];
+ System.arraycopy(options, 0, newopt, 0, len);
+ options = newopt;
+ len = rlen;
+ }
+ fieldValues.put(OPTIONS, options);
+ newIHL += (len >>> UNIT_SIZE_SHIFT);
+ }
+
+ setHeaderLength(newIHL);
+
+ return this;
+ }
+
+ /**
+ * Computes the IPv4 header checksum on the passed stream of bytes
+ * representing the packet
+ *
+ * @param data
+ * The byte stream
+ * @param offset
+ * The byte offset from where the IPv4 packet starts
+ * @return The computed checksum
+ */
+ short computeChecksum(byte[] data, int start) {
+ int end = start + getHeaderLen();
+ short checkSum = (short) 0;
+ int sum = 0, carry = 0, finalSum = 0;
+ int wordData;
+ int checksumStart = start
+ + (getfieldOffset(CHECKSUM) / NetUtils.NumBitsInAByte);
+
+ for (int i = start; i <= (end - 1); i = i + 2) {
+ // Skip, if the current bytes are checkSum bytes
+ if (i == checksumStart) {
+ continue;
+ }
+ wordData = ((data[i] << 8) & 0xFF00) + (data[i + 1] & 0xFF);
+ sum = sum + wordData;
+ }
+ carry = (sum >> 16) & 0xFF;
+ finalSum = (sum & 0xFFFF) + carry;
+ checkSum = (short) ~((short) finalSum & 0xFFFF);
+
+ return checkSum;
+ }
+
+ @Override
+ /**
+ * Gets the number of bits for the fieldname specified
+ * If the fieldname has variable length like "Options", then this value is computed using the header length
+ * @param fieldname - String
+ * @return number of bits for fieldname - int
+ */
+ public int getfieldnumBits(String fieldName) {
+ if (fieldName.equals(OPTIONS)) {
+ return (getHeaderLen() - MIN_HEADER_SIZE) * NetUtils.NumBitsInAByte;
+ }
+ return hdrFieldCoordMap.get(fieldName).getRight();
+ }
+
+ @Override
+ /**
+ * Method to perform post serialization - like computation of checksum of serialized header
+ * @param data
+ * @return void
+ * @Exception throws PacketException
+ */
+ protected void postSerializeCustomOperation(byte[] data)
+ throws PacketException {
+
+ // Recompute the total length field here
+ byte[] totalLength = BitBufferHelper.toByteArray((short) data.length);
+ try {
+ BitBufferHelper.setBytes(data, totalLength, getfieldOffset(TOTLENGTH),
+ getfieldnumBits(TOTLENGTH));
+ } catch (BufferException e) {
+ throw new PacketException(e.getMessage());
+ }
+
+ // Now compute the Header Checksum
+ byte[] checkSum = BitBufferHelper.toByteArray(computeChecksum(data, 0));
+
+ try {
+ BitBufferHelper.setBytes(data, checkSum, getfieldOffset(CHECKSUM),
+ getfieldnumBits(CHECKSUM));
+ } catch (BufferException e) {
+ throw new PacketException(e.getMessage());
+ }
+ }
+
+ @Override
+ /**
+ * Stores the payload of IP, serializes it and stores the length of serialized payload
+ * bytes in Total Length
+ * @param payload - Packet
+ */
+ /**
+ * Set the total length field in the IPv4 Object
+ * Note: this field will get overwritten during serialization phase.
+ */
+ public void setPayload(Packet payload) {
+ this.payload = payload;
+ /*
+ * Deriving the Total Length here
+ */
+ int payloadLength = 0;
+ if (payload != null) {
+ try {
+ payloadLength = payload.serialize().length;
+ } catch (PacketException e) {
+ logger.error("", e);
+ }
+ }
+
+ this.setTotalLength((short) (this.getHeaderLen() + payloadLength));
+ }
+
+
+ /**
+ * Method to perform post deserialization - like compare computed checksum with
+ * the one obtained from IP header
+ */
+ @Override
+ protected void postDeserializeCustomOperation(byte[] data, int startBitOffset) {
+ int start = startBitOffset / NetUtils.NumBitsInAByte;
+ short computedChecksum = computeChecksum(data, start);
+ short actualChecksum = BitBufferHelper.getShort(fieldValues.get(CHECKSUM));
+ if (computedChecksum != actualChecksum) {
+ corrupted = true;
+ }
+ }
+
+}