Replace logger and log by LOG
[controller.git] / opendaylight / commons / liblldp / src / main / java / org / opendaylight / controller / liblldp / Packet.java
1 /*
2  * Copyright (c) 2013, 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.controller.liblldp;
10
11 import java.util.Arrays;
12 import java.util.Map;
13 import java.util.Map.Entry;
14
15 import org.apache.commons.lang3.tuple.Pair;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18
19 /**
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
22  * deserialize
23  */
24
25 public abstract class Packet {
26     protected static final Logger LOG = LoggerFactory.getLogger(Packet.class);
27     // Access level granted to this packet
28     protected boolean writeAccess;
29     // When deserialized from wire, packet could result corrupted
30     protected boolean corrupted;
31     // The packet that encapsulate this packet
32     protected Packet parent;
33     // The packet encapsulated by this packet
34     protected Packet payload;
35     // The unparsed raw payload carried by this packet
36     protected byte[] rawPayload;
37     // Bit coordinates of packet header fields
38     protected Map<String, Pair<Integer, Integer>> hdrFieldCoordMap;
39     // Header fields values: Map<FieldName,Value>
40     protected Map<String, byte[]> hdrFieldsMap;
41     // The class of the encapsulated packet object
42     protected Class<? extends Packet> payloadClass;
43
44     public Packet() {
45         writeAccess = false;
46         corrupted = false;
47     }
48
49     public Packet(final boolean writeAccess) {
50         this.writeAccess = writeAccess;
51         corrupted = false;
52     }
53
54     public Packet getParent() {
55         return parent;
56     }
57
58     public Packet getPayload() {
59         return payload;
60     }
61
62     public void setParent(final Packet parent) {
63         this.parent = parent;
64     }
65
66     public void setPayload(final Packet payload) {
67         this.payload = payload;
68     }
69
70     public void setHeaderField(final String headerField, final byte[] readValue) {
71         hdrFieldsMap.put(headerField, readValue);
72     }
73
74     /**
75      * This method deserializes the data bits obtained from the wire into the
76      * respective header and payload which are of type Packet
77      *
78      * @param data - data from wire to deserialize
79      * @param bitOffset bit position where packet header starts in data
80      *        array
81      * @param size size of packet in bits
82      * @return Packet
83      * @throws PacketException
84      */
85     public Packet deserialize(final byte[] data, final int bitOffset, final int size)
86             throws PacketException {
87
88         // Deserialize the header fields one by one
89         int startOffset = 0, numBits = 0;
90         for (Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
91                 .entrySet()) {
92             String hdrField = pairs.getKey();
93             startOffset = bitOffset + this.getfieldOffset(hdrField);
94             numBits = this.getfieldnumBits(hdrField);
95
96             byte[] hdrFieldBytes = null;
97             try {
98                 hdrFieldBytes = BitBufferHelper.getBits(data, startOffset,
99                         numBits);
100             } catch (final BufferException e) {
101                 throw new PacketException(e.getMessage());
102             }
103
104             /*
105              * Store the raw read value, checks the payload type and set the
106              * payloadClass accordingly
107              */
108             this.setHeaderField(hdrField, hdrFieldBytes);
109
110             if (LOG.isTraceEnabled()) {
111                 LOG.trace("{}: {}: {} (offset {} bitsize {})",
112                         new Object[] { this.getClass().getSimpleName(), hdrField,
113                         HexEncode.bytesToHexString(hdrFieldBytes),
114                         startOffset, numBits });
115             }
116         }
117
118         // Deserialize the payload now
119         int payloadStart = startOffset + numBits;
120         int payloadSize = data.length * NetUtils.NumBitsInAByte - payloadStart;
121
122         if (payloadClass != null) {
123             try {
124                 payload = payloadClass.newInstance();
125             } catch (final Exception e) {
126                 throw new RuntimeException(
127                         "Error parsing payload for Ethernet packet", e);
128             }
129             payload.deserialize(data, payloadStart, payloadSize);
130             payload.setParent(this);
131         } else {
132             /*
133              *  The payload class was not set, it means no class for parsing
134              *  this payload is present. Let's store the raw payload if any.
135              */
136             int start = payloadStart / NetUtils.NumBitsInAByte;
137             int stop = start + payloadSize / NetUtils.NumBitsInAByte;
138             rawPayload = Arrays.copyOfRange(data, start, stop);
139         }
140
141
142         // Take care of computation that can be done only after deserialization
143         postDeserializeCustomOperation(data, payloadStart - getHeaderSize());
144
145         return this;
146     }
147
148     /**
149      * This method serializes the header and payload from the respective
150      * packet class, into a single stream of bytes to be sent on the wire
151      *
152      * @return The byte array representing the serialized Packet
153      * @throws PacketException
154      */
155     public byte[] serialize() throws PacketException {
156
157         // Acquire or compute the serialized payload
158         byte[] payloadBytes = null;
159         if (payload != null) {
160             payloadBytes = payload.serialize();
161         } else if (rawPayload != null) {
162             payloadBytes = rawPayload;
163         }
164         int payloadSize = payloadBytes == null ? 0 : payloadBytes.length;
165
166         // Allocate the buffer to contain the full (header + payload) packet
167         int headerSize = this.getHeaderSize() / NetUtils.NumBitsInAByte;
168         byte packetBytes[] = new byte[headerSize + payloadSize];
169         if (payloadBytes != null) {
170             System.arraycopy(payloadBytes, 0, packetBytes, headerSize, payloadSize);
171         }
172
173         // Serialize this packet header, field by field
174         for (Map.Entry<String, Pair<Integer, Integer>> pairs : hdrFieldCoordMap
175                 .entrySet()) {
176             String field = pairs.getKey();
177             byte[] fieldBytes = hdrFieldsMap.get(field);
178             // Let's skip optional fields when not set
179             if (fieldBytes != null) {
180                 try {
181                     BitBufferHelper.setBytes(packetBytes, fieldBytes,
182                             getfieldOffset(field), getfieldnumBits(field));
183                 } catch (final BufferException e) {
184                     throw new PacketException(e.getMessage());
185                 }
186             }
187         }
188
189         // Perform post serialize operations (like checksum computation)
190         postSerializeCustomOperation(packetBytes);
191
192         if (LOG.isTraceEnabled()) {
193             LOG.trace("{}: {}", this.getClass().getSimpleName(),
194                     HexEncode.bytesToHexString(packetBytes));
195         }
196
197         return packetBytes;
198     }
199
200     /**
201      * This method gets called at the end of the serialization process It is
202      * intended for the child packets to insert some custom data into the output
203      * byte stream which cannot be done or cannot be done efficiently during the
204      * normal Packet.serialize() path. An example is the checksum computation
205      * for IPv4
206      *
207      * @param myBytes serialized bytes
208      * @throws PacketException
209      */
210     protected void postSerializeCustomOperation(byte[] myBytes)
211             throws PacketException {
212         // no op
213     }
214
215     /**
216      * This method re-computes the checksum of the bits received on the wire and
217      * validates it with the checksum in the bits received Since the computation
218      * of checksum varies based on the protocol, this method is overridden.
219      * Currently only IPv4 and ICMP do checksum computation and validation. TCP
220      * and UDP need to implement these if required.
221      *
222      * @param data The byte stream representing the Ethernet frame
223      * @param startBitOffset The bit offset from where the byte array corresponding to this Packet starts in the frame
224      * @throws PacketException
225      */
226     protected void postDeserializeCustomOperation(byte[] data, int startBitOffset)
227             throws PacketException {
228         // no op
229     }
230
231     /**
232      * Gets the header length in bits
233      *
234      * @return int the header length in bits
235      */
236     public int getHeaderSize() {
237         int size = 0;
238         /*
239          * We need to iterate over the fields that were read in the frame
240          * (hdrFieldsMap) not all the possible ones described in
241          * hdrFieldCoordMap. For ex, 802.1Q may or may not be there
242          */
243         for (Map.Entry<String, byte[]> fieldEntry : hdrFieldsMap.entrySet()) {
244             if (fieldEntry.getValue() != null) {
245                 String field = fieldEntry.getKey();
246                 size += getfieldnumBits(field);
247             }
248         }
249         return size;
250     }
251
252     /**
253      * This method fetches the start bit offset for header field specified by
254      * 'fieldname'. The offset is present in the hdrFieldCoordMap of the
255      * respective packet class
256      *
257      * @return Integer - startOffset of the requested field
258      */
259     public int getfieldOffset(final String fieldName) {
260         return hdrFieldCoordMap.get(fieldName).getLeft();
261     }
262
263     /**
264      * This method fetches the number of bits for header field specified by
265      * 'fieldname'. The numBits are present in the hdrFieldCoordMap of the
266      * respective packet class
267      *
268      * @return Integer - number of bits of the requested field
269      */
270     public int getfieldnumBits(final String fieldName) {
271         return hdrFieldCoordMap.get(fieldName).getRight();
272     }
273
274     @Override
275     public String toString() {
276         StringBuilder ret = new StringBuilder();
277         ret.append(this.getClass().getSimpleName());
278         ret.append(": [");
279         for (String field : hdrFieldCoordMap.keySet()) {
280             byte[] value = hdrFieldsMap.get(field);
281             ret.append(field);
282             ret.append(": ");
283             ret.append(HexEncode.bytesToHexString(value));
284             ret.append(", ");
285         }
286         ret.replace(ret.length()-2, ret.length()-1, "]");
287         return ret.toString();
288     }
289
290     /**
291      * Returns the raw payload carried by this packet in case payload was not
292      * parsed. Caller can call this function in case the getPaylod() returns null.
293      *
294      * @return The raw payload if not parsable as an array of bytes, null otherwise
295      */
296     public byte[] getRawPayload() {
297         return rawPayload;
298     }
299
300     /**
301      * Set a raw payload in the packet class
302      *
303      * @param payload The raw payload as byte array
304      */
305     public void setRawPayload(final byte[] payload) {
306         this.rawPayload = Arrays.copyOf(payload, payload.length);
307     }
308
309     /**
310      * Return whether the deserialized packet is to be considered corrupted.
311      * This is the case when the checksum computed after reconstructing the
312      * packet received from wire is not equal to the checksum read from the
313      * stream. For the Packet class which do not have a checksum field, this
314      * function will always return false.
315      *
316      *
317      * @return true if the deserialized packet's recomputed checksum is not
318      *         equal to the packet carried checksum
319      */
320     public boolean isCorrupted() {
321         return corrupted;
322     }
323
324     @Override
325     public int hashCode() {
326         final int prime = 31;
327         int result = super.hashCode();
328         result = prime * result
329                 + (this.hdrFieldsMap == null ? 0 : hdrFieldsMap.hashCode());
330         return result;
331     }
332
333     @Override
334     public boolean equals(final Object obj) {
335         if (this == obj) {
336             return true;
337         }
338         if (getClass() != obj.getClass()) {
339             return false;
340         }
341         Packet other = (Packet) obj;
342         if (hdrFieldsMap == other.hdrFieldsMap) {
343             return true;
344         }
345         if (hdrFieldsMap == null || other.hdrFieldsMap == null) {
346             return false;
347         }
348         if (hdrFieldsMap != null && other.hdrFieldsMap != null) {
349             for (String field : hdrFieldsMap.keySet()) {
350                 if (!Arrays.equals(hdrFieldsMap.get(field), other.hdrFieldsMap.get(field))) {
351                     return false;
352                 }
353             }
354         } else {
355             return false;
356         }
357         return true;
358     }
359 }