layers_ARP.js

const { OsiModelLayers } = require('./osi');
const { ETHERTYPE } = require('./enums');
const { makeLayer, attach } = require('./define');
const { macAddress, ipv4Address } = require('./addr');

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

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

/**
 * ARP protocol layer
 * @class
 * @implements {Layer}
 * @property {number} hardwareType - Hardware type (HTYPE)
 * @property {number} protocolType - Protocol type (PTYPE)
 * @property {number} hardwareLength - Hardware address length (HLEN)
 * @property {number} protocolLength - Protocol length (PLEN)
 * @property {'who-has' | 'is-at'} opcode - Operation: 1 (request) for `who-has`, 2 (reply) for `is-at`.
 * @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)
 */
const ARP = (() => {
  const { Layer, proto } = makeLayer('ARP', `
    //@NE
    struct ARPHeader {
      uint16_t hardwareType;
      uint16_t protocolType;
      uint8_t hardwareLength;
      uint8_t protocolLength;
      uint16_t opcode;
      uint8_t hardwareSrc[6];
      //@LE
      uint32_t protocolSrc;
      uint8_t hardwareDst[6];
      //@LE
      uint32_t protocolDst;
    } __attribute__(packed);
  `, {
    osi: OsiModelLayers.DataLink,
  });

  attach.virtualField(proto, 'hardwareSrc', macAddress(8));
  attach.virtualField(proto, 'hardwareDst', macAddress(18));
  attach.virtualField(proto, 'protocolSrc', ipv4Address(14));
  attach.virtualField(proto, 'protocolDst', ipv4Address(24));
  attach.virtualField(proto, 'opcode', {
    get() { return OPCODE[this._buf.readUInt16BE(6)]; },
    set(v) { this._buf.writeUInt16BE(Number(OPCODE_LOOKUP[v]) | 0, 6); },
  });
  attach.toObjectExtras(proto, ['hardwareSrc', 'hardwareDst', 'protocolSrc', 'protocolDst', 'opcode']);

  attach.defaults(proto, {
    hardwareType: 1,
    hardwareLength: 6,
    protocolType: ETHERTYPE.IP,
    protocolLength: 4,
  });

  return Layer;
})();

module.exports = { ARP };