UNPKG

the-moby-effect

Version:
314 lines 10.5 kB
/** * IPv4 or IPv6 cidr block schemas. * * @since 1.0.0 */ import * as Array from "effect/Array"; import * as Effect from "effect/Effect"; import * as Function from "effect/Function"; import * as ParseResult from "effect/ParseResult"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import * as Address from "./address.js"; import * as CidrBlockMask from "./cidrBlockMask.js"; import * as IPv4 from "./ipv4.js"; import * as IPv6 from "./ipv6.js"; /** @internal */ export const tail = elements => elements.slice(1); /** @internal */ export const splitLiteral = (str, delimiter) => str.split(delimiter); /** * @since 1.0.0 * @category Api interface */ export class CidrBlockBase extends /*#__PURE__*/Schema.Class("CidrBlockMixin")({ address: Address.Address, mask: /*#__PURE__*/Schema.Union(CidrBlockMask.IPv4CidrMask, CidrBlockMask.IPv6CidrMask) }) { /** @since 1.0.0 */ family = this.address.family; /** @internal */ onFamily({ onIPv4, onIPv6 }) { const isIPv4 = () => this.family === "ipv4"; const isIPv6 = () => this.family === "ipv6"; if (isIPv4()) { return onIPv4(this); } else if (isIPv6()) { return onIPv6(this); } else { return Function.absurd(this.family); } } /** * The first address in the range given by this address' subnet, often * referred to as the Network Address. * * @since 1.0.0 */ get networkAddressAsBigint() { return Effect.gen(this, function* () { const bits = this.family === "ipv4" ? 32 : 128; const bigIntegerAddress = yield* this.onFamily({ onIPv4: self => Schema.decode(IPv4.IPv4Bigint)(self.address.ip), onIPv6: self => Schema.decode(IPv6.IPv6Bigint)(self.address.ip) }); const intermediate = bigIntegerAddress.value.toString(2).padStart(bits, "0").slice(0, this.mask); const networkAddressString = intermediate + "0".repeat(bits - this.mask); const networkAddressBigInt = BigInt(`0b${networkAddressString}`); return this.onFamily({ onIPv4: _self => IPv4.IPv4BigintBrand(networkAddressBigInt), onIPv6: _self => IPv6.IPv6BigintBrand(networkAddressBigInt) }); }); } /** * The first address in the range given by this address' subnet, often * referred to as the Network Address. * * @since 1.0.0 */ networkAddress() { return this.onFamily({ onIPv4: self => Function.pipe(self.networkAddressAsBigint, Effect.flatMap(value => Schema.encode(IPv4.IPv4Bigint)({ value, family: "ipv4" })), Effect.flatMap(Schema.decode(IPv4.IPv4))), onIPv6: self => Function.pipe(self.networkAddressAsBigint, Effect.flatMap(value => Schema.encode(IPv6.IPv6Bigint)({ value, family: "ipv6" })), Effect.flatMap(Schema.decode(IPv6.IPv6))) }); } /** * The last address in the range given by this address' subnet, often * referred to as the Broadcast Address. * * @since 1.0.0 */ get broadcastAddressAsBigint() { return Effect.gen(this, function* () { const bits = this.family === "ipv4" ? 32 : 128; const bigIntegerAddress = yield* this.onFamily({ onIPv4: self => Schema.decode(IPv4.IPv4Bigint)(self.address.ip), onIPv6: self => Schema.decode(IPv6.IPv6Bigint)(self.address.ip) }); const intermediate = bigIntegerAddress.value.toString(2).padStart(bits, "0").slice(0, this.mask); const broadcastAddressString = intermediate + "1".repeat(bits - this.mask); const broadcastAddressBigInt = BigInt(`0b${broadcastAddressString}`); return this.onFamily({ onIPv4: _self => IPv4.IPv4BigintBrand(broadcastAddressBigInt), onIPv6: _self => IPv6.IPv6BigintBrand(broadcastAddressBigInt) }); }); } /** * The last address in the range given by this address' subnet, often * referred to as the Broadcast Address. * * @since 1.0.0 */ broadcastAddress() { return this.onFamily({ onIPv4: self => Function.pipe(self.broadcastAddressAsBigint, Effect.flatMap(value => Schema.encode(IPv4.IPv4Bigint)({ value, family: "ipv4" })), Effect.flatMap(Schema.decode(IPv4.IPv4))), onIPv6: self => Function.pipe(self.broadcastAddressAsBigint, Effect.flatMap(value => Schema.encode(IPv6.IPv6Bigint)({ value, family: "ipv6" })), Effect.flatMap(Schema.decode(IPv6.IPv6))) }); } /** * A stream of all addresses in the range given by this address' subnet. * * @since 1.0.0 */ get range() { return this.onFamily({ onIPv4: self => Effect.gen(function* () { const minValue = yield* self.networkAddressAsBigint; const maxValue = yield* self.broadcastAddressAsBigint; return Function.pipe(Stream.iterate(minValue, x => IPv4.IPv4BigintBrand(x + 1n)), Stream.takeWhile(n => n <= maxValue), Stream.flatMap(value => Schema.encode(IPv4.IPv4Bigint)({ value, family: "ipv4" })), Stream.mapEffect(Schema.decode(IPv4.IPv4))); }).pipe(Stream.unwrap), onIPv6: self => Effect.gen(function* () { const minValue = yield* self.networkAddressAsBigint; const maxValue = yield* self.broadcastAddressAsBigint; return Function.pipe(Stream.iterate(minValue, x => IPv6.IPv6BigintBrand(x + 1n)), Stream.takeWhile(n => n <= maxValue), Stream.flatMap(value => Schema.encode(IPv6.IPv6Bigint)({ value, family: "ipv6" })), Stream.mapEffect(Schema.decode(IPv6.IPv6))); }).pipe(Stream.unwrap) }); } /** * The total number of addresses in the range given by this address' subnet. * * @since 1.0.0 */ get total() { return Effect.gen(this, function* () { const minValue = yield* this.networkAddressAsBigint; const maxValue = yield* this.broadcastAddressAsBigint; return maxValue - minValue + 1n; }); } /** * Finds the smallest CIDR block that contains all the given IP addresses. * * @since 1.0.0 */ static cidrBlockForRange = inputs => Effect.gen(function* () { const bigIntMinAndMax = args => { return args.reduce(([min, max], e) => { return [e < min ? e : min, e > max ? e : max]; }, [args[0], args[0]]); }; const bigints = yield* Function.pipe(inputs, Array.map(address => address.family === "ipv4" ? Schema.decode(IPv4.IPv4Bigint)(address.ip) : Schema.decode(IPv6.IPv6Bigint)(address.ip)), Array.map(x => x), Array.map(Effect.map(({ value }) => value)), Effect.all); const bits = inputs[0].family === "ipv4" ? 32 : 128; const [min, max] = bigIntMinAndMax(bigints); const leadingZerosInMin = bits - min.toString(2).length; const leadingZerosInMax = bits - max.toString(2).length; const cidrMask = Math.min(leadingZerosInMin, leadingZerosInMax); const cidrAddress = inputs[0].family === "ipv4" ? yield* Schema.encode(IPv4.IPv4Bigint)({ value: IPv4.IPv4BigintBrand(min), family: "ipv4" }) : yield* Schema.encode(IPv6.IPv6Bigint)({ value: IPv6.IPv6BigintBrand(min), family: "ipv6" }); return yield* Schema.decode(CidrBlockFromString)(`${cidrAddress}/${cidrMask}`); }); } /** * @since 1.0.0 * @category Schemas */ export const IPv4CidrBlock = /*#__PURE__*/Schema.transformOrFail(Schema.Struct({ address: IPv4.IPv4, mask: CidrBlockMask.IPv4CidrMask }), CidrBlockBase, { encode: data => Effect.gen(function* () { const address = yield* Schema.decode(IPv4.IPv4)(data.address); const mask = yield* Schema.decode(CidrBlockMask.IPv4CidrMask)(data.mask); return { address, mask }; }).pipe(Effect.mapError(({ issue }) => issue)), decode: data => ParseResult.succeed({ address: data.address.ip, mask: data.mask }) }).annotations({ identifier: "IPv4CidrBlock", description: "An ipv4 cidr block" }); /** * A schema that transforms a `string` into a `CidrBlock`. * * @since 1.0.0 * @category Schemas */ export const IPv4CidrBlockFromString = /*#__PURE__*/Schema.transform(Schema.TemplateLiteral(Schema.String, Schema.Literal("/"), Schema.Number), IPv4CidrBlock, { decode: str => { const [address, mask] = splitLiteral(str, "/"); return { address, mask: Number.parseInt(mask, 10) }; }, encode: ({ address, mask }) => `${address}/${mask}` }).annotations({ identifier: "IPv4CidrBlockFromString", description: "An ipv4 cidr block from string" }); /** * @since 1.0.0 * @category Schemas */ export const IPv6CidrBlock = /*#__PURE__*/Schema.transformOrFail(Schema.Struct({ address: IPv6.IPv6, mask: CidrBlockMask.IPv6CidrMask }), CidrBlockBase, { encode: data => Effect.gen(function* () { const address = yield* Schema.decode(IPv6.IPv6)(data.address); const mask = yield* Schema.decode(CidrBlockMask.IPv6CidrMask)(data.mask); return { address, mask }; }).pipe(Effect.mapError(({ issue }) => issue)), decode: data => ParseResult.succeed({ address: data.address.ip, mask: data.mask }) }).annotations({ identifier: "IPv6CidrBlock", description: "An ipv6 cidr block" }); /** * A schema that transforms a `string` into a `CidrBlock`. * * @since 1.0.0 * @category Schemas */ export const IPv6CidrBlockFromString = /*#__PURE__*/Schema.transform(Schema.TemplateLiteral(Schema.String, Schema.Literal("/"), Schema.Number), IPv6CidrBlock, { decode: str => { const [address, mask] = splitLiteral(str, "/"); return { address, mask: Number.parseInt(mask, 10) }; }, encode: ({ address, mask }) => `${address}/${mask}` }).annotations({ identifier: "IPv6CidrBlockFromString", description: "An ipv6 cidr block from string" }); /** * @since 1.0.0 * @category Schemas */ export const CidrBlock = /*#__PURE__*/Schema.Union(IPv4CidrBlock, IPv6CidrBlock); /** * A schema that transforms a `string` into a `CidrBlock`. * * @since 1.0.0 * @category Schemas */ export const CidrBlockFromString = /*#__PURE__*/Schema.transform(Schema.TemplateLiteral(Schema.String, Schema.Literal("/"), Schema.Number), CidrBlock, { decode: str => { const [address, mask] = splitLiteral(str, "/"); return { address, mask: Number.parseInt(mask, 10) }; }, encode: ({ address, mask }) => `${address}/${mask}` }).annotations({ identifier: "CidrBlockFromString", description: "A cidr block" }); //# sourceMappingURL=cidrBlock.js.map