2 * Copyright (c) 2013, 2015 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.openflowplugin.libraries.liblldp;
10 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
11 import java.util.Arrays;
13 import java.util.Map.Entry;
14 import org.apache.commons.lang3.tuple.Pair;
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
19 * Abstract class which represents the generic network packet object. It provides
20 * the basic methods which are common for all the packets, like serialize and
23 public abstract class Packet {
25 private static final Logger LOG = LoggerFactory.getLogger(Packet.class);
27 // Access level granted to this packet
28 protected boolean writeAccess;
30 // When deserialized from wire, packet could result corrupted
31 protected boolean corrupted;
33 // The packet that encapsulate this packet
34 protected Packet parent;
36 // The packet encapsulated by this packet
37 protected Packet payload;
39 // The unparsed raw payload carried by this packet
40 protected byte[] rawPayload;
42 // Bit coordinates of packet header fields
43 protected Map<String, Pair<Integer, Integer>> hdrFieldCoordMap;
45 // Header fields values: Map<FieldName,Value>
46 protected Map<String, byte[]> hdrFieldsMap;
48 // The class of the encapsulated packet object
49 protected Class<? extends Packet> payloadClass;
56 public Packet(final boolean writeAccess) {
57 this.writeAccess = writeAccess;
61 public Packet getParent() {
65 public Packet getPayload() {
69 public void setParent(final Packet parent) {
73 public void setPayload(final Packet payload) {
74 this.payload = payload;
77 public void setHeaderField(final String headerField, final byte[] readValue) {
78 hdrFieldsMap.put(headerField, readValue);
82 * This method deserializes the data bits obtained from the wire into the
83 * respective header and payload which are of type Packet.
85 * @param data - data from wire to deserialize
86 * @param bitOffset bit position where packet header starts in data
88 * @param size size of packet in bits
90 * @throws PacketException if deserialization fails
92 public Packet deserialize(final byte[] data, final int bitOffset, final int size)
93 throws PacketException {
95 // Deserialize the header fields one by one
98 for (Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
100 String hdrField = pairs.getKey();
101 startOffset = bitOffset + this.getfieldOffset(hdrField);
102 numBits = this.getfieldnumBits(hdrField);
104 byte[] hdrFieldBytes;
106 hdrFieldBytes = BitBufferHelper.getBits(data, startOffset,
108 } catch (final BufferException e) {
109 throw new PacketException("getBits failed", e);
113 * Store the raw read value, checks the payload type and set the
114 * payloadClass accordingly
116 this.setHeaderField(hdrField, hdrFieldBytes);
118 if (LOG.isTraceEnabled()) {
119 LOG.trace("{}: {}: {} (offset {} bitsize {})", this.getClass().getSimpleName(), hdrField,
120 HexEncode.bytesToHexString(hdrFieldBytes), startOffset, numBits);
124 // Deserialize the payload now
125 int payloadStart = startOffset + numBits;
126 int payloadSize = data.length * NetUtils.NUM_BITS_IN_A_BYTE - payloadStart;
128 if (payloadClass != null) {
130 payload = payloadClass.newInstance();
131 } catch (InstantiationException | IllegalAccessException e) {
132 throw new PacketException("Error parsing payload for Ethernet packet", e);
134 payload.deserialize(data, payloadStart, payloadSize);
135 payload.setParent(this);
138 * The payload class was not set, it means no class for parsing
139 * this payload is present. Let's store the raw payload if any.
141 int start = payloadStart / NetUtils.NUM_BITS_IN_A_BYTE;
142 int stop = start + payloadSize / NetUtils.NUM_BITS_IN_A_BYTE;
143 rawPayload = Arrays.copyOfRange(data, start, stop);
147 // Take care of computation that can be done only after deserialization
148 postDeserializeCustomOperation(data, payloadStart - getHeaderSize());
154 * This method serializes the header and payload from the respective
155 * packet class, into a single stream of bytes to be sent on the wire.
157 * @return The byte array representing the serialized Packet
158 * @throws PacketException if serialization fails
160 public byte[] serialize() throws PacketException {
162 // Acquire or compute the serialized payload
163 byte[] payloadBytes = null;
164 if (payload != null) {
165 payloadBytes = payload.serialize();
166 } else if (rawPayload != null) {
167 payloadBytes = rawPayload;
169 int payloadSize = payloadBytes == null ? 0 : payloadBytes.length;
171 // Allocate the buffer to contain the full (header + payload) packet
172 int headerSize = this.getHeaderSize() / NetUtils.NUM_BITS_IN_A_BYTE;
173 byte[] packetBytes = new byte[headerSize + payloadSize];
174 if (payloadBytes != null) {
175 System.arraycopy(payloadBytes, 0, packetBytes, headerSize, payloadSize);
178 // Serialize this packet header, field by field
179 for (Map.Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
181 String field = pairs.getKey();
182 byte[] fieldBytes = hdrFieldsMap.get(field);
183 // Let's skip optional fields when not set
184 if (fieldBytes != null) {
186 BitBufferHelper.setBytes(packetBytes, fieldBytes,
187 getfieldOffset(field), getfieldnumBits(field));
188 } catch (final BufferException e) {
189 throw new PacketException("setBytes failed", e);
194 // Perform post serialize operations (like checksum computation)
195 postSerializeCustomOperation(packetBytes);
197 if (LOG.isTraceEnabled()) {
198 LOG.trace("packet {}: {}", this.getClass().getSimpleName(),
199 HexEncode.bytesToHexString(packetBytes));
206 * This method gets called at the end of the serialization process It is
207 * intended for the child packets to insert some custom data into the output
208 * byte stream which cannot be done or cannot be done efficiently during the
209 * normal Packet.serialize() path. An example is the checksum computation
212 * @param myBytes serialized bytes
213 * @throws PacketException on failure
215 protected void postSerializeCustomOperation(byte[] myBytes) throws PacketException {
220 * This method re-computes the checksum of the bits received on the wire and
221 * validates it with the checksum in the bits received Since the computation
222 * of checksum varies based on the protocol, this method is overridden.
223 * Currently only IPv4 and ICMP do checksum computation and validation. TCP
224 * and UDP need to implement these if required.
226 * @param data The byte stream representing the Ethernet frame
227 * @param startBitOffset The bit offset from where the byte array corresponding to this Packet starts in the frame
228 * @throws PacketException on failure
230 protected void postDeserializeCustomOperation(byte[] data, int startBitOffset) throws PacketException {
235 * Gets the header length in bits.
237 * @return int the header length in bits
239 public int getHeaderSize() {
242 * We need to iterate over the fields that were read in the frame
243 * (hdrFieldsMap) not all the possible ones described in
244 * hdrFieldCoordMap. For ex, 802.1Q may or may not be there
246 for (Map.Entry<String, byte[]> fieldEntry : hdrFieldsMap.entrySet()) {
247 if (fieldEntry.getValue() != null) {
248 String field = fieldEntry.getKey();
249 size += getfieldnumBits(field);
256 * This method fetches the start bit offset for header field specified by
257 * 'fieldname'. The offset is present in the hdrFieldCoordMap of the
258 * respective packet class
260 * @return Integer - startOffset of the requested field
262 public int getfieldOffset(final String fieldName) {
263 return hdrFieldCoordMap.get(fieldName).getLeft();
267 * This method fetches the number of bits for header field specified by
268 * 'fieldname'. The numBits are present in the hdrFieldCoordMap of the
269 * respective packet class
271 * @return Integer - number of bits of the requested field
273 public int getfieldnumBits(final String fieldName) {
274 return hdrFieldCoordMap.get(fieldName).getRight();
278 public String toString() {
279 StringBuilder ret = new StringBuilder();
280 ret.append(this.getClass().getSimpleName());
282 for (String field : hdrFieldCoordMap.keySet()) {
283 byte[] value = hdrFieldsMap.get(field);
286 ret.append(HexEncode.bytesToHexString(value));
289 ret.replace(ret.length() - 2, ret.length() - 1, "]");
290 return ret.toString();
294 * Returns the raw payload carried by this packet in case payload was not
295 * parsed. Caller can call this function in case the getPaylod() returns null.
297 * @return The raw payload if not parsable as an array of bytes, null otherwise
299 @SuppressFBWarnings("EI_EXPOSE_REP")
300 public byte[] getRawPayload() {
305 * Set a raw payload in the packet class.
307 * @param bytes The raw payload as byte array
309 public void setRawPayload(final byte[] bytes) {
310 this.rawPayload = Arrays.copyOf(bytes, bytes.length);
314 * Return whether the deserialized packet is to be considered corrupted.
315 * This is the case when the checksum computed after reconstructing the
316 * packet received from wire is not equal to the checksum read from the
317 * stream. For the Packet class which do not have a checksum field, this
318 * function will always return false.
321 * @return true if the deserialized packet's recomputed checksum is not
322 * equal to the packet carried checksum
324 public boolean isCorrupted() {
329 public int hashCode() {
330 final int prime = 31;
331 int result = super.hashCode();
332 result = prime * result
333 + (this.hdrFieldsMap == null ? 0 : hdrFieldsMap.hashCode());
338 public boolean equals(final Object obj) {
346 if (getClass() != obj.getClass()) {
349 Packet other = (Packet) obj;
350 if (hdrFieldsMap == other.hdrFieldsMap) {
353 if (hdrFieldsMap == null || other.hdrFieldsMap == null) {
356 for (Entry<String, byte[]> entry : hdrFieldsMap.entrySet()) {
357 String field = entry.getKey();
358 if (!Arrays.equals(entry.getValue(), other.hdrFieldsMap.get(field))) {