blob: 2be05a62001b831992ef980e24babb89797f9be7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2019 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Vincent Perot - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.pcap.core.protocol.tcp;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.common.core.NonNullUtils;
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.unknown.UnknownPacket;
import org.eclipse.tracecompass.internal.pcap.core.trace.PcapFile;
import org.eclipse.tracecompass.internal.pcap.core.util.ConversionHelper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
/**
* Class that represents a TCP packet.
*
* @author Vincent Perot
*/
public class TCPPacket extends Packet {
private final @Nullable Packet fChildPacket;
private final @Nullable ByteBuffer fPayload;
private final int fSourcePort;
private final int fDestinationPort;
private final long fSequenceNumber;
private final long fAcknowledgmentNumber;
private final int fDataOffset; // in 4 bytes block
private final byte fReservedField;
private final boolean fNSFlag;
private final boolean fCWRFlag;
private final boolean fECEFlag;
private final boolean fURGFlag;
private final boolean fACKFlag;
private final boolean fPSHFlag;
private final boolean fRSTFlag;
private final boolean fSYNFlag;
private final boolean fFINFlag;
private final int fWindowSize;
private final int fChecksum;
private final int fUrgentPointer;
private final byte @Nullable [] fOptions; // TODO Interpret options.
private @Nullable TCPEndpoint fSourceEndpoint;
private @Nullable TCPEndpoint fDestinationEndpoint;
private @Nullable Map<String, String> fFields;
/**
* Constructor of the TCP Packet class.
*
* @param file
* The file that contains this packet.
* @param parent
* The parent packet of this packet (the encapsulating packet).
* @param packet
* The entire packet (header and payload).
* @throws BadPacketException
* Thrown when the packet is erroneous.
*/
public TCPPacket(PcapFile file, @Nullable Packet parent, ByteBuffer packet) throws BadPacketException {
super(file, parent, PcapProtocol.TCP);
// The endpoints are lazy loaded. They are defined in the get*Endpoint()
// methods.
fSourceEndpoint = null;
fDestinationEndpoint = null;
fFields = null;
packet.order(ByteOrder.BIG_ENDIAN);
packet.position(0);
fSourcePort = ConversionHelper.unsignedShortToInt(packet.getShort());
fDestinationPort = ConversionHelper.unsignedShortToInt(packet.getShort());
fSequenceNumber = ConversionHelper.unsignedIntToLong(packet.getInt());
fAcknowledgmentNumber = ConversionHelper.unsignedIntToLong(packet.getInt());
byte storage = packet.get();
fDataOffset = ((storage & 0b11110000) >>> 4) & 0x000000FF;
fReservedField = (byte) ((storage & 0b00001110) >>> 1);
fNSFlag = isBitSet(storage, 0);
storage = packet.get();
fCWRFlag = isBitSet(storage, 7);
fECEFlag = isBitSet(storage, 6);
fURGFlag = isBitSet(storage, 5);
fACKFlag = isBitSet(storage, 4);
fPSHFlag = isBitSet(storage, 3);
fRSTFlag = isBitSet(storage, 2);
fSYNFlag = isBitSet(storage, 1);
fFINFlag = isBitSet(storage, 0);
fWindowSize = ConversionHelper.unsignedShortToInt(packet.getShort());
fChecksum = ConversionHelper.unsignedShortToInt(packet.getShort());
fUrgentPointer = ConversionHelper.unsignedShortToInt(packet.getShort());
// Get options if any
if (fDataOffset > TCPValues.DEFAULT_HEADER_LENGTH) {
fOptions = new byte[(fDataOffset - TCPValues.DEFAULT_HEADER_LENGTH) * TCPValues.BLOCK_SIZE];
packet.get(fOptions);
} else {
fOptions = null;
}
// Get payload if any.
if (packet.remaining() > 0) {
ByteBuffer payload = packet.slice();
payload.order(ByteOrder.BIG_ENDIAN);
fPayload = payload;
} else {
fPayload = null;
}
// find child packet
fChildPacket = findChildPacket();
}
@Override
public @Nullable Packet getChildPacket() {
return fChildPacket;
}
@Override
public @Nullable ByteBuffer getPayload() {
return fPayload;
}
/**
* {@inheritDoc}
*
* See http://www.iana.org/assignments/service-names-port-numbers/service-
* names-port-numbers.xhtml or
* http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
*/
@Override
protected @Nullable Packet findChildPacket() throws BadPacketException {
// TODO implement further protocols and update this
ByteBuffer payload = fPayload;
if (payload == null) {
return null;
}
return new UnknownPacket(getPcapFile(), this, payload);
}
@Override
public String toString() {
final ByteBuffer payload = fPayload;
int length = 0;
if (payload != null) {
length = payload.limit();
}
String flagString = ""; // TODO Finish it. Im just too lazy. //$NON-NLS-1$
String string = getProtocol().getName() + ", Source Port: " + fSourcePort + ", Destination Port: " + fDestinationPort + //$NON-NLS-1$ //$NON-NLS-2$
"\nSequence Number: " + fSequenceNumber + ", Acknowledgment Number: " + fAcknowledgmentNumber + //$NON-NLS-1$ //$NON-NLS-2$
"\nHeader length: " + fDataOffset * TCPValues.BLOCK_SIZE + " bytes, Data length: " + length + //$NON-NLS-1$ //$NON-NLS-2$
"\n" + flagString + "Window size value: " + fWindowSize + ", Urgent Pointer: " + String.format("%s%04x", "0x", fUrgentPointer) + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
"\nChecksum: " + String.format("%s%04x", "0x", fChecksum) + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
final Packet child = fChildPacket;
if (child != null) {
return string + child.toString();
}
return string;
}
/**
* Getter method that returns the TCP Source Port.
*
* @return The source Port.
*/
public int getSourcePort() {
return fSourcePort;
}
/**
* Getter method that returns the TCP Destination Port.
*
* @return The destination Port.
*/
public int getDestinationPort() {
return fDestinationPort;
}
/**
* Getter method that returns the Sequence Number. The sequence number has a
* dual role:
* <ul>
* <li>If the SYN flag is set (1), then this is the initial sequence number.
* The sequence number of the actual first data byte and the acknowledged
* number in the corresponding ACK are then this sequence number plus 1.</li>
* <li>If the SYN flag is clear (0), then this is the accumulated sequence
* number of the first data byte of this segment for the current session.</li>
* </ul>
*
* Source: http://en.wikipedia.org/wiki/Transmission_Control_Protocol
*
* @return The Sequence Number.
*/
public long getSequenceNumber() {
return fSequenceNumber;
}
/**
* Getter method that returns the Acknowledgment Number.
*
* If the ACK flag is set then the value of this field is the next sequence
* number that the receiver is expecting. This acknowledges receipt of all
* prior bytes (if any). The first ACK sent by each end acknowledges the
* other end's initial sequence number itself, but no data.
*
* Source: http://en.wikipedia.org/wiki/Transmission_Control_Protocol
*
* @return The Acknowledgment Number.
*/
public long getAcknowledgmentNumber() {
return fAcknowledgmentNumber;
}
/**
* Getter method that returns the size of the TCP header in 4 bytes data
* block. The minimum size is 5 words and the maximum is 15 words.
*
* @return The Data Offset.
*/
public int getDataOffset() {
return fDataOffset;
}
/**
* Getter method that returns the Reserved field. This field is for future
* use and should always be zero. In this library, it is used as a mean to
* verify the validity of a TCP packet.
*
* @return The Reserved Field.
*/
public byte getReservedField() {
return fReservedField;
}
/**
* Getter method that returns the state of the NS flag.
*
* @return The state of the NS flag.
*/
public boolean isNSFlagSet() {
return fNSFlag;
}
/**
* Getter method that returns the state of the CWR flag.
*
* @return The state of the CWR flag.
*/
public boolean isCongestionWindowReducedFlagSet() {
return fCWRFlag;
}
/**
* Getter method that returns the state of the ECE flag.
*
* @return The state of the ECE flag.
*/
public boolean isECNEchoFlagSet() {
return fECEFlag;
}
/**
* Getter method that returns the state of the URG flag.
*
* @return The state of the URG flag.
*/
public boolean isUrgentFlagSet() {
return fURGFlag;
}
/**
* Getter method that returns the state of the ACK flag.
*
* @return The state of the ACK flag.
*/
public boolean isAcknowledgeFlagSet() {
return fACKFlag;
}
/**
* Getter method that returns the state of the PSH flag.
*
* @return The state of the PSH flag.
*/
public boolean isPushFlagSet() {
return fPSHFlag;
}
/**
* Getter method that returns the state of the RST flag.
*
* @return The state of the RST flag.
*/
public boolean isResetFlagSet() {
return fRSTFlag;
}
/**
* Getter method that returns the state of the SYN flag.
*
* @return The state of the SYN flag.
*/
public boolean isSynchronizationFlagSet() {
return fSYNFlag;
}
/**
* Getter method that returns the state of the FIN flag.
*
* @return The state of the FIN flag.
*/
public boolean isFinalFlagSet() {
return fFINFlag;
}
/**
* Getter method that returns the size of the windows, in windows size unit
* (by default, bytes), that the sender of this packet is willing to
* receive.
*
* @return The Window Size.
*/
public int getWindowSize() {
return fWindowSize;
}
/**
* Getter method that returns the checksum of this packet. This checksum may
* be wrong if the packet is erroneous.
*
* @return The data and header checksum.
*/
public int getChecksum() {
return fChecksum;
}
/**
* Getter method that returns the Urgent Pointer. If the URG flag is set,
* this field is an offset from the sequence number indicating the last
* urgent data byte.
*
* @return The Urgent Pointer.
*/
public int getUrgentPointer() {
return fUrgentPointer;
}
/**
* Getter method that returns the options. This method returns null if no
* options are present.
*
* @return The options of the packet.
*/
public byte @Nullable [] getOptions() {
byte[] options = fOptions;
if (options == null) {
return null;
}
return Arrays.copyOf(options, options.length);
}
@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 TCPEndpoint getSourceEndpoint() {
@Nullable
TCPEndpoint endpoint = fSourceEndpoint;
if (endpoint == null) {
endpoint = new TCPEndpoint(this, true);
}
fSourceEndpoint = endpoint;
return fSourceEndpoint;
}
@Override
public TCPEndpoint getDestinationEndpoint() {
@Nullable
TCPEndpoint endpoint = fDestinationEndpoint;
if (endpoint == null) {
endpoint = new TCPEndpoint(this, false);
}
fDestinationEndpoint = endpoint;
return fDestinationEndpoint;
}
@Override
public Map<String, String> getFields() {
Map<String, String> map = fFields;
if (map == null) {
Builder<String, String> builder = ImmutableMap.<@NonNull String, @NonNull String> builder()
.put("Source Port", String.valueOf(fSourcePort)) //$NON-NLS-1$
.put("Destination Port", String.valueOf(fDestinationPort)) //$NON-NLS-1$
.put("Sequence Number", String.valueOf(fSequenceNumber)) //$NON-NLS-1$
.put("Acknowledgement Number", String.valueOf(fAcknowledgmentNumber)) //$NON-NLS-1$
.put("Length", String.valueOf(fDataOffset * TCPValues.BLOCK_SIZE) + " bytes") //$NON-NLS-1$ //$NON-NLS-2$
.put("ECN-Nonce Flag", String.valueOf(fNSFlag)) //$NON-NLS-1$
.put("Congestion Window Reduced Flag", String.valueOf(fCWRFlag)) //$NON-NLS-1$
.put("ECN-Echo Flag", String.valueOf(fECEFlag)) //$NON-NLS-1$
.put("Urgent Flag", String.valueOf(fURGFlag)) //$NON-NLS-1$
.put("ACK Flag", String.valueOf(fACKFlag)) //$NON-NLS-1$
.put("PSH Flag", String.valueOf(fPSHFlag)) //$NON-NLS-1$
.put("RST Flag", String.valueOf(fRSTFlag)) //$NON-NLS-1$
.put("SYN Flag", String.valueOf(fSYNFlag)) //$NON-NLS-1$
.put("FIN Flag", String.valueOf(fFINFlag)) //$NON-NLS-1$
.put("Window Size Value", String.valueOf(fWindowSize)) //$NON-NLS-1$
.put("Checksum", String.format("%s%04x", "0x", fChecksum)) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
.put("Urgent Pointer", String.format("%s%04x", "0x", fUrgentPointer)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
byte[] options = fOptions;
if (options == null) {
builder.put("Options", EMPTY_STRING); //$NON-NLS-1$
} else {
builder.put("Options", ConversionHelper.bytesToHex(options, true)); //$NON-NLS-1$
}
fFields = builder.build();
return fFields;
}
return map;
}
@Override
public String getLocalSummaryString() {
return "Src Port: " + fSourcePort + ", Dst Port: " + fDestinationPort + //$NON-NLS-1$ //$NON-NLS-2$
", Seq: " + fSequenceNumber + ", Ack: " + fAcknowledgmentNumber + //$NON-NLS-1$ //$NON-NLS-2$
", Len: " + (fDataOffset * TCPValues.BLOCK_SIZE); //$NON-NLS-1$
}
@Override
protected String getSignificationString() {
StringBuilder sb = new StringBuilder();
sb.append(fSourcePort)
.append(" > ") //$NON-NLS-1$
.append(fDestinationPort);
if (!(generateFlagString().equals(EMPTY_STRING))) {
sb.append(' ')
.append('[')
.append(generateFlagString())
.append(']');
}
sb.append(" Seq=") //$NON-NLS-1$
.append(fSequenceNumber);
if (fACKFlag) {
sb.append(" Ack=") //$NON-NLS-1$
.append(fAcknowledgmentNumber);
}
sb.append(" Len=") //$NON-NLS-1$
.append((fDataOffset * TCPValues.BLOCK_SIZE));
return NonNullUtils.nullToEmptyString(sb); }
private String generateFlagString() {
StringBuilder sb = new StringBuilder();
boolean start = true;
if (fSYNFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("SYN"); //$NON-NLS-1$
start = false;
}
if (fACKFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("ACK"); //$NON-NLS-1$
start = false;
}
if (fFINFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("FIN"); //$NON-NLS-1$
start = false;
}
if (fRSTFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("RST"); //$NON-NLS-1$
start = false;
}
if (fPSHFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("PSH"); //$NON-NLS-1$
start = false;
}
if (fURGFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("URG"); //$NON-NLS-1$
start = false;
}
if (fNSFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("NS"); //$NON-NLS-1$
start = false;
}
if (fCWRFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("CWR"); //$NON-NLS-1$
start = false;
}
if (fECEFlag) {
if (!start) {
sb.append(", "); //$NON-NLS-1$
}
sb.append("ECE"); //$NON-NLS-1$
start = false;
}
return NonNullUtils.nullToEmptyString(sb);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (fACKFlag ? 1231 : 1237);
result = prime * result + (int) (fAcknowledgmentNumber ^ (fAcknowledgmentNumber >>> 32));
result = prime * result + (fCWRFlag ? 1231 : 1237);
result = prime * result + fChecksum;
final Packet child = fChildPacket;
if (child != null) {
result = prime * result + child.hashCode();
} else {
result = prime * result;
}
result = prime * result + fDataOffset;
result = prime * result + fDestinationPort;
result = prime * result + (fECEFlag ? 1231 : 1237);
result = prime * result + (fFINFlag ? 1231 : 1237);
result = prime * result + (fNSFlag ? 1231 : 1237);
result = prime * result + Arrays.hashCode(fOptions);
result = prime * result + (fPSHFlag ? 1231 : 1237);
if (child == null) {
result = prime * result + payloadHashCode(fPayload);
}
result = prime * result + (fRSTFlag ? 1231 : 1237);
result = prime * result + fReservedField;
result = prime * result + (fSYNFlag ? 1231 : 1237);
result = prime * result + (int) (fSequenceNumber ^ (fSequenceNumber >>> 32));
result = prime * result + fSourcePort;
result = prime * result + (fURGFlag ? 1231 : 1237);
result = prime * result + fUrgentPointer;
result = prime * result + fWindowSize;
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;
}
TCPPacket other = (TCPPacket) obj;
if (fACKFlag != other.fACKFlag) {
return false;
}
if (fAcknowledgmentNumber != other.fAcknowledgmentNumber) {
return false;
}
if (fCWRFlag != other.fCWRFlag) {
return false;
}
if (fChecksum != other.fChecksum) {
return false;
}
if(!Objects.equals(fChildPacket, other.fChildPacket)){
return false;
}
if (fDataOffset != other.fDataOffset) {
return false;
}
if (fDestinationPort != other.fDestinationPort) {
return false;
}
if (fECEFlag != other.fECEFlag) {
return false;
}
if (fFINFlag != other.fFINFlag) {
return false;
}
if (fNSFlag != other.fNSFlag) {
return false;
}
if (!Arrays.equals(fOptions, other.fOptions)) {
return false;
}
if (fPSHFlag != other.fPSHFlag) {
return false;
}
if (fChildPacket == null && !payloadEquals(fPayload, other.fPayload)) {
return false;
}
if (fRSTFlag != other.fRSTFlag) {
return false;
}
if (fReservedField != other.fReservedField) {
return false;
}
if (fSYNFlag != other.fSYNFlag) {
return false;
}
if (fSequenceNumber != other.fSequenceNumber) {
return false;
}
if (fSourcePort != other.fSourcePort) {
return false;
}
if (fURGFlag != other.fURGFlag) {
return false;
}
if (fUrgentPointer != other.fUrgentPointer) {
return false;
}
return (fWindowSize == other.fWindowSize) ;
}
}