layers_ARP.js

const { compile, alignOffset } = require('struct-compile');
const { LinkLayerType } = require("#lib/enums");
const mixins = require("#lib/layers/mixins");
const { OsiModelLayers} = require("#lib/layers/osi");
const { macToString, macFromString } = require("#lib/layers/mac");
const { inetPton, inetNtop } = require('#lib/converters');

const OPCODE = {
  1: 'who-has',
  2: 'is-at'
};

const OPCODE_LOOKUP = Object.entries(OPCODE).reduce((acc, [numCode, stringCode]) => {
  acc[stringCode] = numCode;
  return acc;
}, {});

const { ARPHeader } = compile(`
  //@NE
  struct ARPHeader {
    // Hardware type (HTYPE)
    uint16_t hardwareType;
    // Protocol type (PTYPE)
    uint16_t protocolType;
    // Hardware address length (HLEN)
    uint8_t hardwareSize;
    // Protocol length (PLEN)
    uint8_t protocolSize;
    // Specifies the operation that the sender is performing: 1 (::ARP_REQUEST) for request, 2 (::ARP_REPLY) for reply
    uint16_t opcode;
    // Sender hardware address (SHA)
    uint8_t hardwareSrc[6];
    // @LE Sender protocol address (SPA)
    uint32_t protocolSrc;
    // Target hardware address (THA)
    uint8_t hardwareDst[6];
    // @LE Target protocol address (TPA)
    uint32_t protocolDst;
  } __attribute__(packed);
`);

/**
 * ARP protocol layer
 * @class
 * @implements {Layer}
 * @property {number} hardwareType - Hardware type (HTYPE)
 * @property {number} protocolType - Protocol type (PTYPE)
 * @property {number} hardwareSize - Hardware address length (HLEN)
 * @property {number} protocolSize - Protocol length (PLEN)
 * @property {'who-has' | 'is-at'} opcode - Specifies the operation that the sender is performing: 1 (::ARP_REQUEST) for request, 2 (::ARP_REPLY) for reply
 * @property {string} hardwareSrc - Sender hardware address (SHA)
 * @property {string} protocolSrc - Sender protocol address (SPA)
 * @property {string} hardwareDst - Target hardware address (THA)
 * @property {string} protocolDst - Target protocol address (TPA)
 */
class ARP extends ARPHeader {
  name = 'ARP';

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

  static toAlloc = () => ARPHeader.config.length;

  static osi = OsiModelLayers.DataLink;
  osi = OsiModelLayers.DataLink;

  /**
   * The source mac address in human-readable format.
   * @type {string}
   */
  get hardwareSrc() {
    return macToString(super.hardwareSrc);
  }

  set hardwareSrc(val) {
    super.hardwareSrc = macFromString(val);
  }

  /**
   * The destination mac address in human-readable format.
   * @type {string}
   */
  get hardwareDst() {
    return macToString(super.hardwareDst);
  }

  set hardwareDst(val) {
    super.hardwareDst = macFromString(val);
  }

  /**
   * The source internet address in human-readable format.
   * @type {string}
   */
  get protocolSrc() {
    return inetNtop(super.protocolSrc);
  }

  set protocolSrc(val) {
    super.protocolSrc = inetPton(val);
  }

  /**
   * The destination internet address in human-readable format.
   * @type {string}
   */
  get protocolDst() {
    return inetNtop(super.protocolDst);
  }

  set protocolDst(val) {
    super.protocolDst = inetPton(val);
  }

  /**
   * @return {'who-has' | 'is-at'}
   */
  get opcode() {
    return OPCODE[super.opcode];
  }

  set opcode(val) {
    super.opcode = OPCODE_LOOKUP[val];
  }

  toObject() {
    return {
      ...super.toObject(),
      hardwareSrc: this.hardwareSrc,
      hardwareDst: this.hardwareDst,
      protocolSrc: this.protocolSrc,
      protocolDst: this.protocolDst,
      opcode: this.opcode,
    };
  }

  defaults(obj, layers) {}

  nextProto(layers) {
    return layers.Payload(this.buffer.subarray(this.length), { ...this.opts, prev: this });
  }
};

module.exports = { ARP };