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
9 package org.opendaylight.openflowplugin.libraries.liblldp;
11 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
12 import java.util.Arrays;
14 import java.util.Map.Entry;
15 import org.apache.commons.lang3.tuple.Pair;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
20 * Abstract class which represents the generic network packet object It provides
21 * the basic methods which are common for all the packets, like serialize and
24 public abstract class Packet {
25 protected static final Logger LOG = LoggerFactory.getLogger(Packet.class);
26 // Access level granted to this packet
27 protected boolean writeAccess;
28 // When deserialized from wire, packet could result corrupted
29 protected boolean corrupted;
30 // The packet that encapsulate this packet
31 protected Packet parent;
32 // The packet encapsulated by this packet
33 protected Packet payload;
34 // The unparsed raw payload carried by this packet
35 protected byte[] rawPayload;
36 // Bit coordinates of packet header fields
37 protected Map<String, Pair<Integer, Integer>> hdrFieldCoordMap;
38 // Header fields values: Map<FieldName,Value>
39 protected Map<String, byte[]> hdrFieldsMap;
40 // The class of the encapsulated packet object
41 protected Class<? extends Packet> payloadClass;
48 public Packet(final boolean writeAccess) {
49 this.writeAccess = writeAccess;
53 public Packet getParent() {
57 public Packet getPayload() {
61 public void setParent(final Packet parent) {
65 public void setPayload(final Packet payload) {
66 this.payload = payload;
69 public void setHeaderField(final String headerField, final byte[] readValue) {
70 hdrFieldsMap.put(headerField, readValue);
74 * This method deserializes the data bits obtained from the wire into the
75 * respective header and payload which are of type Packet.
77 * @param data - data from wire to deserialize
78 * @param bitOffset bit position where packet header starts in data
80 * @param size size of packet in bits
82 * @throws PacketException if deserialization fails
84 public Packet deserialize(final byte[] data, final int bitOffset, final int size)
85 throws PacketException {
87 // Deserialize the header fields one by one
90 for (Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
92 String hdrField = pairs.getKey();
93 startOffset = bitOffset + this.getfieldOffset(hdrField);
94 numBits = this.getfieldnumBits(hdrField);
98 hdrFieldBytes = BitBufferHelper.getBits(data, startOffset,
100 } catch (final BufferException e) {
101 throw new PacketException("getBits failed", e);
105 * Store the raw read value, checks the payload type and set the
106 * payloadClass accordingly
108 this.setHeaderField(hdrField, hdrFieldBytes);
110 if (LOG.isTraceEnabled()) {
111 LOG.trace("{}: {}: {} (offset {} bitsize {})", this.getClass().getSimpleName(), hdrField,
112 HexEncode.bytesToHexString(hdrFieldBytes), startOffset, numBits);
116 // Deserialize the payload now
117 int payloadStart = startOffset + numBits;
118 int payloadSize = data.length * NetUtils.NUM_BITS_IN_A_BYTE - payloadStart;
120 if (payloadClass != null) {
122 payload = payloadClass.newInstance();
123 } catch (InstantiationException | IllegalAccessException e) {
124 throw new PacketException("Error parsing payload for Ethernet packet", e);
126 payload.deserialize(data, payloadStart, payloadSize);
127 payload.setParent(this);
130 * The payload class was not set, it means no class for parsing
131 * this payload is present. Let's store the raw payload if any.
133 int start = payloadStart / NetUtils.NUM_BITS_IN_A_BYTE;
134 int stop = start + payloadSize / NetUtils.NUM_BITS_IN_A_BYTE;
135 rawPayload = Arrays.copyOfRange(data, start, stop);
139 // Take care of computation that can be done only after deserialization
140 postDeserializeCustomOperation(data, payloadStart - getHeaderSize());
146 * This method serializes the header and payload from the respective
147 * packet class, into a single stream of bytes to be sent on the wire.
149 * @return The byte array representing the serialized Packet
150 * @throws PacketException if serialization fails
152 public byte[] serialize() throws PacketException {
154 // Acquire or compute the serialized payload
155 byte[] payloadBytes = null;
156 if (payload != null) {
157 payloadBytes = payload.serialize();
158 } else if (rawPayload != null) {
159 payloadBytes = rawPayload;
161 int payloadSize = payloadBytes == null ? 0 : payloadBytes.length;
163 // Allocate the buffer to contain the full (header + payload) packet
164 int headerSize = this.getHeaderSize() / NetUtils.NUM_BITS_IN_A_BYTE;
165 byte[] packetBytes = new byte[headerSize + payloadSize];
166 if (payloadBytes != null) {
167 System.arraycopy(payloadBytes, 0, packetBytes, headerSize, payloadSize);
170 // Serialize this packet header, field by field
171 for (Map.Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
173 String field = pairs.getKey();
174 byte[] fieldBytes = hdrFieldsMap.get(field);
175 // Let's skip optional fields when not set
176 if (fieldBytes != null) {
178 BitBufferHelper.setBytes(packetBytes, fieldBytes,
179 getfieldOffset(field), getfieldnumBits(field));
180 } catch (final BufferException e) {
181 throw new PacketException("setBytes failed", e);
186 // Perform post serialize operations (like checksum computation)
187 postSerializeCustomOperation(packetBytes);
189 if (LOG.isTraceEnabled()) {
190 LOG.trace("{}: {}", this.getClass().getSimpleName(),
191 HexEncode.bytesToHexString(packetBytes));
198 * This method gets called at the end of the serialization process It is
199 * intended for the child packets to insert some custom data into the output
200 * byte stream which cannot be done or cannot be done efficiently during the
201 * normal Packet.serialize() path. An example is the checksum computation
204 * @param myBytes serialized bytes
205 * @throws PacketException on failure
207 protected void postSerializeCustomOperation(byte[] myBytes) throws PacketException {
212 * This method re-computes the checksum of the bits received on the wire and
213 * validates it with the checksum in the bits received Since the computation
214 * of checksum varies based on the protocol, this method is overridden.
215 * Currently only IPv4 and ICMP do checksum computation and validation. TCP
216 * and UDP need to implement these if required.
218 * @param data The byte stream representing the Ethernet frame
219 * @param startBitOffset The bit offset from where the byte array corresponding to this Packet starts in the frame
220 * @throws PacketException on failure
222 protected void postDeserializeCustomOperation(byte[] data, int startBitOffset) throws PacketException {
227 * Gets the header length in bits.
229 * @return int the header length in bits
231 public int getHeaderSize() {
234 * We need to iterate over the fields that were read in the frame
235 * (hdrFieldsMap) not all the possible ones described in
236 * hdrFieldCoordMap. For ex, 802.1Q may or may not be there
238 for (Map.Entry<String, byte[]> fieldEntry : hdrFieldsMap.entrySet()) {
239 if (fieldEntry.getValue() != null) {
240 String field = fieldEntry.getKey();
241 size += getfieldnumBits(field);
248 * This method fetches the start bit offset for header field specified by
249 * 'fieldname'. The offset is present in the hdrFieldCoordMap of the
250 * respective packet class
252 * @return Integer - startOffset of the requested field
254 public int getfieldOffset(final String fieldName) {
255 return hdrFieldCoordMap.get(fieldName).getLeft();
259 * This method fetches the number of bits for header field specified by
260 * 'fieldname'. The numBits are present in the hdrFieldCoordMap of the
261 * respective packet class
263 * @return Integer - number of bits of the requested field
265 public int getfieldnumBits(final String fieldName) {
266 return hdrFieldCoordMap.get(fieldName).getRight();
270 public String toString() {
271 StringBuilder ret = new StringBuilder();
272 ret.append(this.getClass().getSimpleName());
274 for (String field : hdrFieldCoordMap.keySet()) {
275 byte[] value = hdrFieldsMap.get(field);
278 ret.append(HexEncode.bytesToHexString(value));
281 ret.replace(ret.length() - 2, ret.length() - 1, "]");
282 return ret.toString();
286 * Returns the raw payload carried by this packet in case payload was not
287 * parsed. Caller can call this function in case the getPaylod() returns null.
289 * @return The raw payload if not parsable as an array of bytes, null otherwise
291 @SuppressFBWarnings("EI_EXPOSE_REP")
292 public byte[] getRawPayload() {
297 * Set a raw payload in the packet class.
299 * @param bytes The raw payload as byte array
301 public void setRawPayload(final byte[] bytes) {
302 this.rawPayload = Arrays.copyOf(bytes, bytes.length);
306 * Return whether the deserialized packet is to be considered corrupted.
307 * This is the case when the checksum computed after reconstructing the
308 * packet received from wire is not equal to the checksum read from the
309 * stream. For the Packet class which do not have a checksum field, this
310 * function will always return false.
313 * @return true if the deserialized packet's recomputed checksum is not
314 * equal to the packet carried checksum
316 public boolean isCorrupted() {
321 public int hashCode() {
322 final int prime = 31;
323 int result = super.hashCode();
324 result = prime * result
325 + (this.hdrFieldsMap == null ? 0 : hdrFieldsMap.hashCode());
330 public boolean equals(final Object obj) {
338 if (getClass() != obj.getClass()) {
341 Packet other = (Packet) obj;
342 if (hdrFieldsMap == other.hdrFieldsMap) {
345 if (hdrFieldsMap == null || other.hdrFieldsMap == null) {
348 for (Entry<String, byte[]> entry : hdrFieldsMap.entrySet()) {
349 String field = entry.getKey();
350 if (!Arrays.equals(entry.getValue(), other.hdrFieldsMap.get(field))) {