ipv4.js

/**
 * @module IPv4
 * 
 * @summary
 * Represents an IPv4 in the form of a uint32 
 * 
 * @desc
 * IPv4 natively handles conversions between String, Numeric and other IPv4 instances.
 * 
 * @param {IPv4|String|Number} ip The IPV4 value
 */

/**
 * @class IPv4
 * @hideconstructor
 */
class IPv4 {
    
    constructor(ip) 
    {
        this._u32_value = IPv4.to_u32_convert(ip);
    }

    /**
     * @method from
     * @static 
     * 
     * @alias IPv4 
     * 
     * @summary 
     * Constructor Alias
     * 
     * @example
     * const ip = IPv4.from('192.168.23.52');
     * 
     * ip.log();
     * 
     * @example
     * const ip = IPv4.from(0xC0A81738);
     * 
     * ip.log();
     * 
     * 
     * @param {IPv4|String|Number} ip The IPv4 value
     * 
     * @returns an IPv4 instance
     */
    static from(ip) 
    {
        return new IPv4(ip);
    }

    /**
     * @method u32
     * 
     * @desc 
     * Get the stored uint32 representing your ip instance
     * 
     * @example
     * const u32_ip = IPv4.from('192.168.1.1').u32();
     * 
     * console.log(u32_ip); // 0xC0A80101
     * 
     * @returns The full IPv4 as an uint32
     */
    u32() 
    {
        return this._u32_value;
    }

    /**
     * @method u8
     * 
     * @desc 
     * Get a specific byte from the stored uint32 representing your ip instance
     * 
     * @param {Number} index The byte index 
     * 
     * @example
     * const ip = IPv4.from('192.168.1.1');
     *
     * const msb_byte = ip.u8(3); // 192
     * // ... //        ip.u8(2); // 168
     * // ... //        ip.u8(1); //   1
     * const lsb_byte = ip.u8(0); //   1
     * 
     * @returns {Number} The selected IPv4 byte as a uint8 
     */
    u8(index = 0) 
    {
        return (this._u32_value >> (8*index)) & 0xFF;
    }

    /**
     * @method toString
     * 
     * @desc 
     * Convert an IPv4 instance to a string
     * 
     * @example
     * const mask = IPv4.from(0xFFFF0000);
     * 
     * console.log(mask.toString()); // 255.255.0.0
     * 
     * @returns {String} ip as a string
     */
    toString() 
    {
        return IPv4.toString(this._u32_value);
    }

    /**
     * @method valueOf
     * 
     * 
     * @desc 
     * native prototype method override
     * 
     * @example
     * const ip = IPv4.from('192.168.1.1');
     * 
     * console.log(ip | 0xFF); // 0xC0A801FF
     * 
     * @returns The full IPv4 as an uint32
     */
    valueOf() {
        return this._u32_value;
    }

    /**
     * @method copy
     * 
     * @desc
     * Copy an IPv4 instance
     * 
     * @example
     * const host1 = IPv4.from('192.168.1.12');
     * 
     * const host2 = host1.copy().add(12);
     * 
     * host1.toString() // 192.168.1.12
     * host2.toString() // 192.168.1.24
     * 
     * @returns {IPv4} A copy of the instance
     */
    copy() 
    {
        return new IPv4(this._u32_value);
    }

    /**
     * @method op
     * 
     * @summary
     * Run an assignment operation on the uint32
     * 
     * @param {Function} operation Assignement operation function, takes in the current value, returns the new value
     * 
     * @example
     * const ip   = IPv4.from('192.168.1.12');
     * const mask = IPv4.from('255.255.255.0');
     * 
     * const wildcard_ip = mask.op(x => ~x).and(ip); 
     * wildcard_ip.log();
     * 
     * @returns {this} A pointer to itself
     */
    op(operation) 
    {
        this._u32_value = operation(this._u32_value);
        return this;
    }

    /**
     * @method use
     * @static
     * 
     * @desc 
     * Implement plugins as IPv4 static and prototype members 
     * 
     * @param {class} plugin A class template with static & prototype members 
     * 
     * @example
     * const { IPv4, Hardware } = require('ipv4-utils');
     * 
     * IPv4.use(Hardware);
     */
    static use(plugin) 
    {
        let static_keys = Object.getOwnPropertyNames(plugin);
        
        if (plugin.init) 
            static_keys.splice(static_keys.indexOf('init'), 1);

        for (let i = 3; i < static_keys.length; i++) 
        {
            IPv4[static_keys[i]] = plugin[static_keys[i]]
        }

        for (let instance_method in plugin.prototype) 
        {
            IPv4.prototype[instance_method] = plugin.prototype[instance_method];
        }

        if (plugin.init) 
            plugin.init();
    }


    /**
     * @method fromString
     * @static
     * @ignore
     * 
     * @desc
     * Convert from a string ip to uint32 from
     * 
     * @param {String} ip_string A string representing an IPv4
     * 
     * @returns {Number} A uint32 value representing an IPv4
     */
    static fromString(ip_string) 
    {
        // Get byte array
        let bytes = ip_string.split('.').reverse().map(
            x =>
                // Value range restrictions 0 to 255
                Math.max(0, Math.min(Number(x), 0xFF))
        );

        if (bytes.length !== 4) {
            console.error(
                "IPv4 Error: Expects string to be denoted with 4 decimal values separated with `.`"
            );
            return 0x00000000;
        }

        // Returned value
        let value = 0x00000000;

        // Set each parsed byte to the uint32 value
        for (let i = 0; i < 4; i++) {
            value |= bytes[i] << (8*i)
        }

        return value;
    }

    /**
     * @method toString
     * @static
     * @ignore
     * 
     * @desc
     * convert the ip value to a string denoted as `x.x.x.x`
     * 
     * @param {Number} uint32 value representing an IPv4
     * 
     * @returns {String} ip as a string
     */
    static toString(_u32_value)
    {
        // Returned value
        let bytes = new Array(4);

        // Get each byte in uint32
        for (let i = 0; i < 4; i++) 
        {
            bytes[i] = (_u32_value >> (8*i)) & 0xFF;
        }

        // Concat all numeric values to a string
        return bytes.reverse().join('.');
    }

    /**
     * @enum to_u32_conversions
     * @static 
     * @ignore
     * 
     * @desc
     * Lookup table for conversions to u32 based on input value type
     */
    static to_u32_conversions = {
        'String': IPv4.fromString,
        'Number': x => x,
        'IPv4'  : x => x._u32_value
    };

    /**
     * @method to_u32_convert
     * @static 
     * @ignore
     * 
     * @param {IPv4|Number|String} value A value representing an IPv4 
     * 
     * @returns {Number} An IPv4 as a uint32
     */
    static to_u32_convert(value) {
        if (value.constructor.name in IPv4.to_u32_conversions)
        {
            return IPv4.to_u32_conversions[value.constructor.name](value);
        } 
        else
        {
            console.error("IPv4 Error: Value type must be Numeric, String or an instance of IPv4");
            return 0x00000000;
        }
    }    
}

module.exports = IPv4;