layers_DHCP.js

const { OsiModelLayers } = require('./osi');
const { IPProtocolTypes } = require('./enums');
const { macToString, macFromString } = require('./mac');
const { makeLayer, attach } = require('./define');
const { ipv4Address } = require('./addr');

// C-string accessor over a fixed-size byte slot: read trims at the first
// null byte; write zero-fills the slot before copying the new value so
// stale bytes from a previous longer string don't leak through.
const fixedString = (offset, size) => ({
  get() {
    const slice = this._buf.subarray(offset, offset + size);
    const nullIdx = slice.indexOf(0);
    return slice.subarray(0, nullIdx >= 0 ? nullIdx : size).toString();
  },
  set(v) {
    const buf = Buffer.from(String(v));
    this._buf.fill(0, offset, offset + size);
    buf.copy(this._buf, offset, 0, Math.min(buf.length, size));
  },
});

/**
 * DHCP protocol layer
 * @class
 * @property {number} opCode - BootP opcode.
 * @property {number} hardwareType - Hardware type (1 = Ethernet).
 * @property {number} hardwareAddressLength - Hardware address length (6 = MAC).
 * @property {number} hops - Hop count.
 * @property {number} transactionId - DHCP transaction ID.
 * @property {string} clientIpAddress - Client IPv4 address (string form).
 * @property {string} yourIpAddress - Your IPv4 address (string form).
 * @property {number} serverIpAddress - Server IPv4 address as raw uint32.
 * @property {string} gatewayIpAddress - Gateway IPv4 address (string form).
 * @property {string} clientHardwareAddress - Client hardware address (MAC, first 6 bytes of 16).
 * @property {string} serverName - BootP server name (C-string).
 * @property {string} bootFilename - BootP boot file name (C-string).
 * @property {number} magicNumber - DHCP magic number (0x63825363).
 * @implements {Layer}
 */
const DHCP = (() => {
  const { Layer, proto, baseLength } = makeLayer('DHCP', `
    //@NE
    struct DHCPHeader {
      uint8_t opCode;
      uint8_t hardwareType;
      uint8_t hardwareAddressLength;
      uint8_t hops;
      uint32_t transactionId;
      uint16_t secondsElapsed;
      uint16_t flags;
      uint32_t clientIpAddress;
      uint32_t yourIpAddress;
      uint32_t serverIpAddress;
      uint32_t gatewayIpAddress;
      uint8_t clientHardwareAddress[16];
      uint8_t serverName[64];
      uint8_t bootFilename[128];
      uint32_t magicNumber;
    };
  `, {
    osi: OsiModelLayers.Transport,
    length: ($) => $.totalLength * 4,
  });

  attach.virtualField(proto, 'clientIpAddress', ipv4Address(12));
  attach.virtualField(proto, 'yourIpAddress', ipv4Address(16));
  attach.virtualField(proto, 'gatewayIpAddress', ipv4Address(24));

  attach.virtualField(proto, 'clientHardwareAddress', {
    get() { return macToString(this._buf.subarray(28, 34)); },
    set(v) {
      macFromString(v).copy(this._buf, 28);
      this._buf.fill(0, 34, 44);
    },
  });
  attach.virtualField(proto, 'serverName', fixedString(44, 64));
  attach.virtualField(proto, 'bootFilename', fixedString(108, 128));

  attach.toObjectExtras(proto, ['clientHardwareAddress', 'serverName', 'bootFilename']);

  attach.options.tlv8(Layer, proto, {
    baseLength,
    skipTypes: [0x1, 0x0],
    lengthIsTotal: true,
  });

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

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

  attach.defaults(proto, {
    totalLength: ($) => $.length / 4,
  });

  return Layer;
})();

module.exports = { DHCP };