2 * Copyright (c) 2013-2014 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.controller.sal.packet;
11 import java.util.Arrays;
13 import java.util.Map.Entry;
15 import org.apache.commons.lang3.tuple.Pair;
16 import org.opendaylight.controller.sal.match.Match;
17 import org.opendaylight.controller.sal.utils.HexEncode;
18 import org.opendaylight.controller.sal.utils.NetUtils;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
23 * Abstract class which represents the generic network packet object It provides
24 * the basic methods which are common for all the packets, like serialize and
28 public abstract class Packet {
29 protected static final Logger logger = LoggerFactory
30 .getLogger(Packet.class);
31 // Access level granted to this packet
32 protected boolean writeAccess;
33 // When deserialized from wire, packet could result corrupted
34 protected boolean corrupted;
35 // The packet that encapsulate this packet
36 protected Packet parent;
37 // The packet encapsulated by this packet
38 protected Packet payload;
39 // The unparsed raw payload carried by this packet
40 protected byte[] rawPayload;
41 // Bit coordinates of packet header fields
42 protected Map<String, Pair<Integer, Integer>> hdrFieldCoordMap;
43 // Header fields values: Map<FieldName,Value>
44 protected Map<String, byte[]> hdrFieldsMap;
45 // The class of the encapsulated packet object
46 protected Class<? extends Packet> payloadClass;
53 public Packet(boolean writeAccess) {
54 this.writeAccess = writeAccess;
58 public Packet getParent() {
62 public Packet getPayload() {
66 public void setParent(Packet parent) {
70 public void setPayload(Packet payload) {
71 this.payload = payload;
74 public void setHeaderField(String headerField, byte[] readValue) {
75 hdrFieldsMap.put(headerField, readValue);
79 * This method deserializes the data bits obtained from the wire into the
80 * respective header and payload which are of type Packet
82 * @param byte[] data - data from wire to deserialize
83 * @param int bitOffset bit position where packet header starts in data
85 * @param int size of packet in bits
87 * @throws PacketException
89 public Packet deserialize(byte[] data, int bitOffset, int size)
90 throws PacketException {
92 // Deserialize the header fields one by one
93 int startOffset = 0, numBits = 0;
94 for (Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
96 String hdrField = pairs.getKey();
97 startOffset = bitOffset + this.getfieldOffset(hdrField);
98 numBits = this.getfieldnumBits(hdrField);
100 byte[] hdrFieldBytes = null;
102 hdrFieldBytes = BitBufferHelper.getBits(data, startOffset,
104 } catch (BufferException e) {
105 throw new PacketException(e.getMessage());
109 * Store the raw read value, checks the payload type and set the
110 * payloadClass accordingly
112 this.setHeaderField(hdrField, hdrFieldBytes);
114 if (logger.isTraceEnabled()) {
115 logger.trace("{}: {}: {} (offset {} bitsize {})",
116 new Object[] { this.getClass().getSimpleName(), hdrField,
117 HexEncode.bytesToHexString(hdrFieldBytes),
118 startOffset, numBits });
122 // Deserialize the payload now
123 int payloadStart = startOffset + numBits;
124 int payloadSize = data.length * NetUtils.NumBitsInAByte - payloadStart;
126 if (payloadClass != null) {
128 payload = payloadClass.newInstance();
129 } catch (Exception e) {
130 throw new RuntimeException(
131 "Error parsing payload for Ethernet packet", e);
133 payload.deserialize(data, payloadStart, payloadSize);
134 payload.setParent(this);
137 * The payload class was not set, it means no class for parsing
138 * this payload is present. Let's store the raw payload if any.
140 int start = payloadStart / NetUtils.NumBitsInAByte;
141 int stop = start + payloadSize / NetUtils.NumBitsInAByte;
142 rawPayload = Arrays.copyOfRange(data, start, stop);
146 // Take care of computation that can be done only after deserialization
147 postDeserializeCustomOperation(data, payloadStart - getHeaderSize());
153 * This method serializes the header and payload from the respective
154 * packet class, into a single stream of bytes to be sent on the wire
156 * @return The byte array representing the serialized Packet
157 * @throws PacketException
159 public byte[] serialize() throws PacketException {
161 // Acquire or compute the serialized payload
162 byte[] payloadBytes = null;
163 if (payload != null) {
164 payloadBytes = payload.serialize();
165 } else if (rawPayload != null) {
166 payloadBytes = rawPayload;
168 int payloadSize = (payloadBytes == null) ? 0 : payloadBytes.length;
170 // Allocate the buffer to contain the full (header + payload) packet
171 int headerSize = this.getHeaderSize() / NetUtils.NumBitsInAByte;
172 byte packetBytes[] = new byte[headerSize + payloadSize];
173 if (payloadBytes != null) {
174 System.arraycopy(payloadBytes, 0, packetBytes, headerSize, payloadSize);
177 // Serialize this packet header, field by field
178 for (Map.Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
180 String field = pairs.getKey();
181 byte[] fieldBytes = hdrFieldsMap.get(field);
182 // Let's skip optional fields when not set
183 if (fieldBytes != null) {
185 BitBufferHelper.setBytes(packetBytes, fieldBytes,
186 getfieldOffset(field), getfieldnumBits(field));
187 } catch (BufferException e) {
188 throw new PacketException(e.getMessage());
193 // Perform post serialize operations (like checksum computation)
194 postSerializeCustomOperation(packetBytes);
196 if (logger.isTraceEnabled()) {
197 logger.trace("{}: {}", this.getClass().getSimpleName(),
198 HexEncode.bytesToHexString(packetBytes));
205 * This method gets called at the end of the serialization process It is
206 * intended for the child packets to insert some custom data into the output
207 * byte stream which cannot be done or cannot be done efficiently during the
208 * normal Packet.serialize() path. An example is the checksum computation
211 * @param byte[] - serialized bytes
212 * @throws PacketException
214 protected void postSerializeCustomOperation(byte[] myBytes)
215 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 byte[] data The byte stream representing the Ethernet frame
227 * @param int startBitOffset The bit offset from where the byte array corresponding to this Packet starts in the frame
228 * @throws PacketException
230 protected void postDeserializeCustomOperation(byte[] data, int startBitOffset)
231 throws PacketException {
236 * Gets the header length in bits
238 * @return int the header length in bits
240 public int getHeaderSize() {
243 * We need to iterate over the fields that were read in the frame
244 * (hdrFieldsMap) not all the possible ones described in
245 * hdrFieldCoordMap. For ex, 802.1Q may or may not be there
247 for (Map.Entry<String, byte[]> fieldEntry : hdrFieldsMap.entrySet()) {
248 if (fieldEntry.getValue() != null) {
249 String field = fieldEntry.getKey();
250 size += getfieldnumBits(field);
257 * This method fetches the start bit offset for header field specified by
258 * 'fieldname'. The offset is present in the hdrFieldCoordMap of the
259 * respective packet class
263 * @return Integer - startOffset of the requested field
265 public int getfieldOffset(String fieldName) {
266 return hdrFieldCoordMap.get(fieldName).getLeft();
270 * This method fetches the number of bits for header field specified by
271 * 'fieldname'. The numBits are present in the hdrFieldCoordMap of the
272 * respective packet class
276 * @return Integer - number of bits of the requested field
278 public int getfieldnumBits(String fieldName) {
279 return hdrFieldCoordMap.get(fieldName).getRight();
283 public String toString() {
284 StringBuilder ret = new StringBuilder();
285 ret.append(this.getClass().getSimpleName());
287 for (String field : hdrFieldCoordMap.keySet()) {
288 byte[] value = hdrFieldsMap.get(field);
291 ret.append(HexEncode.bytesToHexString(value));
294 ret.replace(ret.length()-2, ret.length()-1, "]");
295 return ret.toString();
299 * Returns the raw payload carried by this packet in case payload was not
300 * parsed. Caller can call this function in case the getPaylod() returns null.
302 * @return The raw payload if not parsable as an array of bytes, null otherwise
304 public byte[] getRawPayload() {
309 * Set a raw payload in the packet class
311 * @param payload The raw payload as byte array
313 public void setRawPayload(byte[] payload) {
314 this.rawPayload = Arrays.copyOf(payload, payload.length);
318 * Return whether the deserialized packet is to be considered corrupted.
319 * This is the case when the checksum computed after reconstructing the
320 * packet received from wire is not equal to the checksum read from the
321 * stream. For the Packet class which do not have a checksum field, this
322 * function will always return false.
325 * @return true if the deserialized packet's recomputed checksum is not
326 * equal to the packet carried checksum
328 public boolean isCorrupted() {
333 public int hashCode() {
334 final int prime = 31;
335 int result = super.hashCode();
336 result = prime * result
337 + ((this.hdrFieldsMap == null) ? 0 : hdrFieldsMap.hashCode());
342 public boolean equals(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 if (hdrFieldsMap != null && other.hdrFieldsMap != null) {
357 for (String field : hdrFieldsMap.keySet()) {
358 if (!Arrays.equals(hdrFieldsMap.get(field), other.hdrFieldsMap.get(field))) {
369 * Adds to the passed Match this packet's header fields
372 * The Match object to populate
374 public void populateMatch(Match match) {
375 // To be overridden by derived packet classes which have well known
376 // header fields so that Packet.getMatch would return desired result
380 * Returns the Match object containing this packet and its payload
381 * encapsulated packets' header fields
383 * @return The Match containing the header fields of this packet and of its
384 * payload encapsulated packets
386 public Match getMatch() {
387 Match match = new Match();
388 Packet packet = this;
389 while (packet != null) {
390 packet.populateMatch(match);
391 packet = packet.getPayload();