| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2014 Ericsson |
| 3 | * |
| 4 | * All rights reserved. This program and the accompanying materials are |
| 5 | * made available under the terms of the Eclipse Public License v1.0 which |
| 6 | * accompanies this distribution, and is available at |
| 7 | * http://www.eclipse.org/legal/epl-v10.html |
| 8 | * |
| 9 | * Contributors: |
| 10 | * Vincent Perot - Initial API and implementation |
| 11 | *******************************************************************************/ |
| 12 | |
| 13 | package org.eclipse.tracecompass.internal.pcap.core.protocol.pcap; |
| 14 | |
| 15 | import java.nio.ByteBuffer; |
| 16 | import java.nio.ByteOrder; |
| 17 | import java.util.Map; |
| 18 | |
| 19 | import org.eclipse.jdt.annotation.Nullable; |
| 20 | import org.eclipse.tracecompass.common.core.NonNullUtils; |
| 21 | import org.eclipse.tracecompass.internal.pcap.core.packet.BadPacketException; |
| 22 | import org.eclipse.tracecompass.internal.pcap.core.packet.Packet; |
| 23 | import org.eclipse.tracecompass.internal.pcap.core.protocol.PcapProtocol; |
| 24 | import org.eclipse.tracecompass.internal.pcap.core.protocol.ethernet2.EthernetIIPacket; |
| 25 | import org.eclipse.tracecompass.internal.pcap.core.protocol.unknown.UnknownPacket; |
| 26 | import org.eclipse.tracecompass.internal.pcap.core.trace.PcapFile; |
| 27 | import org.eclipse.tracecompass.internal.pcap.core.trace.PcapFileValues; |
| 28 | import org.eclipse.tracecompass.internal.pcap.core.util.ConversionHelper; |
| 29 | import org.eclipse.tracecompass.internal.pcap.core.util.LinkTypeHelper; |
| 30 | import org.eclipse.tracecompass.internal.pcap.core.util.PcapTimestampScale; |
| 31 | |
| 32 | import com.google.common.collect.ImmutableMap; |
| 33 | |
| 34 | /** |
| 35 | * Class that represents a Pcap packet. This is the highest level of |
| 36 | * encapsulation. |
| 37 | * |
| 38 | * @author Vincent Perot |
| 39 | */ |
| 40 | public class PcapPacket extends Packet { |
| 41 | |
| 42 | private static final int TIMESTAMP_MICROSECOND_MAX = 1000000; |
| 43 | private static final int TIMESTAMP_NANOSECOND_MAX = 1000000000; |
| 44 | |
| 45 | private final @Nullable Packet fChildPacket; |
| 46 | private final @Nullable ByteBuffer fPayload; |
| 47 | |
| 48 | private final long fTimestamp; // In microseconds |
| 49 | private final long fIncludedLength; |
| 50 | private final long fOriginalLength; |
| 51 | private final long fPacketIndex; |
| 52 | |
| 53 | private @Nullable PcapEndpoint fSourceEndpoint; |
| 54 | private @Nullable PcapEndpoint fDestinationEndpoint; |
| 55 | |
| 56 | private @Nullable Map<String, String> fFields; |
| 57 | |
| 58 | /** |
| 59 | * Constructor of the Pcap Packet class. |
| 60 | * |
| 61 | * @param file |
| 62 | * The file that contains this packet. |
| 63 | * @param parent |
| 64 | * The parent packet of this packet (the encapsulating packet). |
| 65 | * @param header |
| 66 | * The header of the packet. |
| 67 | * @param payload |
| 68 | * The payload of this packet. |
| 69 | * @param index |
| 70 | * The index of the packet in the file. |
| 71 | * @throws BadPacketException |
| 72 | * Thrown when the Packet is erroneous. |
| 73 | */ |
| 74 | public PcapPacket(PcapFile file, @Nullable Packet parent, ByteBuffer header, @Nullable ByteBuffer payload, long index) throws BadPacketException { |
| 75 | super(file, parent, PcapProtocol.PCAP); |
| 76 | |
| 77 | if (header.array().length < PcapFileValues.PACKET_HEADER_SIZE) { |
| 78 | fChildPacket = null; |
| 79 | throw new BadPacketException("The Pcap packet header is too small."); //$NON-NLS-1$ |
| 80 | } |
| 81 | |
| 82 | // The endpoints are lazy loaded. They are defined in the get*Endpoint() |
| 83 | // methods. |
| 84 | fSourceEndpoint = null; |
| 85 | fDestinationEndpoint = null; |
| 86 | |
| 87 | fFields = null; |
| 88 | |
| 89 | fPacketIndex = index; |
| 90 | |
| 91 | // PcapPacket header in File endian |
| 92 | header.order(getPcapFile().getByteOrder()); |
| 93 | header.position(0); |
| 94 | long timestampMostSignificant = ConversionHelper.unsignedIntToLong(header.getInt()); |
| 95 | long timestampLeastSignificant = ConversionHelper.unsignedIntToLong(header.getInt()); |
| 96 | |
| 97 | switch (getTimestampScale()) { |
| 98 | case MICROSECOND: |
| 99 | if (timestampLeastSignificant > TIMESTAMP_MICROSECOND_MAX) { |
| 100 | fChildPacket = null; |
| 101 | throw new BadPacketException("The timestamp is erroneous."); //$NON-NLS-1$ |
| 102 | } |
| 103 | fTimestamp = TIMESTAMP_MICROSECOND_MAX * timestampMostSignificant + timestampLeastSignificant; |
| 104 | break; |
| 105 | case NANOSECOND: |
| 106 | if (timestampLeastSignificant > TIMESTAMP_NANOSECOND_MAX) { |
| 107 | fChildPacket = null; |
| 108 | throw new BadPacketException("The timestamp is erroneous."); //$NON-NLS-1$ |
| 109 | } |
| 110 | fTimestamp = TIMESTAMP_NANOSECOND_MAX * timestampMostSignificant + timestampLeastSignificant; |
| 111 | break; |
| 112 | default: |
| 113 | throw new IllegalArgumentException("The timestamp precision is not valid!"); //$NON-NLS-1$ |
| 114 | } |
| 115 | |
| 116 | fIncludedLength = ConversionHelper.unsignedIntToLong(header.getInt()); |
| 117 | fOriginalLength = ConversionHelper.unsignedIntToLong(header.getInt()); |
| 118 | |
| 119 | // Set up payload |
| 120 | final ByteBuffer pcapPacket = payload; |
| 121 | if (pcapPacket == null) { |
| 122 | fChildPacket = null; |
| 123 | fPayload = null; |
| 124 | return; |
| 125 | } |
| 126 | |
| 127 | pcapPacket.order(ByteOrder.BIG_ENDIAN); |
| 128 | pcapPacket.position(0); |
| 129 | fPayload = pcapPacket; |
| 130 | |
| 131 | // Find Child Packet |
| 132 | fChildPacket = findChildPacket(); |
| 133 | |
| 134 | } |
| 135 | |
| 136 | @Override |
| 137 | public @Nullable Packet getChildPacket() { |
| 138 | return fChildPacket; |
| 139 | } |
| 140 | |
| 141 | @Override |
| 142 | public @Nullable ByteBuffer getPayload() { |
| 143 | return fPayload; |
| 144 | } |
| 145 | |
| 146 | /** |
| 147 | * Getter method that returns the timestamp of this packet, in microseconds/nanoseconds |
| 148 | * relative to epoch. |
| 149 | * |
| 150 | * @return The timestamp of the packet. |
| 151 | */ |
| 152 | public long getTimestamp() { |
| 153 | return fTimestamp; |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Getter method that returns the length in bytes of the packet that was |
| 158 | * included in the {@link PcapFile}. |
| 159 | * |
| 160 | * @return The included length of the packet. |
| 161 | */ |
| 162 | public long getIncludedLength() { |
| 163 | return fIncludedLength; |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Getter method that returns the original length in bytes of the packet. |
| 168 | * |
| 169 | * @return The included length of the packet. |
| 170 | */ |
| 171 | public long getOriginalLength() { |
| 172 | return fOriginalLength; |
| 173 | } |
| 174 | |
| 175 | /** |
| 176 | * Method that indicates if this packet was truncated at capture time. |
| 177 | * |
| 178 | * @return Whether the packet is truncated or not. |
| 179 | */ |
| 180 | public boolean isTruncated() { |
| 181 | return fIncludedLength != fOriginalLength; |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Getter method that returns the index of the packet. |
| 186 | * |
| 187 | * @return The index of the packet. |
| 188 | */ |
| 189 | public long getIndex() { |
| 190 | return fPacketIndex; |
| 191 | } |
| 192 | |
| 193 | @Override |
| 194 | public String toString() { |
| 195 | // TODO Decide if first capture is 0 or 1. Right now, it is 0. |
| 196 | String string = getProtocol().getName() + " " + fPacketIndex + //$NON-NLS-1$ |
| 197 | ": " + fOriginalLength + " bytes on wire, " + //$NON-NLS-1$ //$NON-NLS-2$ |
| 198 | fIncludedLength + " bytes captured.\nArrival time: " + //$NON-NLS-1$ |
| 199 | ConversionHelper.toGMTTime(fTimestamp, getTimestampScale()) + "\n"; //$NON-NLS-1$ |
| 200 | |
| 201 | final Packet child = fChildPacket; |
| 202 | if (child != null) { |
| 203 | return string + child.toString(); |
| 204 | } |
| 205 | return string; |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * {@inheritDoc} |
| 210 | * |
| 211 | * See http://www.tcpdump.org/linktypes.html |
| 212 | */ |
| 213 | @Override |
| 214 | protected @Nullable Packet findChildPacket() throws BadPacketException { |
| 215 | @Nullable |
| 216 | ByteBuffer payload = fPayload; |
| 217 | if (payload == null) { |
| 218 | return null; |
| 219 | } |
| 220 | |
| 221 | switch ((int) getPcapFile().getDataLinkType()) { |
| 222 | case LinkTypeHelper.LINKTYPE_ETHERNET: |
| 223 | return new EthernetIIPacket(getPcapFile(), this, payload); |
| 224 | default: // TODO add more protocols |
| 225 | return new UnknownPacket(getPcapFile(), this, payload); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | @Override |
| 230 | public boolean validate() { |
| 231 | // Not yet implemented. ATM, we consider that all packets are valid. |
| 232 | // This is the case for all packets. |
| 233 | // TODO Implement it. |
| 234 | return true; |
| 235 | } |
| 236 | |
| 237 | @Override |
| 238 | public PcapEndpoint getSourceEndpoint() { |
| 239 | PcapEndpoint endpoint = fSourceEndpoint; |
| 240 | if (endpoint == null) { |
| 241 | endpoint = new PcapEndpoint(this, true); |
| 242 | } |
| 243 | fSourceEndpoint = endpoint; |
| 244 | return fSourceEndpoint; |
| 245 | } |
| 246 | |
| 247 | @Override |
| 248 | public PcapEndpoint getDestinationEndpoint() { |
| 249 | PcapEndpoint endpoint = fDestinationEndpoint; |
| 250 | |
| 251 | if (endpoint == null) { |
| 252 | endpoint = new PcapEndpoint(this, false); |
| 253 | } |
| 254 | fDestinationEndpoint = endpoint; |
| 255 | return fDestinationEndpoint; |
| 256 | } |
| 257 | |
| 258 | // TODO handle plural form correctly |
| 259 | // TODO microsec |
| 260 | @Override |
| 261 | public Map<String, String> getFields() { |
| 262 | Map<String, String> map = fFields; |
| 263 | if (map == null) { |
| 264 | ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| 265 | builder.put("Frame", String.valueOf(fPacketIndex)); //$NON-NLS-1$ |
| 266 | builder.put("Frame Length", String.valueOf(fOriginalLength) + " bytes"); //$NON-NLS-1$ //$NON-NLS-2$ |
| 267 | builder.put("Capture Length", String.valueOf(fIncludedLength) + " bytes"); //$NON-NLS-1$ //$NON-NLS-2$ |
| 268 | builder.put("Capture Time", ConversionHelper.toGMTTime(fTimestamp, getTimestampScale())); //$NON-NLS-1$ |
| 269 | |
| 270 | fFields = NonNullUtils.checkNotNull(builder.build()); |
| 271 | return fFields; |
| 272 | } |
| 273 | return map; |
| 274 | } |
| 275 | |
| 276 | @Override |
| 277 | public String getLocalSummaryString() { |
| 278 | return "Frame " + fPacketIndex + ": " + fOriginalLength + " bytes on wire, " + fIncludedLength + " bytes captured"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| 279 | } |
| 280 | |
| 281 | @Override |
| 282 | protected String getSignificationString() { |
| 283 | return "New Frame: " + fOriginalLength + " bytes on wire"; //$NON-NLS-1$ //$NON-NLS-2$ |
| 284 | } |
| 285 | |
| 286 | @Override |
| 287 | public int hashCode() { |
| 288 | final int prime = 31; |
| 289 | int result = 1; |
| 290 | |
| 291 | Packet child = fChildPacket; |
| 292 | if (child == null) { |
| 293 | result = prime * result; |
| 294 | } else { |
| 295 | result = prime * result + child.hashCode(); |
| 296 | } |
| 297 | |
| 298 | result = prime * result + (int) (fIncludedLength ^ (fIncludedLength >>> 32)); |
| 299 | result = prime * result + (int) (fOriginalLength ^ (fOriginalLength >>> 32)); |
| 300 | result = prime * result + (int) (fPacketIndex ^ (fPacketIndex >>> 32)); |
| 301 | |
| 302 | ByteBuffer payload = fPayload; |
| 303 | if (payload == null) { |
| 304 | result = prime * result; |
| 305 | } else { |
| 306 | result = prime * result + payload.hashCode(); |
| 307 | } |
| 308 | |
| 309 | result = prime * result + (int) (fTimestamp ^ (fTimestamp >>> 32)); |
| 310 | return result; |
| 311 | } |
| 312 | |
| 313 | @Override |
| 314 | public boolean equals(@Nullable Object obj) { |
| 315 | if (this == obj) { |
| 316 | return true; |
| 317 | } |
| 318 | if (obj == null) { |
| 319 | return false; |
| 320 | } |
| 321 | if (getClass() != obj.getClass()) { |
| 322 | return false; |
| 323 | } |
| 324 | PcapPacket other = (PcapPacket) obj; |
| 325 | if(!NonNullUtils.equalsNullable(fChildPacket, other.fChildPacket)){ |
| 326 | return false; |
| 327 | } |
| 328 | if (fIncludedLength != other.fIncludedLength) { |
| 329 | return false; |
| 330 | } |
| 331 | if (fOriginalLength != other.fOriginalLength) { |
| 332 | return false; |
| 333 | } |
| 334 | if (fPacketIndex != other.fPacketIndex) { |
| 335 | return false; |
| 336 | } |
| 337 | if(!NonNullUtils.equalsNullable(fPayload, other.fPayload)) { |
| 338 | return false; |
| 339 | } |
| 340 | if (fTimestamp != other.fTimestamp) { |
| 341 | return false; |
| 342 | } |
| 343 | return true; |
| 344 | } |
| 345 | |
| 346 | /** |
| 347 | * Getter method that returns the Timestamp precision of the packet. |
| 348 | * |
| 349 | * @return the Timestamp precision. |
| 350 | */ |
| 351 | public PcapTimestampScale getTimestampScale() { |
| 352 | return getPcapFile().getTimestampPrecision(); |
| 353 | } |
| 354 | } |