layers_TCP.js

const { compile } = require('struct-compile');
const { OsiModelLayers } = require('./osi');
const { IPProtocolTypes } = require('./enums');
const { AF_INET, inetPton, inetNtop, ntohs } = require('#lib/socket');
const { checksums } = require('#lib/bindings');
const child = require('./child');
const mixins = require('./mixins');
const { omit } = require('#lib/pick');
const { addField } = require('#lib/struct');

const { TCPHeader } = compile(`
  //@NE
  struct TCPHeader {
    // Source TCP port 
    uint16_t src;
    // Destination TCP port 
    uint16_t dst;
    // Sequence number 
    uint32_t seq;
    // Acknowledgment number 
    uint32_t ack;

    // @LE
    uint16_t 
      reservedFlag:4,
      dataOffset:4,

      finFlag:1,
      synFlag:1,
      rstFlag:1,
      pshFlag:1,
      ackFlag:1,
      urgFlag:1,
      eceFlag:1,
      cwrFlag:1;

    // The size of the receive window, which specifies the number of window size units (by default, bytes) 
    uint16_t	windowSize;
    // The 16-bit checksum field is used for error-checking of the header and data 
    uint16_t	checksum;
    // If the URG flag is set, then this 16-bit field is an offset from the sequence number indicating the last urgent data byte 
    uint16_t	urgentPointer;
  } __attribute__((packed));
`);

const { length: baseLength } = TCPHeader.prototype.config;

const flagNames = ['reserved', 'cwr', 'ece', 'urg', 'ack', 'psh', 'rst', 'syn', 'fin'];
const flagKeys = flagNames.map(e => e + 'Flag');


/**
 * @typedef {Object} TCPFlags
 * @property {number} reserved - Reserved flag (0 or 1).
 * @property {number} cwr - Congestion Window Reduced (0 or 1).
 * @property {number} ece - ECN-Echo (0 или 1).
 * @property {number} urg - Urgent pointer field significant (0 или 1).
 * @property {number} ack - Acknowledgment field significant (0 или 1).
 * @property {number} psh - Push Function (0 или 1).
 * @property {number} rst - Reset the connection (0 или 1).
 * @property {number} syn - Synchronize sequence numbers (0 или 1).
 * @property {number} fin - No more data from sender (0 или 1).
 */

/**
 * TCP protocol layer
 * @class
 * @property {number} src - Source TCP port.
 * @property {number} dst - Destination TCP port.
 * @property {number} seq - Sequence number.
 * @property {number} ack - Acknowledgment number.
 * @property {number} windowSize - The size of the receive window, which specifies the number of window size units (by default, bytes).
 * @property {number} checksum - The 16-bit checksum field is used for error-checking of the header and data.
 * @property {number} urgentPointer - If the URG flag is set, then this 16-bit field is an offset from the sequence number indicating the last urgent data byte.
 * @property {TCPFlags} flags - TCP flags.
 * @implements {Layer}
 */
class TCP extends TCPHeader {
  name = 'TCP';

  constructor(data = {}, opts = {}) {
    super(data);
    mixins.ctor(this, data, opts);

    this.length = opts.allocated ?? this.dataOffset * 4;

    /**
     * TLV options;
     * @type {Iterable.<TLVOption>}
     */
    this.options;
  }

  static toAlloc = (data) => baseLength + TCP.prototype.optionsLength(data.options);
  static osi = OsiModelLayers.Transport;
  osi = OsiModelLayers.Transport;

  _prepareFlags() {
    const res = {};
    for (const flag of flagNames) {
      res[flag] = this[flag + 'Flag'];
    }
    return res;
  }

  /**
   * Get the flags object. Changes to this object will update the associated buffer.
   * @type {TCPFlags}
   */
  get flags() {
    const self = this;
    return new Proxy(Object.seal(this._prepareFlags()), {
      get(target, prop, receiver) {
        return target[prop];
      },
      set(target, prop, value) {
        target[prop] = value;
        const internalKey = prop + 'Flag';
        if (self[internalKey] !== undefined) {
          self[internalKey] = value;
        }
      },
    });
  }

  set flags(obj) {
    for (const flag of flagNames) {
      if (obj[flag] !== undefined) {
        this[flag + 'Flag'] = obj[flag];
      }
    }
  }

  /**
   * Retrieves all fields of the TCP layer.
   * @example
   * tcp.toObject();
   * // {
   * //   src: 52622,
   * //   dst: 24043,
   * //   seq: 3994458414,
   * //   ack: 3198720281,
   * //   dataOffset: 8,
   * //   windowSize: 2048,
   * //   checksum: 3346,
   * //   urgentPointer: 0,
   * //   flags: {
   * //     reserved: 0,
   * //     cwr: 0,
   * //     ece: 0,
   * //     urg: 0,
   * //     ack: 1,
   * //     psh: 0,
   * //     rst: 0,
   * //     syn: 0,
   * //     fin: 0
   * //   },
   * //   options: [
   * //     { type: 1 },
   * //     { type: 1 },
   * //     { type: 8, recLength: 10, value: Buffer.from([0x52, 0xd3, 0xc6, 0x50, 0xdd, 0x04, 0xcd, 0xd6]) }
   * //   ],
   * // }
   * @returns {Object} The TCP layer fields as an object.
   */
  toObject() {
    return {
      ...omit(super.toObject(), ...flagKeys),
      flags: this.flags,
      options: [...this.options],
    };
  }

  /**
   * Calculates and updates the checksum for the TCP layer.
   * This method mutates the object by setting the `checksum` property
   * based on the current state of the `buffer` and `prev` field.
   */
  calculateChecksum() {
    this.checksum = checksums.pseudo({
      data: this.buffer,
      addrType: this.prev?.name ?? 'IPv4',
      src: this.prev?.src,
      dst: this.prev?.dst,
      protocolType: IPProtocolTypes.TCP,
    });
  }

  defaults(obj = {}, layers) {
    if (!obj.windowSize) {
      this.windowSize = 2048;
    }
    if (!obj.urgentPointer) {
      this.urgentPointer = 0;
    }
    if (!obj.dataOffset) {
      this.dataOffset = this.length / 4;
    }
  }

  checksums(obj) {
    if (!obj.checksum) {
      this.calculateChecksum();
    }
  }

  nextProto(layers) {
    return new layers.Payload(this._buf.subarray(this.length));
  }
};

addField(TCP.prototype, 'flags');

mixins.withOptions(TCP.prototype, { baseLength, skipTypes: [0x1, 0x0], lengthIsTotal: true });

module.exports = { TCP };