/*******************************************************************************
* Copyright (c) 2014 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Vincent Perot - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.pcap.core.protocol.pcap;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.pcap.core.packet.BadPacketException;
import org.eclipse.tracecompass.internal.pcap.core.packet.Packet;
import org.eclipse.tracecompass.internal.pcap.core.protocol.PcapProtocol;
import org.eclipse.tracecompass.internal.pcap.core.protocol.ethernet2.EthernetIIPacket;
import org.eclipse.tracecompass.internal.pcap.core.protocol.unknown.UnknownPacket;
import org.eclipse.tracecompass.internal.pcap.core.trace.PcapFile;
import org.eclipse.tracecompass.internal.pcap.core.trace.PcapFileValues;
import org.eclipse.tracecompass.internal.pcap.core.util.ConversionHelper;
import org.eclipse.tracecompass.internal.pcap.core.util.LinkTypeHelper;
import org.eclipse.tracecompass.internal.pcap.core.util.PcapTimestampScale;
import com.google.common.collect.ImmutableMap;
/**
* Class that represents a Pcap packet. This is the highest level of
* encapsulation.
*
* @author Vincent Perot
*/
public class PcapPacket extends Packet {
private static final int TIMESTAMP_MICROSECOND_MAX = 1000000;
private static final int TIMESTAMP_NANOSECOND_MAX = 1000000000;
private final @Nullable Packet fChildPacket;
private final @Nullable ByteBuffer fPayload;
private final long fTimestamp; // In microseconds
private final long fIncludedLength;
private final long fOriginalLength;
private final long fPacketIndex;
private @Nullable PcapEndpoint fSourceEndpoint;
private @Nullable PcapEndpoint fDestinationEndpoint;
private @Nullable Map<String, String> fFields;
/**
* Constructor of the Pcap Packet class.
*
* @param file
* The file that contains this packet.
* @param parent
* The parent packet of this packet (the encapsulating packet).
* @param header
* The header of the packet.
* @param payload
* The payload of this packet.
* @param index
* The index of the packet in the file.
* @throws BadPacketException
* Thrown when the Packet is erroneous.
*/
public PcapPacket(PcapFile file, @Nullable Packet parent, ByteBuffer header, @Nullable ByteBuffer payload, long index) throws BadPacketException {
super(file, parent, PcapProtocol.PCAP);
if (header.array().length < PcapFileValues.PACKET_HEADER_SIZE) {
fChildPacket = null;
throw new BadPacketException("The Pcap packet header is too small."); //$NON-NLS-1$
}
// The endpoints are lazy loaded. They are defined in the get*Endpoint()
// methods.
fSourceEndpoint = null;
fDestinationEndpoint = null;
fFields = null;
fPacketIndex = index;
// PcapPacket header in File endian
header.order(getPcapFile().getByteOrder());
header.position(0);
long timestampMostSignificant = ConversionHelper.unsignedIntToLong(header.getInt());
long timestampLeastSignificant = ConversionHelper.unsignedIntToLong(header.getInt());
switch (getTimestampScale()) {
case MICROSECOND:
if (timestampLeastSignificant > TIMESTAMP_MICROSECOND_MAX) {
fChildPacket = null;
throw new BadPacketException("The timestamp is erroneous."); //$NON-NLS-1$
}
fTimestamp = TIMESTAMP_MICROSECOND_MAX * timestampMostSignificant + timestampLeastSignificant;
break;
case NANOSECOND:
if (timestampLeastSignificant > TIMESTAMP_NANOSECOND_MAX) {
fChildPacket = null;
throw new BadPacketException("The timestamp is erroneous."); //$NON-NLS-1$
}
fTimestamp = TIMESTAMP_NANOSECOND_MAX * timestampMostSignificant + timestampLeastSignificant;
break;
default:
throw new IllegalArgumentException("The timestamp precision is not valid!"); //$NON-NLS-1$
}
fIncludedLength = ConversionHelper.unsignedIntToLong(header.getInt());
fOriginalLength = ConversionHelper.unsignedIntToLong(header.getInt());
// Set up payload
final ByteBuffer pcapPacket = payload;
if (pcapPacket == null) {
fChildPacket = null;
fPayload = null;
return;
}
pcapPacket.order(ByteOrder.BIG_ENDIAN);
pcapPacket.position(0);
fPayload = pcapPacket;
// Find Child Packet
fChildPacket = findChildPacket();
}
@Override
public @Nullable Packet getChildPacket() {
return fChildPacket;
}
@Override
public @Nullable ByteBuffer getPayload() {
return fPayload;
}
/**
* Getter method that returns the timestamp of this packet, in
* microseconds/nanoseconds relative to epoch.
*
* @return The timestamp of the packet.
*/
public long getTimestamp() {
return fTimestamp;
}
/**
* Getter method that returns the length in bytes of the packet that was
* included in the {@link PcapFile}.
*
* @return The included length of the packet.
*/
public long getIncludedLength() {
return fIncludedLength;
}
/**
* Getter method that returns the original length in bytes of the packet.
*
* @return The included length of the packet.
*/
public long getOriginalLength() {
return fOriginalLength;
}
/**
* Method that indicates if this packet was truncated at capture time.
*
* @return Whether the packet is truncated or not.
*/
public boolean isTruncated() {
return fIncludedLength != fOriginalLength;
}
/**
* Getter method that returns the index of the packet.
*
* @return The index of the packet.
*/
public long getIndex() {
return fPacketIndex;
}
@Override
public String toString() {
// TODO Decide if first capture is 0 or 1. Right now, it is 0.
String string = getProtocol().getName() + " " + fPacketIndex + //$NON-NLS-1$
": " + fOriginalLength + " bytes on wire, " + //$NON-NLS-1$ //$NON-NLS-2$
fIncludedLength + " bytes captured.\nArrival time: " + //$NON-NLS-1$
ConversionHelper.toGMTTime(fTimestamp, getTimestampScale()) + "\n"; //$NON-NLS-1$
final Packet child = fChildPacket;
if (child != null) {
return string + child.toString();
}
return string;
}
/**
* {@inheritDoc}
*
* See http://www.tcpdump.org/linktypes.html
*/
@Override
protected @Nullable Packet findChildPacket() throws BadPacketException {
@Nullable
ByteBuffer payload = fPayload;
if (payload == null) {
return null;
}
switch ((int) getPcapFile().getDataLinkType()) {
case LinkTypeHelper.LINKTYPE_ETHERNET:
return new EthernetIIPacket(getPcapFile(), this, payload);
default: // TODO add more protocols
return new UnknownPacket(getPcapFile(), this, payload);
}
}
@Override
public boolean validate() {
// Not yet implemented. ATM, we consider that all packets are valid.
// This is the case for all packets.
// TODO Implement it.
return true;
}
@Override
public PcapEndpoint getSourceEndpoint() {
PcapEndpoint endpoint = fSourceEndpoint;
if (endpoint == null) {
endpoint = new PcapEndpoint(this, true);
}
fSourceEndpoint = endpoint;
return fSourceEndpoint;
}
@Override
public PcapEndpoint getDestinationEndpoint() {
PcapEndpoint endpoint = fDestinationEndpoint;
if (endpoint == null) {
endpoint = new PcapEndpoint(this, false);
}
fDestinationEndpoint = endpoint;
return fDestinationEndpoint;
}
// TODO handle plural form correctly
// TODO microsec
@Override
public Map<String, String> getFields() {
Map<String, String> map = fFields;
if (map == null) {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
builder.put("Frame", String.valueOf(fPacketIndex)); //$NON-NLS-1$
builder.put("Frame Length", String.valueOf(fOriginalLength) + " bytes"); //$NON-NLS-1$ //$NON-NLS-2$
builder.put("Capture Length", String.valueOf(fIncludedLength) + " bytes"); //$NON-NLS-1$ //$NON-NLS-2$
builder.put("Capture Time", ConversionHelper.toGMTTime(fTimestamp, getTimestampScale())); //$NON-NLS-1$
fFields = builder.build();
return fFields;
}
return map;
}
@Override
public String getLocalSummaryString() {
return "Frame " + fPacketIndex + ": " + fOriginalLength + " bytes on wire, " + fIncludedLength + " bytes captured"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
@Override
protected String getSignificationString() {
return "New Frame: " + fOriginalLength + " bytes on wire"; //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
Packet child = fChildPacket;
if (child == null) {
result = prime * result;
} else {
result = prime * result + child.hashCode();
}
result = prime * result + (int) (fIncludedLength ^ (fIncludedLength >>> 32));
result = prime * result + (int) (fOriginalLength ^ (fOriginalLength >>> 32));
result = prime * result + (int) (fPacketIndex ^ (fPacketIndex >>> 32));
ByteBuffer payload = fPayload;
if (payload == null) {
result = prime * result;
} else {
result = prime * result + payload.hashCode();
}
result = prime * result + (int) (fTimestamp ^ (fTimestamp >>> 32));
return result;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcapPacket other = (PcapPacket) obj;
if (!Objects.equals(fChildPacket, other.fChildPacket)) {
return false;
}
if (fIncludedLength != other.fIncludedLength) {
return false;
}
if (fOriginalLength != other.fOriginalLength) {
return false;
}
if (fPacketIndex != other.fPacketIndex) {
return false;
}
if (!Objects.equals(fPayload, other.fPayload)) {
return false;
}
if (fTimestamp != other.fTimestamp) {
return false;
}
return true;
}
/**
* Getter method that returns the Timestamp precision of the packet.
*
* @return the Timestamp precision.
*/
public PcapTimestampScale getTimestampScale() {
return getPcapFile().getTimestampPrecision();
}
}