layers_IPv4.js

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

const IP_DONT_FRAGMENT  = 0x40;
const IP_MORE_FRAGMENTS = 0x20;

const { IPv4Header } = compile(`
  //@NE
  struct IPv4Header {
    //IP header length, has the value of 5 for IPv4
    uint8_t headerLength:4;

    //IP version number, has the value of 4 for IPv4
    uint8_t version:4;

    //type of service, same as Differentiated Services Code Point (DSCP)
    uint8_t typeOfService;

    //Entire packet (fragment) size, including header and data, in bytes
    uint16_t totalLength;

    //Identification field. Primarily used for uniquely identifying the group of fragments of a single IP datagram
    uint16_t id;

    //Fragment offset field, measured in units of eight-byte blocks (64 bits) @LE
    uint16_t fragmentOffsetRaw;

    //An eight-bit time to live field helps prevent datagrams from persisting (e.g. going in circles) on an internet.  In practice, the field has become a hop count 
    uint8_t timeToLive;

    //Defines the protocol used in the data portion of the IP datagram. Must be one of ::IPProtocolTypes 
    uint8_t protocol;

    //Error-checking of the header 
    uint16_t checksum;

    //@LE IPv4 address of the sender of the packet 
    uint32_t src;

    //@LE IPv4 address of the receiver of the packet 
    uint32_t dst;

    /*The options start here.*/
  } __attribute__(packed);
`);

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

const childProto = {
  [IPProtocolTypes.UDP]: 'UDP',
  [IPProtocolTypes.TCP]: 'TCP',
  [IPProtocolTypes.ICMP]: 'ICMP',
  [IPProtocolTypes.GRE]: 'GRE',
  [IPProtocolTypes.IGMP]: 'IGMP',
  [IPProtocolTypes.AH]: 'AuthenticationHeader',
  [IPProtocolTypes.ESP]: 'ESP',
  [IPProtocolTypes.IPV6]: 'IPv6',
  [IPProtocolTypes.VRRP]: 'VRRP',
};

const lookupChild = child.lookupChild(childProto);
const lookupKey = child.lookupKey(childProto);

/**
 * IPv4 protocol layer
 * @class
 * @implements {Layer}
 * @property {number} headerLength - IP header length, has the value of 5 for IPv4.
 * @property {number} version - IP version number, has the value of 4 for IPv4.
 * @property {number} typeOfService - type of service, same as Differentiated Services Code Point (DSCP)
 * @property {number} totalLength - Entire packet (fragment) size, including header and data, in bytes
 * @property {number} id - Identification field. Primarily used for uniquely identifying the group of fragments of a single IP datagram
 * @property {number} fragmentOffsetRaw - Fragment offset field, measured in units of eight-byte blocks (64 bits) @LE
 * @property {number} timeToLive - An eight-bit time to live field helps prevent datagrams from persisting (e.g. going in circles) on an internet.  In practice, the field has become a hop count 
 * @property {number} protocol - Defines the protocol used in the data portion of the IP datagram. Must be one of ::IPProtocolTypes 
 * @property {number} checksum - Error-checking of the header 
 * @property {string} src - IPv4 address of the sender of the packet 
 * @property {string} dst  - IPv4 address of the receiver of the packet 
 * @implements {Layer}
 */
class IPv4 extends IPv4Header {
  name = 'IPv4';

  /**
   * @param {Buffer|Object} data - Input buffer or object with protocol fields.
   * @param {Object} opts - Options for the layer.
   */
  constructor(data, opts = {}) {
    super(data);
    mixins.ctor(this, data, opts);

    this.length = opts.allocated ?? this.headerLength * 4;
  }

  static toAlloc = (data) => baseLength + IPv4.prototype.optionsLength(data.options);
  static OptionTypes = IPv4OptionTypes;
  osi = OsiModelLayers.Network;

  /**
   * The source IP address in human-readable format.
   * @type {string}
   */
  get src() {
    return inetNtop(AF_INET, super.src);
  }

  set src(val) {
    super.src = inetPton(AF_INET, val);
  }

  /**
   * The destination IP address in human-readable format.
   * @type {string}
   */
  get dst() {
    return inetNtop(AF_INET, super.dst);
  }

  set dst(val) {
    super.dst = inetPton(AF_INET, val);
  }

  /**
   * Calculates and updates the checksum for the IPv4 layer.
   * This method mutates the object by setting the `checksum` property
   * based on the current state of the `buffer`.
   */
  calculateChecksum() {
    this.checksum = checksums.ip(this.buffer.subarray(0, this.length));
  }

  /**
   * @type {number}
   */
  get fragmentOffsetFlags() {
    return super.fragmentOffsetRaw & 0xE0;
  }

  /**
   * @type {boolean}
   */
  get isFragment() {
    return ((this.fragmentOffsetFlags & IP_MORE_FRAGMENTS) != 0 || this.fragmentOffsetValue != 0);
  }

  /**
   * @type {boolean}
   */
  get isFirstFragment() {
    return isFragment() && (this.fragmentOffsetValue == 0);
  }

  /**
   * @type {boolean}
   */
  get isLastFragment() {
    return this.isFragment && ((this.fragmentOffsetFlags & IP_MORE_FRAGMENTS) == 0);
  }

  /**
   * @type {number}
   */
  get fragmentOffsetValue() {
    return ntohs(super.fragmentOffsetRaw & 0xFF1F) * 8;
  }

  toObject() {
    return {
      ...super.toObject(),
      fragmentInfo: {
        isFragment: this.isFragment,
        value: this.fragmentOffsetValue,
        flags: this.fragmentOffsetFlags,
      },
      options: [...this.options],
    };
  }

  defaults(obj = {}, layers) {
    if (!obj.headerLength) {
      this.headerLength = IPv4.toAlloc(obj) / 4;
      this.length = this.headerLength * 4;
    }
    if (!obj.version) {
      this.version = 4;
    }
    if (!obj.typeOfService) {
      this.typeOfService = 0;
    }
    if (!obj.id) {
      this.id = 0;
    }
    if (!obj.timeToLive) {
      this.timeToLive = 64;
    }
    if (!obj.totalLength) {
      this.totalLength = this.length + (this.next?.length ?? 0);
    }
    if (!obj.protocol) {
      if (!this.next) {
        this.protocol = IPProtocolTypes.RAW;
      }
      this.protocol = lookupKey(layers, this.next) ?? IPProtocolTypes.RAW;
    }
  }

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

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

    if (this.protocol == IPProtocolTypes.IPIP) {
      const { version } = this;
      if (version == 4) {
        return new layers.IPv4(this._buf.subarray(this.length), this);
      }
      else if (version == 6) {
        return new layers.IPv6(this._buf.subarray(this.length), this);
      }
      else {
        throw new Error(`Invalid IP version ${version}`);
      }
    }

    return lookupChild(layers, this.protocol, this);
  }
};

mixins.withOptions(IPv4.prototype, { baseLength });

module.exports = { IPv4 };