Skip to content

Commit

Permalink
feat(internet): improve ipv4 method (#2992)
Browse files Browse the repository at this point in the history
  • Loading branch information
ST-DDT authored Oct 10, 2024
1 parent 5b1c858 commit a5a6c5b
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 11 deletions.
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',
});

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

0 comments on commit a5a6c5b

Please sign in to comment.