diff --git a/README.md b/README.md
index e4c7879..e947aac 100644
--- a/README.md
+++ b/README.md
@@ -175,69 +175,69 @@ A few terms used throughout the API can be confusing if you haven't worked deepl
- `static isValid(address: string): boolean` — Returns true if the given string is a valid IPv6 address (with optional CIDR subnet and zone identifier), false otherwise. Host bits in the subnet portion are allowed (e.g. `2001:db8::1/32` is valid); for strict network-address validation compare `correctForm()` to `startAddress().correctForm()`, or use `networkForm()`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L167)
- `static fromBigInt(bigInt: bigint): Address6` — Convert a BigInt to a v6 address object. The value must be in the range `[0, 2**128 - 1]`; otherwise `AddressError` is thrown. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L188)
- `static fromURL(url: string): { error: string; address: null; port: null } | { error?: undefined; address: Address6; port: number | null }` — Parse a URL (with optional bracketed host and port) into an address and port. Returns either `{ address, port }` on success or `{ error, address: null, port: null }` if the URL could not be parsed. Ports are returned as numbers (or `null` if absent or out of range). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L213)
-- `static fromAddressAndMask(address: string, mask: string): Address6` — Construct an `Address6` from an address and a hex subnet mask given as separate strings (e.g. as returned by Node's `os.networkInterfaces()`). Throws `AddressError` if the mask is non-contiguous (e.g. `ffff::ffff`). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L277)
-- `static fromAddressAndWildcardMask(address: string, wildcardMask: string): Address6` — Construct an `Address6` from an address and a Cisco-style wildcard mask given as separate strings (e.g. `::ffff:ffff:ffff:ffff` for a `/64`). The wildcard mask is the bitwise inverse of the subnet mask. Throws `AddressError` if the mask is non-contiguous. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L291)
-- `static fromWildcard(input: string): Address6` — Construct an `Address6` from a wildcard pattern with trailing `*` groups. The number of trailing wildcards determines the prefix length: each `*` represents 16 bits. `::` is expanded to zero groups (not wildcards) before evaluating trailing wildcards. Only trailing whole-group wildcards are supported. Partial-group wildcards (e.g. `2001:db8::0*`) and interior wildcards (e.g. `*::1`) throw `AddressError`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L313)
-- `static fromAddress4(address: string): Address6` — Create an IPv6-mapped address given an IPv4 address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L374)
-- `static fromArpa(arpaFormAddress: string): Address6` — Return an address from ip6.arpa form [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L390)
-- `static fromAddress4Nat64(address: string, prefix: string): Address6` — Embed an IPv4 address into a NAT64 IPv6 address using the encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052). The default prefix is the well-known prefix `64:ff9b::/96`. The prefix length must be one of 32, 40, 48, 56, 64, or 96; for prefixes shorter than /64 the IPv4 octets are split around the reserved bits 64–71. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1060)
-- `static fromByteArray(bytes: number[]): Address6` — Convert a byte array to an Address6 object. Accepts unsigned bytes (0 to 255) or signed bytes (-128 to 127, as an `Int8Array` or a Java `byte[]` holds them), folding signed values to their unsigned equivalent. Throws `AddressError` unless given exactly 16 integers from -128 to 255. To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1175)
-- `static fromUnsignedByteArray(bytes: number[]): Address6` — Convert an unsigned byte array to an Address6 object. Throws `AddressError` unless given exactly 16 integers from 0 to 255. To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1192)
+- `static fromAddressAndMask(address: string, mask: string): Address6` — Construct an `Address6` from an address and a hex subnet mask given as separate strings (e.g. as returned by Node's `os.networkInterfaces()`). Throws `AddressError` if the mask is non-contiguous (e.g. `ffff::ffff`). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L280)
+- `static fromAddressAndWildcardMask(address: string, wildcardMask: string): Address6` — Construct an `Address6` from an address and a Cisco-style wildcard mask given as separate strings (e.g. `::ffff:ffff:ffff:ffff` for a `/64`). The wildcard mask is the bitwise inverse of the subnet mask. Throws `AddressError` if the mask is non-contiguous. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L294)
+- `static fromWildcard(input: string): Address6` — Construct an `Address6` from a wildcard pattern with trailing `*` groups. The number of trailing wildcards determines the prefix length: each `*` represents 16 bits. `::` is expanded to zero groups (not wildcards) before evaluating trailing wildcards. Only trailing whole-group wildcards are supported. Partial-group wildcards (e.g. `2001:db8::0*`) and interior wildcards (e.g. `*::1`) throw `AddressError`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L316)
+- `static fromAddress4(address: string): Address6` — Create an IPv6-mapped address given an IPv4 address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L377)
+- `static fromArpa(arpaFormAddress: string): Address6` — Return an address from ip6.arpa form [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L393)
+- `static fromAddress4Nat64(address: string, prefix: string): Address6` — Embed an IPv4 address into a NAT64 IPv6 address using the encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052). The default prefix is the well-known prefix `64:ff9b::/96`. The prefix length must be one of 32, 40, 48, 56, 64, or 96; for prefixes shorter than /64 the IPv4 octets are split around the reserved bits 64–71. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1063)
+- `static fromByteArray(bytes: number[]): Address6` — Convert a byte array to an Address6 object. Accepts unsigned bytes (0 to 255) or signed bytes (-128 to 127, as an `Int8Array` or a Java `byte[]` holds them), folding signed values to their unsigned equivalent. Throws `AddressError` unless given exactly 16 integers from -128 to 255. To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1178)
+- `static fromUnsignedByteArray(bytes: number[]): Address6` — Convert an unsigned byte array to an Address6 object. Throws `AddressError` unless given exactly 16 integers from 0 to 255. To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1195)
**Instance methods**
-- `microsoftTranscription(): string` — Return the Microsoft UNC transcription of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L416)
-- `mask(mask?: number): string` — Return the first n bits of the address, defaulting to the subnet mask [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L425)
-- `possibleSubnets(subnetSize?: number): string` — Return the number of possible subnets of a given size in the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L435)
-- `startAddress(): Address6` — The first address in the range given by this address' subnet Often referred to as the Network Address. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L460)
-- `startAddressExclusive(): Address6` — The first host address in the range given by this address's subnet ie the first address after the Network Address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L469)
-- `endAddress(): Address6` — The last address in the range given by this address' subnet Often referred to as the Broadcast [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L487)
-- `endAddressExclusive(): Address6` — The last host address in the range given by this address's subnet ie the last address prior to the Broadcast Address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L496)
-- `subnetMaskAddress(): Address6` — The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a `/64`. Returns an `Address6`; call `.correctForm()` for the string. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L506)
-- `wildcardMask(): Address6` — The Cisco-style wildcard mask, e.g. `::ffff:ffff:ffff:ffff` for a `/64`. This is the bitwise inverse of `subnetMaskAddress()`. Returns an `Address6`; call `.correctForm()` for the string. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L518)
-- `networkForm(): string` — The network address in CIDR string form, e.g. `2001:db8::/32` for `2001:db8::1/32`. For an address with no explicit subnet the prefix is `/128`, e.g. `networkForm()` on `2001:db8::1` returns `2001:db8::1/128`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L531)
-- `getScope(): string` — Return the scope of the address. The 4-bit scope field ([RFC 4291 §2.7](https://datatracker.ietf.org/doc/html/rfc4291#section-2.7)) is only defined for multicast addresses; for unicast addresses the scope is derived from the address type per [RFC 4007 §6](https://datatracker.ietf.org/doc/html/rfc4007#section-6). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L543)
-- `getType(): string` — Return the type of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L570)
-- `getBits(start: number, end: number): bigint` — Return the bits in the given range as a BigInt [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L585)
-- `getBitsBase2(start: number, end: number): string` — Return the bits in the given range as a base-2 string [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L593)
-- `getBitsBase16(start: number, end: number): string` — Return the bits in the given range as a base-16 string [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L601)
-- `getBitsPastSubnet(): string` — Return the bits that are set past the subnet mask length [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L617)
-- `reverseForm(options?: ReverseFormOptions): string` — Return the reversed ip6.arpa form of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L627)
-- `correctForm(): string` — Returns the address in correct form, per [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952): leading zeros stripped, the longest run of zero groups collapsed to `::`, and hex digits lowercased (e.g. `2001:db8::1`). This is the recommended form for display. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L662)
-- `binaryZeroPad(): string` — Return a zero-padded base-2 string representation of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L724)
-- `parse4in6(address: string): string` — Parses a v4-in-v6 string (e.g. `::ffff:192.168.0.1`) by extracting the trailing IPv4 address into `this.address4` / `this.parsedAddress4` and returning the address with the v4 portion converted to two v6 groups. Used internally by `parse()`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L738)
-- `parse(address: string): string[]` — Parses an IPv6 address string into its 8 hexadecimal groups (expanding any `::` elision and any trailing v4-in-v6 portion) and stores the result on `this.parsedAddress`. Called automatically by the constructor; you typically don't need to call it directly. Throws `AddressError` if the input is malformed. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L795)
-- `canonicalForm(): string` — Returns the canonical (fully expanded) form of the address: all 8 groups, each padded to 4 hex digits, with no `::` collapsing (e.g. `2001:0db8:0000:0000:0000:0000:0000:0001`). Useful for sorting and byte-exact comparison. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L875)
-- `decimal(): string` — Return the decimal form of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L883)
-- `bigInt(): bigint` — Return the address as a BigInt [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L891)
-- `to4(): Address4` — Return the last two groups of this address as an IPv4 address string. If this address carries a CIDR prefix that covers the trailing 32 bits (i.e. `subnetMask >= 96`), the resulting `Address4` inherits the corresponding v4 prefix (`subnetMask - 96`); otherwise it defaults to `/32`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L906)
-- `to4in6(): string` — Return the v4-in-v6 form of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L930)
-- `inspectTeredo(): TeredoProperties` — Decodes the Teredo tunneling fields embedded in this address. Returns the Teredo prefix, server IPv4, client IPv4, raw flag bits, cone-NAT flag, UDP port, and Microsoft-format flag breakdown (reserved, universal/local, group/individual, nonce). Only meaningful for addresses in `2001::/32`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L951)
-- `inspect6to4(): SixToFourProperties` — Decodes the 6to4 tunneling fields embedded in this address. Returns the 6to4 prefix and the embedded IPv4 gateway address. Only meaningful for addresses in `2002::/16`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1014)
-- `to6to4(): Address6 | null` — Return a v6 6to4 address from a v6 v4inv6 address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1034)
-- `toAddress4Nat64(prefix: string): Address4 | null` — Extract the embedded IPv4 address from a NAT64 IPv6 address using the encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052). The default prefix is the well-known prefix `64:ff9b::/96`. Returns `null` if this address is not contained within the given prefix. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1103)
-- `toByteArray(): number[]` — Return a byte array. To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toByteArray())`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1138)
-- `toUnsignedByteArray(): number[]` — Return an unsigned byte array. To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toUnsignedByteArray())`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1157)
-- `isCanonical(): boolean` — Returns true if the address is in the canonical form, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1233)
-- `isLinkLocal(): boolean` — Returns true if the address is a link local address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1241)
-- `isMulticast(): boolean` — Returns true if the address is a multicast address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1262)
-- `is4(): boolean` — Returns true if the address was written in v4-in-v6 dotted-quad notation (e.g. `::ffff:127.0.0.1`), false otherwise. This is a notation-level flag and does not reflect whether the address bits lie in the IPv4-mapped (`::ffff:0:0/96`) subnet — for that, see isMapped4. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1279)
-- `isMapped4(): boolean` — Returns true if the address is an IPv4-mapped IPv6 address in `::ffff:0:0/96` ([RFC 4291 §2.5.5.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2)), false otherwise. Unlike is4, this checks the underlying address bits rather than the textual notation, so `::ffff:127.0.0.1` and `::ffff:7f00:1` both return true. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1291)
-- `embeddedIPv4(): Address4 | null` — If this address embeds a routable IPv4 address — i.e. it is IPv4-mapped (`::ffff:0:0/96`) or sits in the NAT64 well-known prefix (`64:ff9b::/96`, [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052)) — return that embedded address as an Address4; otherwise return null. The special-property checks (`isLoopback`, `isLinkLocal`, `isMulticast`, `isUnspecified`, `isPrivate`, `isCGNAT`, `isBroadcast`) call this first and delegate to the embedded Address4 when present, so a literal such as `::ffff:127.0.0.1` is classified by what it actually reaches (loopback) rather than by its IPv6 wrapper (which `getType()` reports as IPv4-mapped). This matters wherever the checks back a trust-boundary decision (e.g. an SSRF allow/deny filter): without normalization, `::ffff:10.0.0.1`, `::ffff:169.254.169.254`, `64:ff9b::7f00:1`, etc. would all read as non-internal. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1312)
-- `isTeredo(): boolean` — Returns true if the address is a Teredo address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1324)
-- `is6to4(): boolean` — Returns true if the address is a 6to4 address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1332)
-- `isLoopback(): boolean` — Returns true if the address is a loopback address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1340)
-- `isULA(): boolean` — Returns true if the address is a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)). ULAs are the IPv6 equivalent of IPv4 [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private addresses. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1353)
-- `isPrivate(): boolean` — Returns true if the address is private, i.e. a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)) or an IPv4-mapped / NAT64 address whose embedded IPv4 address is in one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private ranges (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to Address4.isPrivate; use it instead of isULA when you need to catch mapped RFC 1918 addresses as well as native ULAs. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1367)
-- `isCGNAT(): boolean` — Returns true if the address is an IPv4-mapped / NAT64 address whose embedded IPv4 address is in the carrier-grade NAT range `100.64.0.0/10` ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)), false otherwise. There is no native IPv6 CGNAT range, so this only ever returns true for an embedded IPv4 address (e.g. `::ffff:100.64.0.1`). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1384)
-- `isBroadcast(): boolean` — Returns true if the address is an IPv4-mapped / NAT64 address whose embedded IPv4 address is the limited broadcast address `255.255.255.255` ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)), false otherwise. There is no IPv6 broadcast, so this only ever returns true for an embedded IPv4 address (e.g. `::ffff:255.255.255.255`). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1401)
-- `isUnspecified(): boolean` — Returns true if the address is the unspecified address `::`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1414)
-- `isDocumentation(): boolean` — Returns true if the address is in the documentation prefix `2001:db8::/32` ([RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849)). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1427)
-- `href(optionalPort?: string | number): string` — Returns the address as an HTTP URL with the host bracketed, e.g. `http://[2001:db8::1]/`. If `optionalPort` is provided it is appended, e.g. `http://[2001:db8::1]:8080/`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1438)
-- `link(options?: { className?: string; prefix?: string; v4?: boolean }): string` — Returns an HTML `` element whose `href` encodes the address in a URL hash fragment (default prefix `/#address=`). Useful for linking between pages of an address-inspector UI. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1456)
-- `group(): string` — Groups an address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1496)
-- `regularExpressionString(this: Address6, substringSearch: boolean): string` — Generate a regular expression string that can be used to find or validate all variations of this address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1548)
-- `regularExpression(this: Address6, substringSearch: boolean): RegExp` — Generate a regular expression that can be used to find or validate all variations of this address. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1602)
+- `microsoftTranscription(): string` — Return the Microsoft UNC transcription of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L419)
+- `mask(mask?: number): string` — Return the first n bits of the address, defaulting to the subnet mask [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L428)
+- `possibleSubnets(subnetSize?: number): string` — Return the number of possible subnets of a given size in the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L438)
+- `startAddress(): Address6` — The first address in the range given by this address' subnet Often referred to as the Network Address. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L463)
+- `startAddressExclusive(): Address6` — The first host address in the range given by this address's subnet ie the first address after the Network Address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L472)
+- `endAddress(): Address6` — The last address in the range given by this address' subnet Often referred to as the Broadcast [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L490)
+- `endAddressExclusive(): Address6` — The last host address in the range given by this address's subnet ie the last address prior to the Broadcast Address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L499)
+- `subnetMaskAddress(): Address6` — The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a `/64`. Returns an `Address6`; call `.correctForm()` for the string. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L509)
+- `wildcardMask(): Address6` — The Cisco-style wildcard mask, e.g. `::ffff:ffff:ffff:ffff` for a `/64`. This is the bitwise inverse of `subnetMaskAddress()`. Returns an `Address6`; call `.correctForm()` for the string. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L521)
+- `networkForm(): string` — The network address in CIDR string form, e.g. `2001:db8::/32` for `2001:db8::1/32`. For an address with no explicit subnet the prefix is `/128`, e.g. `networkForm()` on `2001:db8::1` returns `2001:db8::1/128`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L534)
+- `getScope(): string` — Return the scope of the address. The 4-bit scope field ([RFC 4291 §2.7](https://datatracker.ietf.org/doc/html/rfc4291#section-2.7)) is only defined for multicast addresses; for unicast addresses the scope is derived from the address type per [RFC 4007 §6](https://datatracker.ietf.org/doc/html/rfc4007#section-6). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L546)
+- `getType(): string` — Return the type of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L573)
+- `getBits(start: number, end: number): bigint` — Return the bits in the given range as a BigInt [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L588)
+- `getBitsBase2(start: number, end: number): string` — Return the bits in the given range as a base-2 string [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L596)
+- `getBitsBase16(start: number, end: number): string` — Return the bits in the given range as a base-16 string [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L604)
+- `getBitsPastSubnet(): string` — Return the bits that are set past the subnet mask length [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L620)
+- `reverseForm(options?: ReverseFormOptions): string` — Return the reversed ip6.arpa form of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L630)
+- `correctForm(): string` — Returns the address in correct form, per [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952): leading zeros stripped, the longest run of zero groups collapsed to `::`, and hex digits lowercased (e.g. `2001:db8::1`). This is the recommended form for display. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L665)
+- `binaryZeroPad(): string` — Return a zero-padded base-2 string representation of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L727)
+- `parse4in6(address: string): string` — Parses a v4-in-v6 string (e.g. `::ffff:192.168.0.1`) by extracting the trailing IPv4 address into `this.address4` / `this.parsedAddress4` and returning the address with the v4 portion converted to two v6 groups. Used internally by `parse()`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L741)
+- `parse(address: string): string[]` — Parses an IPv6 address string into its 8 hexadecimal groups (expanding any `::` elision and any trailing v4-in-v6 portion) and stores the result on `this.parsedAddress`. Called automatically by the constructor; you typically don't need to call it directly. Throws `AddressError` if the input is malformed. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L798)
+- `canonicalForm(): string` — Returns the canonical (fully expanded) form of the address: all 8 groups, each padded to 4 hex digits, with no `::` collapsing (e.g. `2001:0db8:0000:0000:0000:0000:0000:0001`). Useful for sorting and byte-exact comparison. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L878)
+- `decimal(): string` — Return the decimal form of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L886)
+- `bigInt(): bigint` — Return the address as a BigInt [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L894)
+- `to4(): Address4` — Return the last two groups of this address as an IPv4 address string. If this address carries a CIDR prefix that covers the trailing 32 bits (i.e. `subnetMask >= 96`), the resulting `Address4` inherits the corresponding v4 prefix (`subnetMask - 96`); otherwise it defaults to `/32`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L909)
+- `to4in6(): string` — Return the v4-in-v6 form of the address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L933)
+- `inspectTeredo(): TeredoProperties` — Decodes the Teredo tunneling fields embedded in this address. Returns the Teredo prefix, server IPv4, client IPv4, raw flag bits, cone-NAT flag, UDP port, and Microsoft-format flag breakdown (reserved, universal/local, group/individual, nonce). Only meaningful for addresses in `2001::/32`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L954)
+- `inspect6to4(): SixToFourProperties` — Decodes the 6to4 tunneling fields embedded in this address. Returns the 6to4 prefix and the embedded IPv4 gateway address. Only meaningful for addresses in `2002::/16`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1017)
+- `to6to4(): Address6 | null` — Return a v6 6to4 address from a v6 v4inv6 address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1037)
+- `toAddress4Nat64(prefix: string): Address4 | null` — Extract the embedded IPv4 address from a NAT64 IPv6 address using the encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052). The default prefix is the well-known prefix `64:ff9b::/96`. Returns `null` if this address is not contained within the given prefix. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1106)
+- `toByteArray(): number[]` — Return a byte array. To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toByteArray())`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1141)
+- `toUnsignedByteArray(): number[]` — Return an unsigned byte array. To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toUnsignedByteArray())`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1160)
+- `isCanonical(): boolean` — Returns true if the address is in the canonical form, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1236)
+- `isLinkLocal(): boolean` — Returns true if the address is a link local address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1244)
+- `isMulticast(): boolean` — Returns true if the address is a multicast address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1265)
+- `is4(): boolean` — Returns true if the address was written in v4-in-v6 dotted-quad notation (e.g. `::ffff:127.0.0.1`), false otherwise. This is a notation-level flag and does not reflect whether the address bits lie in the IPv4-mapped (`::ffff:0:0/96`) subnet — for that, see isMapped4. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1282)
+- `isMapped4(): boolean` — Returns true if the address is an IPv4-mapped IPv6 address in `::ffff:0:0/96` ([RFC 4291 §2.5.5.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2)), false otherwise. Unlike is4, this checks the underlying address bits rather than the textual notation, so `::ffff:127.0.0.1` and `::ffff:7f00:1` both return true. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1294)
+- `embeddedIPv4(): Address4 | null` — If this address embeds a routable IPv4 address — i.e. it is IPv4-mapped (`::ffff:0:0/96`) or sits in the NAT64 well-known prefix (`64:ff9b::/96`, [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052)) — return that embedded address as an Address4; otherwise return null. The special-property checks (`isLoopback`, `isLinkLocal`, `isMulticast`, `isUnspecified`, `isPrivate`, `isCGNAT`, `isBroadcast`) call this first and delegate to the embedded Address4 when present, so a literal such as `::ffff:127.0.0.1` is classified by what it actually reaches (loopback) rather than by its IPv6 wrapper (which `getType()` reports as IPv4-mapped). This matters wherever the checks back a trust-boundary decision (e.g. an SSRF allow/deny filter): without normalization, `::ffff:10.0.0.1`, `::ffff:169.254.169.254`, `64:ff9b::7f00:1`, etc. would all read as non-internal. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1315)
+- `isTeredo(): boolean` — Returns true if the address is a Teredo address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1327)
+- `is6to4(): boolean` — Returns true if the address is a 6to4 address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1335)
+- `isLoopback(): boolean` — Returns true if the address is a loopback address, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1343)
+- `isULA(): boolean` — Returns true if the address is a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)). ULAs are the IPv6 equivalent of IPv4 [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private addresses. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1356)
+- `isPrivate(): boolean` — Returns true if the address is private, i.e. a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)) or an IPv4-mapped / NAT64 address whose embedded IPv4 address is in one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private ranges (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to Address4.isPrivate; use it instead of isULA when you need to catch mapped RFC 1918 addresses as well as native ULAs. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1370)
+- `isCGNAT(): boolean` — Returns true if the address is an IPv4-mapped / NAT64 address whose embedded IPv4 address is in the carrier-grade NAT range `100.64.0.0/10` ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)), false otherwise. There is no native IPv6 CGNAT range, so this only ever returns true for an embedded IPv4 address (e.g. `::ffff:100.64.0.1`). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1387)
+- `isBroadcast(): boolean` — Returns true if the address is an IPv4-mapped / NAT64 address whose embedded IPv4 address is the limited broadcast address `255.255.255.255` ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)), false otherwise. There is no IPv6 broadcast, so this only ever returns true for an embedded IPv4 address (e.g. `::ffff:255.255.255.255`). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1404)
+- `isUnspecified(): boolean` — Returns true if the address is the unspecified address `::`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1417)
+- `isDocumentation(): boolean` — Returns true if the address is in the documentation prefix `2001:db8::/32` ([RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849)). [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1430)
+- `href(optionalPort?: string | number): string` — Returns the address as an HTTP URL with the host bracketed, e.g. `http://[2001:db8::1]/`. If `optionalPort` is provided it is appended, e.g. `http://[2001:db8::1]:8080/`. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1441)
+- `link(options?: { className?: string; prefix?: string; v4?: boolean }): string` — Returns an HTML `` element whose `href` encodes the address in a URL hash fragment (default prefix `/#address=`). Useful for linking between pages of an address-inspector UI. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1459)
+- `group(): string` — Groups an address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1499)
+- `regularExpressionString(this: Address6, substringSearch: boolean): string` — Generate a regular expression string that can be used to find or validate all variations of this address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1551)
+- `regularExpression(this: Address6, substringSearch: boolean): RegExp` — Generate a regular expression that can be used to find or validate all variations of this address. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1605)
**Properties**
@@ -255,9 +255,9 @@ A few terms used throughout the API can be confusing if you haven't worked deepl
- `subnetMask: number` — [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L108)
- `v4: boolean` — [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L109)
- `zone: string` — [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L110)
-- `isInSubnet: (this: Address4 | Address6, address: Address4 | Address6) => boolean` — Returns true if the given address is in the subnet of the current address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1213)
-- `isHostInSubnet: (this: Address4 | Address6, address: Address4 | Address6) => boolean` — Returns true if this address's host bits fall inside the given subnet, ignoring this address's own subnet mask. See common.isHostInSubnet. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1221)
-- `isCorrect: (this: Address4 | Address6) => boolean` — Returns true if the address is correct, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1227)
+- `isInSubnet: (this: Address4 | Address6, address: Address4 | Address6) => boolean` — Returns true if the given address is in the subnet of the current address [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1216)
+- `isHostInSubnet: (this: Address4 | Address6, address: Address4 | Address6) => boolean` — Returns true if this address's host bits fall inside the given subnet, ignoring this address's own subnet mask. See common.isHostInSubnet. [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1224)
+- `isCorrect: (this: Address4 | Address6) => boolean` — Returns true if the address is correct, false otherwise [src](https://github.com/beaugunderson/ip-address/blob/master/src/ipv6.ts#L1230)
diff --git a/src/ipv6.ts b/src/ipv6.ts
index a2311a8..8e91182 100644
--- a/src/ipv6.ts
+++ b/src/ipv6.ts
@@ -214,33 +214,28 @@ export class Address6 {
let host: string;
let port: string | number | null = null;
let result: RegExpExecArray | null;
+ let error: string;
// Remove the protocol prefix, if any
const stripped = url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '');
// If we have brackets parse them and find a port
if (stripped.indexOf('[') !== -1 && stripped.indexOf(']:') !== -1) {
+ error = 'failed to parse address with port';
result = constants6.RE_URL_WITH_PORT.exec(stripped);
if (result === null) {
- return {
- error: 'failed to parse address with port',
- address: null,
- port: null,
- };
+ return { error, address: null, port: null };
}
host = result[1];
port = result[2];
} else {
+ error = 'failed to parse address from URL';
result = constants6.RE_URL.exec(stripped);
if (result === null) {
- return {
- error: 'failed to parse address from URL',
- address: null,
- port: null,
- };
+ return { error, address: null, port: null };
}
host = result[1] ?? result[2];
@@ -259,10 +254,18 @@ export class Address6 {
port = null;
}
- return {
- address: new Address6(host),
- port,
- };
+ // The URL character class is a superset of valid IPv6, so a host the
+ // regex accepted (an IPv4 literal, bare punctuation, too many groups)
+ // can still be rejected by the parser
+ let address: Address6;
+
+ try {
+ address = new Address6(host);
+ } catch {
+ return { error, address: null, port: null };
+ }
+
+ return { address, port };
}
/**
diff --git a/test/functionality-v6-test.ts b/test/functionality-v6-test.ts
index 2cc03a9..90388b0 100644
--- a/test/functionality-v6-test.ts
+++ b/test/functionality-v6-test.ts
@@ -690,6 +690,75 @@ describe('v6', () => {
expect(obj.address?.address).to.equal('::ffff:192.168.1.1');
expect(obj.port).to.equal(8080);
});
+
+ it('should fail gracefully for an IPv4 host', () => {
+ const obj = Address6.fromURL('http://127.0.0.1/admin');
+
+ expect(obj.error).to.equal('failed to parse address from URL');
+ expect(obj.address).to.equal(null);
+ expect(obj.port).to.equal(null);
+ });
+
+ it('should fail gracefully for a bracketed IPv4 host with a port', () => {
+ const obj = Address6.fromURL('http://[169.254.169.254]:8080/latest');
+
+ expect(obj.error).to.equal('failed to parse address with port');
+ expect(obj.address).to.equal(null);
+ expect(obj.port).to.equal(null);
+ });
+
+ it('should fail gracefully for a bare IPv4 address', () => {
+ const obj = Address6.fromURL('0.0.0.0');
+
+ expect(obj.error).to.equal('failed to parse address from URL');
+ expect(obj.address).to.equal(null);
+ expect(obj.port).to.equal(null);
+ });
+
+ it('should fail gracefully for too many colons', () => {
+ const obj = Address6.fromURL('http://[:::]/foo');
+
+ expect(obj.error).to.equal('failed to parse address from URL');
+ expect(obj.address).to.equal(null);
+ expect(obj.port).to.equal(null);
+ });
+
+ it('should fail gracefully for too many groups', () => {
+ const obj = Address6.fromURL('http://[1:2:3:4:5:6:7:8:9]:80/foo');
+
+ expect(obj.error).to.equal('failed to parse address with port');
+ expect(obj.address).to.equal(null);
+ expect(obj.port).to.equal(null);
+ });
+
+ it('should fail gracefully for out of range v4-in-v6 octets', () => {
+ const obj = Address6.fromURL('http://[::ffff:999.1.1.1]/foo');
+
+ expect(obj.error).to.equal('failed to parse address from URL');
+ expect(obj.address).to.equal(null);
+ expect(obj.port).to.equal(null);
+ });
+
+ it('should never throw for a host matching the URL character class', () => {
+ const hosts = [
+ '1.2.3.4',
+ '255.255.255.255',
+ '[127.0.0.1]:65535',
+ '.',
+ '..',
+ ':',
+ '::::',
+ 'ffff',
+ 'abcdef',
+ '1:2',
+ '[::ffff:1.2.3.4.5]:80',
+ ];
+
+ hosts.forEach((host) => {
+ expect(() => Address6.fromURL(`http://${host}/`), host).to.not.throw();
+ expect(() => Address6.fromURL(host), host).to.not.throw();
+ });
+ });
});
describe('regularExpressionString', () => {