const { compile } = require('struct-compile');
const { OsiModelLayers } = require('./osi');
const { IPProtocolTypes } = 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 { macToString, macFromString } = require('./mac');
const { DHCPHeader } = compile(`
//@NE
struct DHCPHeader {
/** BootP opcode */
uint8_t opCode;
/** Hardware type, set to 1 (Ethernet) by default */
uint8_t hardwareType;
/** Hardware address length, set to 6 (MAC address length) by default */
uint8_t hardwareAddressLength;
/** Hop count */
uint8_t hops;
/** DHCP/BootP transaction ID */
uint32_t transactionId;
/** The elapsed time, in seconds since the client sent its first BOOTREQUEST message */
uint16_t secondsElapsed;
/** BootP flags */
uint16_t flags;
/** Client IPv4 address */
uint32_t clientIpAddress;
/** Your IPv4 address */
uint32_t yourIpAddress;
/** Server IPv4 address */
uint32_t serverIpAddress;
/** Gateway IPv4 address */
uint32_t gatewayIpAddress;
/** Client hardware address, by default contains the MAC address (only 6 first bytes are used) */
uint8_t clientHardwareAddress[16];
/** BootP server name */
uint8_t serverName[64];
/** BootP boot file name */
uint8_t bootFilename[128];
/** DHCP magic number (set to the default value of 0x63538263) */
uint32_t magicNumber;
};
`);
const { length: baseLength } = DHCPHeader.prototype.config;
/**
* DHCP protocol layer
* @class
* @property {number} src - Source DHCP port.
* @property {number} dst - Destination DHCP port.
* @property {number} totalLength - This field specifies the length in bytes of the DHCP datagram (the header fields and Data field) in octets.
* @property {number} checksum - The 16-bit checksum field is used for error-checking of the header and data.
* @implements {Layer}
*/
class DHCP extends DHCPHeader {
name = 'DHCP';
constructor(data = {}, opts = {}) {
super(data);
mixins.ctor(this, data, opts);
this.length = opts.allocated ?? this.totalLength * 4;
/**
* TLV options;
* @type {Iterable.<TLVOption>}
*/
this.options;
}
/**
* The destination IP address in human-readable format.
* @type {string}
*/
get clientIpAddress() {
return inetNtop(AF_INET, super.clientIpAddress);
}
set clientIpAddress(val) {
super.clientIpAddress = inetPton(AF_INET, val);
}
/**
* The destination IP address in human-readable format.
* @type {string}
*/
get yourIpAddress() {
return inetNtop(AF_INET, super.yourIpAddress);
}
set yourIpAddress(val) {
super.yourIpAddress = inetPton(AF_INET, val);
}
/**
* The destination IP address in human-readable format.
* @type {string}
*/
get gatewayIpAddress() {
return inetNtop(AF_INET, super.gatewayIpAddress);
}
set gatewayIpAddress(val) {
super.gatewayIpAddress = inetPton(AF_INET, val);
}
/**
* Usually the client MAC
* @type {string}
*/
get clientHardwareAddress() {
return macToString(super.clientHardwareAddress.slice(0, 6));
}
set clientHardwareAddress(val) {
super.clientHardwareAddress = Buffer.concat([macFromString(val), Buffer.from(Array.from({ length: 10 }, () => 0))]);
}
/**
* BootP server name.
* @type {string}
*/
get serverName() {
const buf = Buffer.from(super.serverName);
return buf.subarray(0, buf.indexOf(0)).toString();
}
set serverName(val) {
const buf = Buffer.from(val);
super.serverName = Buffer.concat([buf, Buffer.alloc(64 - buf.length)]);
}
/**
* BootP boot file name.
* @type {string}
*/
get bootFilename() {
const buf = Buffer.from(super.bootFilename);
return buf.subarray(0, buf.indexOf(0)).toString();
}
set bootFilename(val) {
const buf = Buffer.from(val);
super.bootFilename = Buffer.concat([buf, Buffer.alloc(64 - buf.length)]);
}
static toAlloc = (data) => baseLength;
static osi = OsiModelLayers.Transport;
osi = OsiModelLayers.Transport;
/**
* Retrieves all fields of the DHCP layer.
* @example
* udp.toObject();
* // {
* // src: 52622,
* // dst: 24043,
* // seq: 3994458414,
* // totalLength: 2048,
* // checksum: 3346,
* // }
* @returns {Object} The DHCP layer fields as an object.
*/
toObject() {
return {
...super.toObject(),
clientHardwareAddress: this.clientHardwareAddress,
serverName: this.serverName,
bootFilename: this.bootFilename,
};
}
/**
* Calculates and updates the checksum for the DHCP layer.
* This method mutates the object by setting the `checksum` property
* based on the current state of the `buffer` and `prev` field.
*/
calculateChecksum() {
this.checksum = checksums.pseudo({
data: this.buffer,
addrType: this.prev?.name ?? 'IPv4',
src: this.prev?.src,
dst: this.prev?.dst,
protocolType: IPProtocolTypes.DHCP,
});
}
defaults(obj = {}, layers) {
if (!obj.totalLength) {
this.totalLength = this.length / 4;
}
}
checksums(obj) {
if (!obj.checksum) {
this.calculateChecksum();
}
}
nextProto(layers) {
return new layers.Payload(this._buf.subarray(this.length));
}
};
mixins.withOptions(DHCP.prototype, { baseLength, skipTypes: [0x1, 0x0], lengthIsTotal: true });
module.exports = { DHCP };