Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(internet): improve ipv4 method #2992

Merged
merged 14 commits into from
Oct 10, 2024
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export type { GitModule } from './modules/git';
export type { HackerModule } from './modules/hacker';
export type { HelpersModule, SimpleHelpersModule } from './modules/helpers';
export type { ImageModule } from './modules/image';
export type { InternetModule } from './modules/internet';
export { IPv4Network } from './modules/internet';
export type { IPv4NetworkType, InternetModule } from './modules/internet';
export type { LocationModule } from './modules/location';
export type { LoremModule } from './modules/lorem';
export type { MusicModule } from './modules/music';
Expand Down
170 changes: 166 additions & 4 deletions src/modules/internet/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FakerError } from '../../errors/faker-error';
import { ModuleBase } from '../../internal/module-base';
import { charMapping } from './char-mappings';
import * as random_ua from './user-agent';
Expand All @@ -23,6 +24,82 @@ export type HTTPStatusCodeType =

export type HTTPProtocolType = 'http' | 'https';

export enum IPv4Network {
/**
* Equivalent to: `0.0.0.0/0`
*/
Any = 'any',
/**
* Equivalent to: `127.0.0.0/8`
*
* @see [RFC1122](https://www.rfc-editor.org/rfc/rfc1122)
*/
Loopback = 'loopback',
/**
* Equivalent to: `10.0.0.0/8`
*
* @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
*/
PrivateA = 'private-a',
/**
* Equivalent to: `172.16.0.0/12`
*
* @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
*/
PrivateB = 'private-b',
/**
* Equivalent to: `192.168.0.0/16`
*
* @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
*/
PrivateC = 'private-c',
/**
* Equivalent to: `192.0.2.0/24`
*
* @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
*/
TestNet1 = 'test-net-1',
/**
* Equivalent to: `198.51.100.0/24`
*
* @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
*/
TestNet2 = 'test-net-2',
/**
* Equivalent to: `203.0.113.0/24`
*
* @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
*/
TestNet3 = 'test-net-3',
/**
* Equivalent to: `169.254.0.0/16`
*
* @see [RFC3927](https://www.rfc-editor.org/rfc/rfc3927)
*/
LinkLocal = 'link-local',
/**
* Equivalent to: `224.0.0.0/4`
*
* @see [RFC5771](https://www.rfc-editor.org/rfc/rfc5771)
*/
Multicast = 'multicast',
}

export type IPv4NetworkType = `${IPv4Network}`;

const ipv4Networks: Record<IPv4Network, string> = {
[IPv4Network.Any]: '0.0.0.0/0',
[IPv4Network.Loopback]: '127.0.0.0/8',
[IPv4Network.PrivateA]: '10.0.0.0/8',
[IPv4Network.PrivateB]: '172.16.0.0/12',
[IPv4Network.PrivateC]: '192.168.0.0/16',
[IPv4Network.TestNet1]: '192.0.2.0/24',
[IPv4Network.TestNet2]: '198.51.100.0/24',
[IPv4Network.TestNet3]: '203.0.113.0/24',
[IPv4Network.LinkLocal]: '169.254.0.0/16',
[IPv4Network.Multicast]: '224.0.0.0/4',
};

/**
* Module to generate internet related entries.
*
Expand Down Expand Up @@ -485,15 +562,100 @@ export class InternetModule extends ModuleBase {
/**
* Generates a random IPv4 address.
*
* @param options The optional options object.
* @param options.cidrBlock The optional CIDR block to use. Must be in the format `x.x.x.x/y`. Defaults to `'0.0.0.0/0'`.
*
* @example
* faker.internet.ipv4() // '245.108.222.0'
* faker.internet.ipv4({ cidrBlock: '192.168.0.0/16' }) // '192.168.215.224'
*
* @since 6.1.1
*/
ipv4(): string {
return Array.from({ length: 4 }, () => this.faker.number.int(255)).join(
'.'
);
ipv4(options?: {
/**
* The optional CIDR block to use. Must be in the format `x.x.x.x/y`.
*
* @default '0.0.0.0/0'
*/
cidrBlock?: string;
}): string;
/**
* Generates a random IPv4 address.
*
* @param options The optional options object.
* @param options.network The optional network to use. This is intended as an alias for well-known `cidrBlock`s. Defaults to `'any'`.
*
* @example
* faker.internet.ipv4() // '245.108.222.0'
* faker.internet.ipv4({ network: 'private-a' }) // '10.199.154.205'
*
* @since 6.1.1
*/
ipv4(options?: {
/**
* The optional network to use. This is intended as an alias for well-known `cidrBlock`s.
*
* @default 'any'
*/
network?: IPv4NetworkType;
}): string;
/**
* Generates a random IPv4 address.
*
* @param options The optional options object.
* @param options.cidrBlock The optional CIDR block to use. Must be in the format `x.x.x.x/y`. Defaults to `'0.0.0.0/0'`.
* @param options.network The optional network to use. This is intended as an alias for well-known `cidrBlock`s. Defaults to `'any'`.
*
* @example
* faker.internet.ipv4() // '245.108.222.0'
* faker.internet.ipv4({ cidrBlock: '192.168.0.0/16' }) // '192.168.215.224'
* faker.internet.ipv4({ network: 'private-a' }) // '10.199.154.205'
*
* @since 6.1.1
*/
ipv4(
options?:
| {
/**
* The optional CIDR block to use. Must be in the format `x.x.x.x/y`.
*
* @default '0.0.0.0/0'
*/
cidrBlock?: string;
}
| {
/**
* The optional network to use. This is intended as an alias for well-known `cidrBlock`s.
*
* @default 'any'
*/
network?: IPv4NetworkType;
}
): string;
ipv4(
options: { cidrBlock?: string; network?: IPv4NetworkType } = {}
): string {
const { network = 'any', cidrBlock = ipv4Networks[network] } = options;

if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/.test(cidrBlock)) {
throw new FakerError(
`Invalid CIDR block provided: ${cidrBlock}. Must be in the format x.x.x.x/y.`
);
}

const [ipText, subnet] = cidrBlock.split('/');
const subnetMask = 0xffffffff >>> Number.parseInt(subnet);
const [rawIp1, rawIp2, rawIp3, rawIp4] = ipText.split('.').map(Number);
const rawIp = (rawIp1 << 24) | (rawIp2 << 16) | (rawIp3 << 8) | rawIp4;
const networkIp = rawIp & ~subnetMask;
const hostOffset = this.faker.number.int(subnetMask);
const ip = networkIp | hostOffset;
return [
(ip >>> 24) & 0xff,
(ip >>> 16) & 0xff,
(ip >>> 8) & 0xff,
ip & 0xff,
].join('.');
}

/**
Expand Down
22 changes: 17 additions & 5 deletions test/modules/__snapshots__/internet.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ exports[`internet > 42 > httpStatusCode > noArgs 1`] = `226`;

exports[`internet > 42 > httpStatusCode > with options 1`] = `410`;

exports[`internet > 42 > ip 1`] = `"243.187.153.39"`;
exports[`internet > 42 > ip 1`] = `"243.98.3.69"`;

exports[`internet > 42 > ipv4 1`] = `"95.243.187.153"`;
exports[`internet > 42 > ipv4 > noArgs 1`] = `"95.225.220.121"`;

exports[`internet > 42 > ipv4 > with cidrBlock 1`] = `"192.168.13.95"`;

exports[`internet > 42 > ipv4 > with network 1`] = `"229.254.29.199"`;

exports[`internet > 42 > ipv6 1`] = `"8ead:331d:df0f:c444:6b96:d368:ab4b:d1d3"`;

Expand Down Expand Up @@ -182,7 +186,11 @@ exports[`internet > 1211 > httpStatusCode > with options 1`] = `429`;

exports[`internet > 1211 > ip 1`] = `"d4fe:fa7f:baec:9dc4:c48f:a8eb:f46f:b7c8"`;

exports[`internet > 1211 > ipv4 1`] = `"237.228.57.255"`;
exports[`internet > 1211 > ipv4 > noArgs 1`] = `"237.179.127.46"`;

exports[`internet > 1211 > ipv4 > with cidrBlock 1`] = `"192.168.13.237"`;

exports[`internet > 1211 > ipv4 > with network 1`] = `"238.219.55.242"`;

exports[`internet > 1211 > ipv6 1`] = `"ed4f:efa7:fbae:c9dc:4c48:fa8e:bf46:fb7c"`;

Expand Down Expand Up @@ -296,9 +304,13 @@ exports[`internet > 1337 > httpStatusCode > noArgs 1`] = `201`;

exports[`internet > 1337 > httpStatusCode > with options 1`] = `407`;

exports[`internet > 1337 > ip 1`] = `"40.71.117.82"`;
exports[`internet > 1337 > ip 1`] = `"40.159.131.70"`;

exports[`internet > 1337 > ipv4 > noArgs 1`] = `"67.20.12.145"`;

exports[`internet > 1337 > ipv4 > with cidrBlock 1`] = `"192.168.13.67"`;

exports[`internet > 1337 > ipv4 1`] = `"67.40.71.117"`;
exports[`internet > 1337 > ipv4 > with network 1`] = `"228.49.64.201"`;

exports[`internet > 1337 > ipv6 1`] = `"536a:7b5f:a28d:2f9b:b79c:a46e:a394:bc4f"`;

Expand Down
98 changes: 97 additions & 1 deletion test/modules/internet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import validator from 'validator';
import { describe, expect, it } from 'vitest';
import { allFakers, faker } from '../../src';
import { FakerError } from '../../src/errors/faker-error';
import { IPv4Network } from '../../src/modules/internet';
import { seededTests } from '../support/seeded-runs';
import { times } from './../support/times';

Expand All @@ -15,7 +17,6 @@ describe('internet', () => {
'domainSuffix',
'domainWord',
'ip',
'ipv4',
'ipv6',
'port',
'userAgent'
Expand Down Expand Up @@ -133,6 +134,12 @@ describe('internet', () => {
protocol: 'http',
});
});

t.describe('ipv4', (t) => {
t.it('noArgs')
.it('with cidrBlock', { cidrBlock: '192.168.13.37/24' })
.it('with network', { network: IPv4Network.Multicast });
});
});

describe.each(times(NON_SEEDED_BASED_RUN).map(() => faker.seed()))(
Expand Down Expand Up @@ -595,6 +602,95 @@ describe('internet', () => {
expect(+part).toBeLessThanOrEqual(255);
}
});

it('should return a random IPv4 for a given CIDR block', () => {
const actual = faker.internet.ipv4({
cidrBlock: '192.168.42.255/24',
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
});

expect(actual).toBeTruthy();
expect(actual).toBeTypeOf('string');
expect(actual).toSatisfy((value: string) => validator.isIP(value, 4));
expect(actual).toMatch(/^192\.168\.42\.\d{1,3}$/);
});

it('should return a random IPv4 for a given CIDR block non-8ish network mask', () => {
const actual = faker.internet.ipv4({
cidrBlock: '192.168.0.255/20',
});

expect(actual).toBeTruthy();
expect(actual).toBeTypeOf('string');
expect(actual).toSatisfy((value: string) => validator.isIP(value, 4));

const [first, second, third, fourth] = actual.split('.').map(Number);
expect(first).toBe(192);
expect(second).toBe(168);
expect(third).toBeGreaterThanOrEqual(0);
expect(third).toBeLessThanOrEqual(15);
expect(fourth).toBeGreaterThanOrEqual(0);
expect(fourth).toBeLessThanOrEqual(255);
});

it.each([
'',
'...',
'.../',
'.0.0.0/0',
'0..0.0/0',
'0.0..0/0',
'0.0.0./0',
'0.0.0.0/',
'a.0.0.0/0',
'0.b.0.0/0',
'0.0.c.0/0',
'0.0.0.d/0',
'0.0.0.0/e',
])(
'should throw an error if not following the x.x.x.x/y format',
(cidrBlock) => {
expect(() =>
faker.internet.ipv4({
cidrBlock,
})
).toThrow(
new FakerError(
`Invalid CIDR block provided: ${cidrBlock}. Must be in the format x.x.x.x/y.`
)
);
}
);

it.each([
[IPv4Network.Any, /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
[IPv4Network.Loopback, /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
[IPv4Network.PrivateA, /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
[
IPv4Network.PrivateB,
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/,
],
[IPv4Network.PrivateC, /^192\.168\.\d{1,3}\.\d{1,3}$/],
[IPv4Network.TestNet1, /^192\.0\.2\.\d{1,3}$/],
[IPv4Network.TestNet2, /^198\.51\.100\.\d{1,3}$/],
[IPv4Network.TestNet3, /^203\.0\.113\.\d{1,3}$/],
[IPv4Network.LinkLocal, /^169\.254\.\d{1,3}\.\d{1,3}$/],
[
IPv4Network.Multicast,
/^2(2[4-9]|3[0-9])\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
],
] as const)(
'should return a random IPv4 for %s network',
(network, regex) => {
const actual = faker.internet.ipv4({ network });

expect(actual).toBeTruthy();
expect(actual).toBeTypeOf('string');
expect(actual).toSatisfy((value: string) =>
validator.isIP(value, 4)
);
expect(actual).toMatch(regex);
}
);
});

describe('ipv6()', () => {
Expand Down