All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pcap.codec.tcp.Tcp Maven / Gradle / Ivy

There is a newer version: 1.5.1
Show newest version
/** This code is licenced under the GPL version 2. */
package pcap.codec.tcp;

import java.util.Arrays;
import java.util.Objects;
import pcap.codec.AbstractPacket;
import pcap.codec.ApplicationLayer;
import pcap.codec.Packet;
import pcap.codec.TransportLayer;
import pcap.common.annotation.Inclubating;
import pcap.common.memory.Memory;
import pcap.common.net.Inet4Address;
import pcap.common.net.Inet6Address;
import pcap.common.net.InetAddress;
import pcap.common.util.Strings;
import pcap.common.util.Validate;

/** @author Ardika Rommy Sanjaya */
@Inclubating
public class Tcp extends AbstractPacket {

  private final Header header;
  private final Packet payload;
  private final Builder builder;

  private Tcp(final Builder builder) {
    this.header = new Header(builder);
    this.payloadBuffer = builder.payloadBuffer;
    if (this.payloadBuffer != null
        && this.payloadBuffer.readerIndex() < this.payloadBuffer.writerIndex()) {
      this.payload =
          ApplicationLayer.valueOf(this.header.payloadType().value())
              .newInstance(this.payloadBuffer);
    } else {
      this.payload = null;
    }
    this.builder = builder;
  }

  public static Tcp newPacket(Memory buffer) {
    return new Tcp.Builder().build(buffer);
  }

  @Override
  public Header header() {
    return header;
  }

  @Override
  public Packet payload() {
    return payload;
  }

  @Override
  public Builder builder() {
    return builder;
  }

  @Override
  public Memory buffer() {
    return header().buffer();
  }

  @Override
  public String toString() {
    return Strings.toStringBuilder(this).add("header", header).add("payload", payload).toString();
  }

  public static final class Header extends AbstractPacket.Header {

    public static final int TCP_HEADER_LENGTH = 20;

    private final short sourcePort;
    private final short destinationPort;
    private final int sequence;
    private final int acknowledge;
    private final byte dataOffset;
    private final TcpFlags flags;
    private final short windowSize;
    private final short checksum;
    private final short urgentPointer;
    private final byte[] options;

    private final Builder builder;

    private Header(final Builder builder) {
      this.sourcePort = builder.sourcePort;
      this.destinationPort = builder.destinationPort;
      this.sequence = builder.sequence;
      this.acknowledge = builder.acknowledge;
      this.dataOffset = builder.dataOffset;
      this.flags = builder.flags;
      this.windowSize = builder.windowSize;
      this.checksum = builder.checksum;
      this.urgentPointer = builder.urgentPointer;
      this.options = builder.options;
      this.buffer = resetIndex(builder.buffer, length());
      this.builder = builder;
    }

    static short calculateChecksum(Memory buffer, InetAddress srcAddr, InetAddress dstAddr) {
      int length = buffer.capacity();
      Memory buf;
      int pseudoSize;
      if (srcAddr instanceof Inet4Address && dstAddr instanceof Inet4Address) {
        pseudoSize = 12;
      } else if (srcAddr instanceof Inet6Address && dstAddr instanceof Inet6Address) {
        pseudoSize = 40;
      } else {
        return 0;
      }

      buf = ALLOCATOR.allocate(length + pseudoSize + (length % 2 == 0 ? 0 : 1));
      buf.writeBytes(buffer, 0, buffer.capacity());
      buf.writeByte(0);

      buf.writeBytes(srcAddr.address());
      buf.writeBytes(dstAddr.address());
      buf.writeByte(0);
      buf.writeByte(TransportLayer.TCP.value());
      if (srcAddr instanceof Inet4Address && dstAddr instanceof Inet4Address) {
        buf.writeShort(length);
      } else if (srcAddr instanceof Inet6Address && dstAddr instanceof Inet6Address) {
        buf.writeInt(length);
      }

      int accumulation = 0;
      while (buf.readableBytes() > 1) {
        accumulation += buf.readShort() & 0xFFFF;
      }

      buf.release();

      accumulation = (accumulation >> 16 & 0xFFFF) + (accumulation & 0xFFFF);
      return (short) (~accumulation & 0xFFFF);
    }

    /**
     * Source port.
     *
     * @return returns source port.
     */
    public int sourcePort() {
      return sourcePort & 0xffff;
    }

    /**
     * Destination port.
     *
     * @return returns destination port.
     */
    public int destinationPort() {
      return destinationPort & 0xffff;
    }

    /**
     * Sequence number.
     *
     * @return sequence number.
     */
    public int sequence() {
      return sequence;
    }

    /**
     * Acknowledge.
     *
     * @return returns acknowledge.
     */
    public int acknowledge() {
      return acknowledge;
    }

    /**
     * Data offset.
     *
     * @return returns data offset.
     */
    public int dataOffset() {
      return dataOffset & 0xf;
    }

    /**
     * Tcp flags.
     *
     * @return returns {@link TcpFlags}.
     */
    public TcpFlags flags() {
      return flags;
    }

    /**
     * Windows size.
     *
     * @return returns windows size.
     */
    public int windowSize() {
      return windowSize & 0xffff;
    }

    /**
     * Checksum.
     *
     * @return returns checksum.
     */
    public int checksum() {
      return checksum & 0xffff;
    }

    /**
     * Urgent pointer.
     *
     * @return urgent pointer.
     */
    public int urgentPointer() {
      return urgentPointer & 0xffff;
    }

    /**
     * Get options.
     *
     * @return returns options.
     */
    public byte[] options() {
      if (options == null) {
        return new byte[0];
      }
      byte[] buffer = new byte[this.options.length];
      System.arraycopy(options, 0, buffer, 0, buffer.length);
      return buffer;
    }

    /**
     * Check whether checksum is valid.
     *
     * @param srcAddr source ip pseudo header.
     * @param dstAddr destination ip pseudo header.
     * @return returns true if checksum is valid, false otherwise.
     */
    public boolean isValidChecksum(InetAddress srcAddr, InetAddress dstAddr) {
      Memory buf =
          ALLOCATOR.allocate(
              length() + (builder.payloadBuffer == null ? 0 : builder.payloadBuffer.capacity()));
      buf.writeBytes(buffer, 0, length());
      buf.writeBytes(builder.payloadBuffer, 0, builder.payloadBuffer.capacity());
      boolean valid = 0 == calculateChecksum(buf, srcAddr, dstAddr);
      buf.release();
      return valid;
    }

    @Override
    public ApplicationLayer payloadType() {
      return ApplicationLayer.valueOf(destinationPort);
    }

    @Override
    public int length() {
      int length = TCP_HEADER_LENGTH;
      if (this.options != null) {
        length += this.options.length;
      }
      return length;
    }

    @Override
    public Memory buffer() {
      if (buffer == null) {
        buffer = ALLOCATOR.allocate(length());
        buffer.writeShort(this.sourcePort);
        buffer.writeShort(this.destinationPort);
        buffer.writeInt(this.sequence);
        buffer.writeInt(this.acknowledge);
        buffer.writeShort((this.flags.value() & 0x1ff) | (this.dataOffset & 0xf) << 12);
        buffer.writeShort(this.windowSize);
        buffer.writeShort(this.checksum);
        buffer.writeShort(this.urgentPointer);
        if (this.options != null) {
          buffer.writeBytes(this.options);
        }
      }
      return buffer;
    }

    @Override
    public Builder builder() {
      return builder;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Header header = (Header) o;
      return sourcePort == header.sourcePort
          && destinationPort == header.destinationPort
          && sequence == header.sequence
          && acknowledge == header.acknowledge
          && dataOffset == header.dataOffset
          && windowSize == header.windowSize
          && checksum == header.checksum
          && urgentPointer == header.urgentPointer
          && flags.equals(header.flags)
          && Arrays.equals(options, header.options);
    }

    @Override
    public int hashCode() {
      int result =
          Objects.hash(
              sourcePort,
              destinationPort,
              sequence,
              acknowledge,
              dataOffset,
              flags,
              windowSize,
              checksum,
              urgentPointer);
      result = 31 * result + Arrays.hashCode(options);
      return result;
    }

    @Override
    public String toString() {
      return Strings.toStringBuilder(this)
          .add("sourcePort", sourcePort & 0xFFFF)
          .add("destinationPort", destinationPort & 0xFFFF)
          .add("sequence", (sequence & 0xFFFFFFFFL))
          .add("acknowledge", (acknowledge & 0xFFFFFFFFL))
          .add("dataOffset", dataOffset & 0xFF)
          .add("flags", flags)
          .add("windowSize", windowSize & 0xFFFF)
          .add("checksum", checksum & 0xFFFF)
          .add("urgentPointer", urgentPointer & 0xFF)
          .add("options", options == null || options.length == 0 ? "(None)" : Strings.hex(options))
          .toString();
    }
  }

  public static class Builder extends AbstractPacket.Builder {

    private short sourcePort;
    private short destinationPort;
    private int sequence;
    private int acknowledge;
    private byte dataOffset;
    private TcpFlags flags;
    private short windowSize;
    private short checksum;
    private short urgentPointer;
    private byte[] options;

    private Memory buffer;
    private Memory payloadBuffer;

    /** A helper field. */
    private boolean calculateChecksum;

    private InetAddress srcAddr;
    private InetAddress dstAddr;

    /**
     * Source port.
     *
     * @param sourcePort source port.
     * @return returns this {@link Builder}.
     */
    public Builder sourcePort(int sourcePort) {
      this.sourcePort = (short) (sourcePort & 0xffff);
      return this;
    }

    /**
     * Destination port.
     *
     * @param destinationPort destination port.
     * @return returns this {@link Builder}.
     */
    public Builder destinationPort(int destinationPort) {
      this.destinationPort = (short) (destinationPort & 0xffff);
      return this;
    }

    /**
     * Sequence number.
     *
     * @param sequence sequence number,
     * @return returns this {@link Builder}.
     */
    public Builder sequence(int sequence) {
      this.sequence = sequence;
      return this;
    }

    /**
     * Acknowledge.
     *
     * @param acknowledge acknowledge.
     * @return returns this {@link Builder}.
     */
    public Builder acknowledge(int acknowledge) {
      this.acknowledge = acknowledge;
      return this;
    }

    /**
     * Data offset.
     *
     * @param dataOffset data offset.
     * @return returns this {@link Builder}.
     */
    public Builder dataOffset(int dataOffset) {
      this.dataOffset = (byte) (dataOffset & 0xf);
      return this;
    }

    /**
     * Flags.
     *
     * @param flags flags.
     * @return returns this {@link Builder}.
     */
    public Builder flags(TcpFlags flags) {
      this.flags = flags;
      return this;
    }

    /**
     * Windows size.
     *
     * @param windowSize window size.
     * @return returns this {@link Builder}.
     */
    public Builder windowsSize(int windowSize) {
      this.windowSize = (short) (windowSize & 0xffff);
      return this;
    }

    /**
     * Checksum.
     *
     * @param checksum cheksum.
     * @return returns this {@link Builder}.
     */
    public Builder checksum(int checksum) {
      this.checksum = (short) (checksum & 0xffff);
      return this;
    }

    /**
     * Urgent pointer.
     *
     * @param urgentPointer urgent pointer.
     * @return returns this {@link Builder}.
     */
    public Builder urgentPointer(int urgentPointer) {
      this.urgentPointer = (short) (urgentPointer & 0xffff);
      return this;
    }

    /**
     * Options.
     *
     * @param options options.
     * @return returns this {@link Builder}.
     */
    public Builder options(byte[] options) {
      this.options = Validate.nullPointerThenReturns(options, new byte[0]);
      this.dataOffset = (byte) (20 + options.length + 3 >> 2);
      return this;
    }

    @Override
    public Builder payload(AbstractPacket packet) {
      this.payloadBuffer = packet.buffer();
      return this;
    }

    /**
     * Calculate checksum.
     *
     * @param srcAddr source ip address (pseudo header).
     * @param dstAddr destination ip address (pseudo header).
     * @param calculateChecksum true for calculating checksum, false otherwise.
     * @return returns this {@link Builder}.
     */
    public Builder calculateChecksum(
        InetAddress srcAddr, InetAddress dstAddr, boolean calculateChecksum) {
      this.srcAddr = srcAddr;
      this.dstAddr = dstAddr;
      this.calculateChecksum = calculateChecksum;
      return this;
    }

    @Override
    public Tcp build() {
      if (calculateChecksum && srcAddr != null && dstAddr != null) {
        if (buffer == null) {
          final Tcp tcp = new Tcp(this);
          int length =
              tcp.header.length()
                  + (this.payloadBuffer == null ? 0 : this.payloadBuffer.capacity());
          this.buffer = ALLOCATOR.allocate(length);
          this.buffer.writeBytes(tcp.header().buffer(), 0, tcp.header().length());
          if (this.payloadBuffer != null) {
            this.buffer.writeBytes(payloadBuffer, 0, this.payloadBuffer.capacity());
          }
        }
        checksum(Tcp.Header.calculateChecksum(buffer, srcAddr, dstAddr));
        buffer.setShort(16, this.checksum);
      }
      return new Tcp(this);
    }

    @Override
    public Tcp build(Memory buffer) {
      resetIndex(buffer);
      this.sourcePort = buffer.readShort();
      this.destinationPort = buffer.readShort();
      this.sequence = buffer.readInt();
      this.acknowledge = buffer.readInt();
      short tmp = buffer.readShort();
      this.dataOffset = (byte) (tmp >> 12 & 0xf);
      this.flags = new TcpFlags.Builder().build((short) (tmp & 0x1ff));
      this.windowSize = buffer.readShort();
      this.checksum = buffer.readShort();
      this.urgentPointer = buffer.readShort();
      if (this.dataOffset > 5 && buffer.readableBytes() > 0) {
        int optionLength = (this.dataOffset << 2) - Header.TCP_HEADER_LENGTH;
        if (buffer.capacity() < Header.TCP_HEADER_LENGTH + optionLength) {
          optionLength = buffer.capacity() - Header.TCP_HEADER_LENGTH;
        }
        this.options = new byte[optionLength];
        buffer.readBytes(options);
        int length = 20 + optionLength;
        this.payloadBuffer = buffer.slice(length, buffer.capacity() - length);
      } else {
        this.options = new byte[0];
      }
      this.buffer = buffer;
      this.payloadBuffer = buffer.slice();
      return new Tcp(this);
    }

    @Override
    public Builder reset() {
      return reset(readerIndex, Header.TCP_HEADER_LENGTH);
    }

    @Override
    public Builder reset(int offset, int length) {
      if (buffer != null) {
        Validate.notIllegalArgument(offset + length <= buffer.capacity());
        Validate.notIllegalArgument((sourcePort & 0xFFFF) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument((destinationPort & 0xFFFF) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument((sequence & 0xFFFFFFFFL) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument((acknowledge & 0xFFFFFFFFL) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument(flags != null, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument((windowSize & 0xFFFF) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument((checksum & 0xFFFF) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument((urgentPointer & 0xFFFF) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument((dataOffset & 0xF) >= 0, ILLEGAL_HEADER_EXCEPTION);
        Validate.notIllegalArgument(options != null, ILLEGAL_HEADER_EXCEPTION);
        int index = offset;
        buffer.setShort(index, sourcePort);
        index += 2;
        buffer.setShort(index, destinationPort);
        index += 2;
        buffer.setInt(index, sequence);
        index += 4;
        buffer.setInt(index, acknowledge);
        index += 4;
        int tmp = flags.value() & 0x1FF | (dataOffset << 12);
        buffer.setShort(index, tmp);
        index += 2;
        buffer.setShort(index, windowSize);
        index += 2;
        buffer.setShort(index, checksum);
        index += 2;
        buffer.setShort(index, urgentPointer);
        index += 2;
        if (dataOffset > 5 && options != null) {
          buffer.setBytes(index, options);
        }
      }
      return this;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy