layers_TCP.js

const { OsiModelLayers } = require('./osi');
const { IPProtocolTypes } = require('./enums');
const { addField } = require('#lib/struct');
const { makeLayer, attach } = require('./define');

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.
 * @property {number} ece - ECN-Echo.
 * @property {number} urg - Urgent pointer field significant.
 * @property {number} ack - Acknowledgment field significant.
 * @property {number} psh - Push Function.
 * @property {number} rst - Reset the connection.
 * @property {number} syn - Synchronize sequence numbers.
 * @property {number} fin - No more data from sender.
 */

/**
 * 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 - Receive window size.
 * @property {number} checksum - Header + data checksum.
 * @property {number} urgentPointer - Urgent data byte offset (when URG is set).
 * @property {TCPFlags} flags - TCP flags.
 * @implements {Layer}
 */
const TCP = (() => {
  const { Layer, proto, baseLength } = makeLayer('TCP', `
    //@NE
    struct TCPHeader {
      uint16_t src;
      uint16_t dst;
      uint32_t seq;
      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;
      uint16_t windowSize;
      uint16_t checksum;
      uint16_t urgentPointer;
    } __attribute__((packed));
  `, {
    osi: OsiModelLayers.Transport,
    length: ($) => $.dataOffset * 4,
  });

  // The `flags` virtual field exposes the 9 *Flag bit fields as a single
  // mutable object. Reads delegate to struct setters/getters via prototype
  // chain so writes through the Proxy stay in sync with the underlying
  // bit-packed field.
  attach.virtualField(proto, 'flags', {
    get() {
      const self = this;
      const target = {};
      for (const flag of flagNames) target[flag] = self[flag + 'Flag'];
      return new Proxy(Object.seal(target), {
        get(t, prop) { return t[prop]; },
        set(t, prop, value) {
          t[prop] = value;
          const internalKey = prop + 'Flag';
          if (self[internalKey] !== undefined) self[internalKey] = value;
          return true;
        },
      });
    },
    set(obj) {
      for (const flag of flagNames) {
        if (obj[flag] !== undefined) this[flag + 'Flag'] = obj[flag];
      }
    },
  });

  // TLV options (NOP/EOL skipped, length-is-total-record per RFC 793).
  attach.options.tlv8(Layer, proto, {
    baseLength,
    skipTypes: [0x1, 0x0],
    lengthIsTotal: true,
  });

  // Wrap toObject again to omit raw *Flag keys and project them onto a
  // single `flags` object. attach.options.tlv8 already added `options`.
  const previousToObject = proto.toObject;
  proto.toObject = function() {
    const obj = previousToObject.call(this);
    for (const k of flagKeys) delete obj[k];
    obj.flags = this.flags;
    return obj;
  };

  attach.checksum.pseudo(proto, IPProtocolTypes.TCP);

  // TCP terminates the layer chain with a raw Payload over its body. Mirrors
  // the legacy class which never inspected the application protocol.
  proto.nextProto = function nextProto(layers) {
    return new layers.Payload(this._buf.subarray(this.length));
  };

  attach.defaults(proto, {
    windowSize: 2048,
    urgentPointer: 0,
    dataOffset: ($) => $.length / 4,
  });

  // struct-compile registers `flags` as a virtual field for downstream
  // tooling that walks `proto.config.fields`.
  addField(proto, 'flags');

  return Layer;
})();

module.exports = { TCP };