@nullix/zod-mongoose-studio 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2 +0 -0
  3. package/.output/public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2 +0 -0
  4. package/.output/public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2 +0 -0
  5. package/.output/public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2 +0 -0
  6. package/.output/public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2 +0 -0
  7. package/.output/public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2 +0 -0
  8. package/.output/public/_nuxt/{qQyvCkbV.js → 5Tu2WUJz.js} +1 -1
  9. package/.output/public/_nuxt/B0N6xv5E.js +1 -0
  10. package/.output/public/_nuxt/{DjPT-NCE.js → B0cUlOSM.js} +1 -1
  11. package/.output/public/_nuxt/B9paGgN0.js +33 -0
  12. package/.output/public/_nuxt/{IxYCG8hB.js → BCIlnd0g.js} +1 -1
  13. package/.output/public/_nuxt/{ikxafTHA.js → BNDnV2op.js} +180 -184
  14. package/.output/public/_nuxt/{Ce-T_1aD.js → BO4hqv6q.js} +1 -1
  15. package/.output/public/_nuxt/{Bd32GOUy.js → BOsJJ_ZU.js} +1 -1
  16. package/.output/public/_nuxt/{BzhSDy30.js → C2BHp-MG.js} +1 -1
  17. package/.output/public/_nuxt/{Cs8GXJCB.js → CKULsFUz.js} +1 -1
  18. package/.output/public/_nuxt/CMH9iX_E.js +6 -0
  19. package/.output/public/_nuxt/{tzj4nvDE.js → CzbCpNnc.js} +2 -2
  20. package/.output/public/_nuxt/{CC3n3Aga.js → D87QMS6j.js} +1 -1
  21. package/.output/public/_nuxt/DA0pQuyP.js +27 -0
  22. package/.output/public/_nuxt/{B7P0wQ35.js → DcJ7oPvZ.js} +1 -1
  23. package/.output/public/_nuxt/{Ick_23I4.js → Dce9SnGa.js} +1 -1
  24. package/.output/public/_nuxt/{CAd-qHfI.js → DpKrXpkK.js} +1 -1
  25. package/.output/public/_nuxt/DsnIpnT9.js +1 -0
  26. package/.output/public/_nuxt/{CNZorjDf.js → I3Ucam2s.js} +1 -1
  27. package/.output/public/_nuxt/builds/latest.json +1 -1
  28. package/.output/public/_nuxt/builds/meta/51f1351c-0667-4235-9ddc-16582a51ce03.json +1 -0
  29. package/.output/public/_nuxt/{entry.J_CbC0oY.css → entry.B-vs1tRH.css} +1 -1
  30. package/.output/public/_nuxt/error-404.CTSXlFYa.css +1 -0
  31. package/.output/public/_nuxt/error-500.DqEfQnVZ.css +1 -0
  32. package/.output/public/_nuxt/{nDZOJ7rN.js → o-NCh2rp.js} +2 -2
  33. package/.output/public/_nuxt/{qwxNOyg7.js → sKEJiAHO.js} +1 -1
  34. package/.output/public/_nuxt/{81vqh61j.js → tqQNlhT9.js} +1 -1
  35. package/.output/public/_nuxt/ypRHVLFO.js +1 -0
  36. package/.output/server/chunks/_/error-500.mjs.map +1 -1
  37. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  38. package/.output/server/chunks/build/{error-404-BN-7rNE4.mjs → error-404-KRkA0DPI.mjs} +10 -4
  39. package/.output/server/chunks/build/error-404-KRkA0DPI.mjs.map +1 -0
  40. package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs +8 -0
  41. package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs.map +1 -0
  42. package/.output/server/chunks/build/{error-500-DhIQ-2yt.mjs → error-500-DIIFf55p.mjs} +10 -4
  43. package/.output/server/chunks/build/error-500-DIIFf55p.mjs.map +1 -0
  44. package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs +8 -0
  45. package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs.map +1 -0
  46. package/.output/server/chunks/build/index-CB1N4gax.mjs +557 -0
  47. package/.output/server/chunks/build/index-CB1N4gax.mjs.map +1 -0
  48. package/.output/server/chunks/build/server.mjs +326 -248
  49. package/.output/server/chunks/build/server.mjs.map +1 -1
  50. package/.output/server/chunks/build/styles.mjs +4 -4
  51. package/.output/server/chunks/nitro/nitro.mjs +2302 -835
  52. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  53. package/.output/server/chunks/routes/api/editor-types.get.mjs +44 -0
  54. package/.output/server/chunks/routes/api/editor-types.get.mjs.map +1 -0
  55. package/.output/server/chunks/routes/api/parse.post.mjs +26 -1316
  56. package/.output/server/chunks/routes/api/parse.post.mjs.map +1 -1
  57. package/.output/server/chunks/routes/api/resolve.get.mjs +6 -0
  58. package/.output/server/chunks/routes/api/resolve.get.mjs.map +1 -1
  59. package/.output/server/chunks/routes/api/studio/run.post.mjs +35 -0
  60. package/.output/server/chunks/routes/api/studio/run.post.mjs.map +1 -0
  61. package/.output/server/chunks/routes/renderer.mjs +1 -1
  62. package/.output/server/chunks/routes/renderer.mjs.map +1 -1
  63. package/.output/server/index.mjs +7 -1
  64. package/.output/server/index.mjs.map +1 -1
  65. package/.output/server/node_modules/acorn/dist/acorn.mjs +6266 -0
  66. package/.output/server/node_modules/acorn/package.json +50 -0
  67. package/.output/server/node_modules/ip-address/dist/common.js +112 -4
  68. package/.output/server/node_modules/ip-address/dist/ipv4.js +322 -87
  69. package/.output/server/node_modules/ip-address/dist/ipv6.js +654 -199
  70. package/.output/server/node_modules/ip-address/dist/v4/constants.js +45 -2
  71. package/.output/server/node_modules/ip-address/dist/v6/constants.js +55 -3
  72. package/.output/server/node_modules/ip-address/dist/v6/helpers.js +12 -3
  73. package/.output/server/node_modules/ip-address/package.json +49 -37
  74. package/.output/server/node_modules/ufo/dist/index.mjs +2 -2
  75. package/.output/server/node_modules/ufo/package.json +9 -9
  76. package/.output/server/package.json +4 -3
  77. package/README.md +62 -19
  78. package/app/assets/css/main.css +1 -0
  79. package/app/assets/css/studio.css +131 -0
  80. package/app/components/StudioConversion.vue +41 -0
  81. package/app/components/StudioDataPanel.vue +21 -0
  82. package/app/components/StudioEditor.vue +78 -102
  83. package/app/components/StudioExpandButton.vue +11 -0
  84. package/app/components/StudioOutput.vue +7 -1
  85. package/app/components/StudioSourcePanel.vue +16 -0
  86. package/app/components/StudioValidation.vue +47 -0
  87. package/app/components/ZodMongooseStudio.vue +216 -198
  88. package/app/composables/useStudioRunner.ts +142 -0
  89. package/app/pages/index.vue +1 -1
  90. package/app/plugins/monaco.client.ts +2 -0
  91. package/bin/index.mjs +5 -0
  92. package/nuxt.config.ts +4 -3
  93. package/package.json +4 -2
  94. package/server/api/editor-types.get.ts +28 -0
  95. package/server/api/parse.post.ts +17 -175
  96. package/server/api/studio/run.post.ts +17 -0
  97. package/server/middleware/security.ts +29 -7
  98. package/server/utils/example-data.ts +80 -0
  99. package/server/utils/parse-request.ts +28 -0
  100. package/server/utils/runner-client.ts +40 -0
  101. package/server/utils/studio-execution.ts +176 -0
  102. package/.output/public/_nuxt/BD40YYqZ.js +0 -33
  103. package/.output/public/_nuxt/BlQmLjV4.js +0 -1
  104. package/.output/public/_nuxt/CJ4Cv9fY.js +0 -61
  105. package/.output/public/_nuxt/builds/meta/34871c14-b0f4-4898-9ff0-debaff743355.json +0 -1
  106. package/.output/public/_nuxt/error-404.64pZEiDn.css +0 -1
  107. package/.output/public/_nuxt/error-500.DHOpI0Ir.css +0 -1
  108. package/.output/public/_nuxt/rH_O4qXT.js +0 -1
  109. package/.output/server/chunks/build/error-404-BN-7rNE4.mjs.map +0 -1
  110. package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs +0 -8
  111. package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs.map +0 -1
  112. package/.output/server/chunks/build/error-500-DhIQ-2yt.mjs.map +0 -1
  113. package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs +0 -8
  114. package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs.map +0 -1
  115. package/.output/server/chunks/build/index-B4T6zHp0.mjs +0 -1904
  116. package/.output/server/chunks/build/index-B4T6zHp0.mjs.map +0 -1
  117. package/.output/server/node_modules/zod/index.js +0 -4
  118. /package/.output/public/_nuxt/{editor.CdBKkuhR.css → monaco.CdBKkuhR.css} +0 -0
@@ -34,6 +34,7 @@ const ipv4_1 = require("./ipv4");
34
34
  const regular_expressions_1 = require("./v6/regular-expressions");
35
35
  const address_error_1 = require("./address-error");
36
36
  const common_1 = require("./common");
37
+ const isCorrect6 = common.isCorrect(constants6.BITS);
37
38
  function assert(condition) {
38
39
  if (!condition) {
39
40
  throw new Error('Assertion failed.');
@@ -72,12 +73,10 @@ function paddedHex(octet) {
72
73
  return parseInt(octet, 16).toString(16).padStart(4, '0');
73
74
  }
74
75
  function unsignByte(b) {
75
- // eslint-disable-next-line no-bitwise
76
76
  return b & 0xff;
77
77
  }
78
78
  /**
79
79
  * Represents an IPv6 address
80
- * @class Address6
81
80
  * @param {string} address - An IPv6 address string
82
81
  * @param {number} [groups=8] - How many octets to parse
83
82
  * @example
@@ -93,19 +92,28 @@ class Address6 {
93
92
  this.zone = '';
94
93
  // #region Attributes
95
94
  /**
96
- * Returns true if the given address is in the subnet of the current address
97
- * @memberof Address6
98
- * @instance
95
+ * Returns true if the given address is in the subnet of the current address.
96
+ * An `Address4` is never in the subnet of an `Address6`; convert with
97
+ * `Address6.fromAddress4()` or `to4()` to compare across families.
99
98
  * @returns {boolean}
100
99
  */
101
100
  this.isInSubnet = common.isInSubnet;
101
+ /**
102
+ * Returns true if this address's host bits fall inside the given subnet,
103
+ * ignoring this address's own subnet mask. Prefer this over `isInSubnet`
104
+ * when classifying a single address, so the answer doesn't change with the
105
+ * CIDR suffix the caller happened to write — notably when the address came
106
+ * from untrusted input and the result backs a trust-boundary decision.
107
+ * An `Address4` is never in the subnet of an `Address6`; convert with
108
+ * `Address6.fromAddress4()` or `to4()` to compare across families.
109
+ * @returns {boolean}
110
+ */
111
+ this.isHostInSubnet = common.isHostInSubnet;
102
112
  /**
103
113
  * Returns true if the address is correct, false otherwise
104
- * @memberof Address6
105
- * @instance
106
114
  * @returns {boolean}
107
115
  */
108
- this.isCorrect = common.isCorrect(constants6.BITS);
116
+ this.isCorrect = isCorrect6;
109
117
  if (optionalGroups === undefined) {
110
118
  this.groups = constants6.GROUPS;
111
119
  }
@@ -125,7 +133,10 @@ class Address6 {
125
133
  }
126
134
  address = address.replace(constants6.RE_SUBNET_STRING, '');
127
135
  }
128
- else if (/\//.test(address)) {
136
+ // RE_SUBNET_STRING anchors on the end of the address, so it strips only
137
+ // the trailing suffix. A second one left behind (`::/0/1`) is malformed
138
+ // and must be rejected rather than parsed as an address group.
139
+ if (/\//.test(address)) {
129
140
  throw new address_error_1.AddressError('Invalid subnet mask.');
130
141
  }
131
142
  const zone = constants6.RE_ZONE_STRING.exec(address);
@@ -133,23 +144,39 @@ class Address6 {
133
144
  this.zone = zone[0];
134
145
  address = address.replace(constants6.RE_ZONE_STRING, '');
135
146
  }
147
+ // The longest well-formed address is all but the last two groups written
148
+ // as four hex digits with their colons, then a 15-character dotted quad:
149
+ // 5 * (groups - 2) + 15, which is 45 for eight groups, the same line
150
+ // CPython's ipaddress module draws. Rejecting longer input here keeps the
151
+ // parse diagnostics, which wrap every offending character in a span,
152
+ // proportional to an address rather than to whatever was passed in.
153
+ const longest = this.groups * 5 + 5;
154
+ if (address.length > longest) {
155
+ throw new address_error_1.AddressError(`IPv6 addresses are at most ${longest} characters.`);
156
+ }
136
157
  this.addressMinusSuffix = address;
137
158
  this.parsedAddress = this.parse(this.addressMinusSuffix);
138
159
  }
160
+ /**
161
+ * Returns true if the given string is a valid IPv6 address (with optional
162
+ * CIDR subnet and zone identifier), false otherwise. Host bits in the
163
+ * subnet portion are allowed (e.g. `2001:db8::1/32` is valid); for strict
164
+ * network-address validation compare `correctForm()` to
165
+ * `startAddress().correctForm()`, or use `networkForm()`.
166
+ */
139
167
  static isValid(address) {
140
168
  try {
141
169
  // eslint-disable-next-line no-new
142
170
  new Address6(address);
143
171
  return true;
144
172
  }
145
- catch (e) {
173
+ catch {
146
174
  return false;
147
175
  }
148
176
  }
149
177
  /**
150
- * Convert a BigInt to a v6 address object
151
- * @memberof Address6
152
- * @static
178
+ * Convert a BigInt to a v6 address object. The value must be in the
179
+ * range `[0, 2**128 - 1]`; otherwise `AddressError` is thrown.
153
180
  * @param {bigint} bigInt - a BigInt to convert
154
181
  * @returns {Address6}
155
182
  * @example
@@ -158,65 +185,57 @@ class Address6 {
158
185
  * address.correctForm(); // '::e8:d4a5:1000'
159
186
  */
160
187
  static fromBigInt(bigInt) {
188
+ if (bigInt < BigInt(0) || bigInt > (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1)) {
189
+ throw new address_error_1.AddressError('IPv6 BigInt must be in the range 0 to 2**128 - 1');
190
+ }
161
191
  const hex = bigInt.toString(16).padStart(32, '0');
162
192
  const groups = [];
163
- let i;
164
- for (i = 0; i < constants6.GROUPS; i++) {
193
+ for (let i = 0; i < constants6.GROUPS; i++) {
165
194
  groups.push(hex.slice(i * 4, (i + 1) * 4));
166
195
  }
167
196
  return new Address6(groups.join(':'));
168
197
  }
169
198
  /**
170
- * Convert a URL (with optional port number) to an address object
171
- * @memberof Address6
172
- * @static
173
- * @param {string} url - a URL with optional port number
199
+ * Parse a URL (with optional bracketed host and port) into an address and
200
+ * port. Returns either `{ address, port }` on success or
201
+ * `{ error, address: null, port: null }` if the URL could not be parsed.
202
+ * Ports are returned as numbers (or `null` if absent or out of range).
174
203
  * @example
175
204
  * var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
176
205
  * addressAndPort.address.correctForm(); // 'ffff::'
177
206
  * addressAndPort.port; // 8080
178
207
  */
179
208
  static fromURL(url) {
209
+ var _a;
180
210
  let host;
181
211
  let port = null;
182
212
  let result;
213
+ let error;
214
+ // Remove the protocol prefix, if any
215
+ const stripped = url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '');
183
216
  // If we have brackets parse them and find a port
184
- if (url.indexOf('[') !== -1 && url.indexOf(']:') !== -1) {
185
- result = constants6.RE_URL_WITH_PORT.exec(url);
217
+ if (stripped.indexOf('[') !== -1 && stripped.indexOf(']:') !== -1) {
218
+ error = 'failed to parse address with port';
219
+ result = constants6.RE_URL_WITH_PORT.exec(stripped);
186
220
  if (result === null) {
187
- return {
188
- error: 'failed to parse address with port',
189
- address: null,
190
- port: null,
191
- };
221
+ return { error, address: null, port: null };
192
222
  }
193
223
  host = result[1];
194
224
  port = result[2];
195
- // If there's a URL extract the address
196
225
  }
197
- else if (url.indexOf('/') !== -1) {
198
- // Remove the protocol prefix
199
- url = url.replace(/^[a-z0-9]+:\/\//, '');
200
- // Parse the address
201
- result = constants6.RE_URL.exec(url);
226
+ else {
227
+ error = 'failed to parse address from URL';
228
+ result = constants6.RE_URL.exec(stripped);
202
229
  if (result === null) {
203
- return {
204
- error: 'failed to parse address from URL',
205
- address: null,
206
- port: null,
207
- };
230
+ return { error, address: null, port: null };
208
231
  }
209
- host = result[1];
210
- // Otherwise just assign the URL to the host and let the library parse it
211
- }
212
- else {
213
- host = url;
232
+ host = (_a = result[1]) !== null && _a !== void 0 ? _a : result[2];
214
233
  }
215
234
  // If there's a port convert it to an integer
216
235
  if (port) {
217
236
  port = parseInt(port, 10);
218
- // squelch out of range ports
219
- if (port < 0 || port > 65536) {
237
+ // squelch out of range ports (valid ports are 0-65535)
238
+ if (port < 0 || port > 65535) {
220
239
  port = null;
221
240
  }
222
241
  }
@@ -224,15 +243,103 @@ class Address6 {
224
243
  // Standardize `undefined` to `null`
225
244
  port = null;
226
245
  }
227
- return {
228
- address: new Address6(host),
229
- port,
230
- };
246
+ // The URL character class is a superset of valid IPv6, so a host the
247
+ // regex accepted (an IPv4 literal, bare punctuation, too many groups)
248
+ // can still be rejected by the parser
249
+ let address;
250
+ try {
251
+ address = new Address6(host);
252
+ }
253
+ catch {
254
+ return { error, address: null, port: null };
255
+ }
256
+ return { address, port };
257
+ }
258
+ /**
259
+ * Construct an `Address6` from an address and a hex subnet mask given as
260
+ * separate strings (e.g. as returned by Node's `os.networkInterfaces()`).
261
+ * Throws `AddressError` if the mask is non-contiguous (e.g.
262
+ * `ffff::ffff`).
263
+ * @example
264
+ * var address = Address6.fromAddressAndMask('fe80::1', 'ffff:ffff:ffff:ffff::');
265
+ * address.subnetMask; // 64
266
+ */
267
+ static fromAddressAndMask(address, mask) {
268
+ const bits = common.prefixLengthFromMask(new Address6(mask).bigInt(), constants6.BITS);
269
+ return new Address6(`${address}/${bits}`);
270
+ }
271
+ /**
272
+ * Construct an `Address6` from an address and a Cisco-style wildcard mask
273
+ * given as separate strings (e.g. `::ffff:ffff:ffff:ffff` for a `/64`).
274
+ * The wildcard mask is the bitwise inverse of the subnet mask. Throws
275
+ * `AddressError` if the mask is non-contiguous.
276
+ * @example
277
+ * var address = Address6.fromAddressAndWildcardMask('fe80::1', '::ffff:ffff:ffff:ffff');
278
+ * address.subnetMask; // 64
279
+ */
280
+ static fromAddressAndWildcardMask(address, wildcardMask) {
281
+ const wildcard = new Address6(wildcardMask).bigInt();
282
+ const allOnes = (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1);
283
+ const mask = wildcard ^ allOnes;
284
+ const bits = common.prefixLengthFromMask(mask, constants6.BITS);
285
+ return new Address6(`${address}/${bits}`);
286
+ }
287
+ /**
288
+ * Construct an `Address6` from a wildcard pattern with trailing `*`
289
+ * groups. The number of trailing wildcards determines the prefix
290
+ * length: each `*` represents 16 bits. `::` is expanded to zero groups
291
+ * (not wildcards) before evaluating trailing wildcards.
292
+ *
293
+ * Only trailing whole-group wildcards are supported. Partial-group
294
+ * wildcards (e.g. `2001:db8::0*`) and interior wildcards (e.g.
295
+ * `*::1`) throw `AddressError`.
296
+ * @example
297
+ * Address6.fromWildcard('2001:db8:*:*:*:*:*:*').subnet; // '/32'
298
+ * Address6.fromWildcard('2001:db8::*').subnet; // '/112'
299
+ * Address6.fromWildcard('*:*:*:*:*:*:*:*').subnet; // '/0'
300
+ */
301
+ static fromWildcard(input) {
302
+ if (input.includes('%') || input.includes('/')) {
303
+ throw new address_error_1.AddressError('Wildcard pattern must not include a zone or CIDR suffix');
304
+ }
305
+ const halves = input.split('::');
306
+ if (halves.length > 2) {
307
+ throw new address_error_1.AddressError("Wildcard pattern cannot contain more than one '::'");
308
+ }
309
+ let groups;
310
+ if (halves.length === 2) {
311
+ const left = halves[0] === '' ? [] : halves[0].split(':');
312
+ const right = halves[1] === '' ? [] : halves[1].split(':');
313
+ const remaining = constants6.GROUPS - left.length - right.length;
314
+ if (remaining < 1) {
315
+ throw new address_error_1.AddressError("Wildcard pattern with '::' has too many groups");
316
+ }
317
+ groups = [...left, ...new Array(remaining).fill('0'), ...right];
318
+ }
319
+ else {
320
+ groups = input.split(':');
321
+ }
322
+ if (groups.length !== constants6.GROUPS) {
323
+ throw new address_error_1.AddressError('Wildcard pattern must have 8 groups');
324
+ }
325
+ let firstWildcard = -1;
326
+ for (let i = 0; i < groups.length; i++) {
327
+ if (groups[i] === '*') {
328
+ if (firstWildcard === -1) {
329
+ firstWildcard = i;
330
+ }
331
+ }
332
+ else if (firstWildcard !== -1) {
333
+ throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing groups (e.g. `2001:db8:*:*:*:*:*:*`)');
334
+ }
335
+ }
336
+ const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard;
337
+ const replaced = groups.map((g) => (g === '*' ? '0' : g));
338
+ const subnetBits = constants6.BITS - trailing * 16;
339
+ return new Address6(`${replaced.join(':')}/${subnetBits}`);
231
340
  }
232
341
  /**
233
342
  * Create an IPv6-mapped address given an IPv4 address
234
- * @memberof Address6
235
- * @static
236
343
  * @param {string} address - An IPv4 address string
237
344
  * @returns {Address6}
238
345
  * @example
@@ -246,35 +353,35 @@ class Address6 {
246
353
  return new Address6(`::ffff:${address4.correctForm()}/${mask6}`);
247
354
  }
248
355
  /**
249
- * Return an address from ip6.arpa form
250
- * @memberof Address6
251
- * @static
356
+ * Return an address from ip6.arpa form. A full 32-nibble name gives a /128
357
+ * address; a shorter name, as used for a delegated reverse zone, gives the
358
+ * network it covers, with a subnet mask of four bits per nibble, so
359
+ * `fromArpa(x.reverseForm())` round-trips {@link reverseForm} for any prefix.
252
360
  * @param {string} arpaFormAddress - an 'ip6.arpa' form address
253
361
  * @returns {Adress6}
254
362
  * @example
255
363
  * var address = Address6.fromArpa(e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.)
256
364
  * address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe'
365
+ * Address6.fromArpa('8.b.d.0.1.0.0.2.ip6.arpa.').networkForm(); // '2001:db8::/32'
257
366
  */
258
367
  static fromArpa(arpaFormAddress) {
259
- // remove ending ".ip6.arpa." or just "."
260
- let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, '');
261
- const semicolonAmount = 7;
262
- // correct ip6.arpa form with ending removed will be 63 characters
263
- if (address.length !== 63) {
368
+ // remove an ending ".ip6.arpa", in any case and with or without the root
369
+ // dot
370
+ const nibbles = arpaFormAddress.replace(/(\.ip6\.arpa)?\.?$/i, '');
371
+ if (!/^[0-9a-f](\.[0-9a-f]){0,31}$/i.test(nibbles)) {
264
372
  throw new address_error_1.AddressError("Invalid 'ip6.arpa' form.");
265
373
  }
266
- const parts = address.split('.').reverse();
267
- for (let i = semicolonAmount; i > 0; i--) {
268
- const insertIndex = i * 4;
269
- parts.splice(insertIndex, 0, ':');
374
+ const reversed = nibbles.split('.').reverse();
375
+ const subnetMask = reversed.length * 4;
376
+ const hex = reversed.join('').padEnd(32, '0');
377
+ const groups = [];
378
+ for (let i = 0; i < constants6.GROUPS; i++) {
379
+ groups.push(hex.slice(i * 4, (i + 1) * 4));
270
380
  }
271
- address = parts.join('');
272
- return new Address6(address);
381
+ return new Address6(`${groups.join(':')}/${subnetMask}`);
273
382
  }
274
383
  /**
275
384
  * Return the Microsoft UNC transcription of the address
276
- * @memberof Address6
277
- * @instance
278
385
  * @returns {String} the Microsoft UNC transcription of the address
279
386
  */
280
387
  microsoftTranscription() {
@@ -282,8 +389,6 @@ class Address6 {
282
389
  }
283
390
  /**
284
391
  * Return the first n bits of the address, defaulting to the subnet mask
285
- * @memberof Address6
286
- * @instance
287
392
  * @param {number} [mask=subnet] - the number of bits to mask
288
393
  * @returns {String} the first n bits of the address as a string
289
394
  */
@@ -292,8 +397,6 @@ class Address6 {
292
397
  }
293
398
  /**
294
399
  * Return the number of possible subnets of a given size in the address
295
- * @memberof Address6
296
- * @instance
297
400
  * @param {number} [subnetSize=128] - the subnet size
298
401
  * @returns {String}
299
402
  */
@@ -309,8 +412,6 @@ class Address6 {
309
412
  }
310
413
  /**
311
414
  * Helper function getting start address.
312
- * @memberof Address6
313
- * @instance
314
415
  * @returns {bigint}
315
416
  */
316
417
  _startAddress() {
@@ -319,8 +420,6 @@ class Address6 {
319
420
  /**
320
421
  * The first address in the range given by this address' subnet
321
422
  * Often referred to as the Network Address.
322
- * @memberof Address6
323
- * @instance
324
423
  * @returns {Address6}
325
424
  */
326
425
  startAddress() {
@@ -329,8 +428,6 @@ class Address6 {
329
428
  /**
330
429
  * The first host address in the range given by this address's subnet ie
331
430
  * the first address after the Network Address
332
- * @memberof Address6
333
- * @instance
334
431
  * @returns {Address6}
335
432
  */
336
433
  startAddressExclusive() {
@@ -339,28 +436,26 @@ class Address6 {
339
436
  }
340
437
  /**
341
438
  * Helper function getting end address.
342
- * @memberof Address6
343
- * @instance
344
439
  * @returns {bigint}
345
440
  */
346
441
  _endAddress() {
347
442
  return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`);
348
443
  }
349
444
  /**
350
- * The last address in the range given by this address' subnet
351
- * Often referred to as the Broadcast
352
- * @memberof Address6
353
- * @instance
445
+ * The last address in the range given by this address's subnet. IPv6 has
446
+ * no broadcast address, so this is an ordinary assignable address (in a
447
+ * 64-bit-interface-identifier subnet it falls inside the reserved
448
+ * subnet-anycast block of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526)).
354
449
  * @returns {Address6}
355
450
  */
356
451
  endAddress() {
357
452
  return Address6.fromBigInt(this._endAddress());
358
453
  }
359
454
  /**
360
- * The last host address in the range given by this address's subnet ie
361
- * the last address prior to the Broadcast Address
362
- * @memberof Address6
363
- * @instance
455
+ * The address one before {@link endAddress}. This is the IPv6 counterpart
456
+ * of the IPv4 method that skips the broadcast address; IPv6 has no broadcast,
457
+ * so it drops exactly one address and does not model the 128 reserved
458
+ * subnet-anycast identifiers of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526).
364
459
  * @returns {Address6}
365
460
  */
366
461
  endAddressExclusive() {
@@ -368,36 +463,100 @@ class Address6 {
368
463
  return Address6.fromBigInt(this._endAddress() - adjust);
369
464
  }
370
465
  /**
371
- * Return the scope of the address
372
- * @memberof Address6
373
- * @instance
466
+ * Returns the address `n` addresses after this one (or before, when `n` is
467
+ * negative), keeping this address's subnet mask. Throws `AddressError` when
468
+ * the result would fall outside the IPv6 address space or `n` is not an
469
+ * integer.
470
+ * @param {number | bigint} n
471
+ * @returns {Address6}
472
+ * @example
473
+ * new Address6('2001:db8::/64').offset(1).correctForm(); // '2001:db8::1'
474
+ */
475
+ offset(n) {
476
+ return Address6.fromBigInt(common.offsetBigInt(this.bigInt(), n, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask);
477
+ }
478
+ /**
479
+ * Returns the network that follows this address's network: the address after
480
+ * {@link endAddress}, with the same subnet mask. Throws `AddressError` when
481
+ * this network is the last one in the address space.
482
+ * @returns {Address6}
483
+ * @example
484
+ * new Address6('2001:db8::/64').nextNetwork().networkForm(); // '2001:db8:0:1::/64'
485
+ */
486
+ nextNetwork() {
487
+ return Address6.fromBigInt(common.offsetBigInt(this._endAddress(), 1, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask);
488
+ }
489
+ withSubnetMask(subnetMask) {
490
+ return new Address6(`${this.correctForm()}/${subnetMask}`);
491
+ }
492
+ /**
493
+ * The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a
494
+ * `/64`. Returns an `Address6`; call `.correctForm()` for the string.
495
+ * @returns {Address6}
496
+ */
497
+ subnetMaskAddress() {
498
+ return Address6.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants6.BITS - this.subnetMask)}`));
499
+ }
500
+ /**
501
+ * The Cisco-style wildcard mask, e.g. `::ffff:ffff:ffff:ffff` for a
502
+ * `/64`. This is the bitwise inverse of `subnetMaskAddress()`. Returns
503
+ * an `Address6`; call `.correctForm()` for the string.
504
+ * @returns {Address6}
505
+ */
506
+ wildcardMask() {
507
+ return Address6.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants6.BITS - this.subnetMask)}`));
508
+ }
509
+ /**
510
+ * The network address in CIDR string form, e.g. `2001:db8::/32` for
511
+ * `2001:db8::1/32`. For an address with no explicit subnet the prefix
512
+ * is `/128`, e.g. `networkForm()` on `2001:db8::1` returns
513
+ * `2001:db8::1/128`.
514
+ * @returns {string}
515
+ */
516
+ networkForm() {
517
+ return `${this.startAddress().correctForm()}/${this.subnetMask}`;
518
+ }
519
+ /**
520
+ * Return the scope of the address. The 4-bit scope field
521
+ * ([RFC 4291 §2.7](https://datatracker.ietf.org/doc/html/rfc4291#section-2.7))
522
+ * is only defined for multicast addresses; for unicast addresses the scope
523
+ * is derived from the address type per
524
+ * [RFC 4007 §6](https://datatracker.ietf.org/doc/html/rfc4007#section-6).
374
525
  * @returns {String}
375
526
  */
376
527
  getScope() {
377
- let scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
378
- if (this.getType() === 'Global unicast' && scope !== 'Link local') {
379
- scope = 'Global';
528
+ const type = this.getType();
529
+ if (type === 'Multicast' || type.startsWith('Multicast ')) {
530
+ const scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
531
+ return scope || 'Unknown';
532
+ }
533
+ // RFC 4291 §2.5.3: the loopback address is treated as having Link-Local
534
+ // scope. (Multicast scope 1, "Interface-Local", is a different concept
535
+ // used only for loopback transmission of multicast.)
536
+ if (type === 'Link-local unicast' || type === 'Loopback') {
537
+ return 'Link local';
538
+ }
539
+ // RFC 4007 §6: the unspecified address has no scope.
540
+ if (type === 'Unspecified') {
541
+ return 'Unknown';
380
542
  }
381
- return scope || 'Unknown';
543
+ return 'Global';
382
544
  }
383
545
  /**
384
546
  * Return the type of the address
385
- * @memberof Address6
386
- * @instance
387
547
  * @returns {String}
388
548
  */
389
549
  getType() {
390
- for (const subnet of Object.keys(constants6.TYPES)) {
391
- if (this.isInSubnet(new Address6(subnet))) {
392
- return constants6.TYPES[subnet];
550
+ for (let i = 0; i < TYPE_SUBNETS.length; i++) {
551
+ const entry = TYPE_SUBNETS[i];
552
+ if (this.isHostInSubnet(entry[0])) {
553
+ return entry[1];
393
554
  }
394
555
  }
395
556
  return 'Global unicast';
396
557
  }
397
558
  /**
398
559
  * Return the bits in the given range as a BigInt
399
- * @memberof Address6
400
- * @instance
401
560
  * @returns {bigint}
402
561
  */
403
562
  getBits(start, end) {
@@ -405,8 +564,6 @@ class Address6 {
405
564
  }
406
565
  /**
407
566
  * Return the bits in the given range as a base-2 string
408
- * @memberof Address6
409
- * @instance
410
567
  * @returns {String}
411
568
  */
412
569
  getBitsBase2(start, end) {
@@ -414,8 +571,6 @@ class Address6 {
414
571
  }
415
572
  /**
416
573
  * Return the bits in the given range as a base-16 string
417
- * @memberof Address6
418
- * @instance
419
574
  * @returns {String}
420
575
  */
421
576
  getBitsBase16(start, end) {
@@ -429,8 +584,6 @@ class Address6 {
429
584
  }
430
585
  /**
431
586
  * Return the bits that are set past the subnet mask length
432
- * @memberof Address6
433
- * @instance
434
587
  * @returns {String}
435
588
  */
436
589
  getBitsPastSubnet() {
@@ -438,10 +591,8 @@ class Address6 {
438
591
  }
439
592
  /**
440
593
  * Return the reversed ip6.arpa form of the address
441
- * @memberof Address6
442
594
  * @param {Object} options
443
595
  * @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
444
- * @instance
445
596
  * @returns {String}
446
597
  */
447
598
  reverseForm(options) {
@@ -467,10 +618,10 @@ class Address6 {
467
618
  return 'ip6.arpa.';
468
619
  }
469
620
  /**
470
- * Return the correct form of the address
471
- * @memberof Address6
472
- * @instance
473
- * @returns {String}
621
+ * Returns the address in correct form, per
622
+ * [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952): leading zeros
623
+ * stripped, the longest run of zero groups collapsed to `::`, and hex digits
624
+ * lowercased (e.g. `2001:db8::1`). This is the recommended form for display.
474
625
  */
475
626
  correctForm() {
476
627
  let i;
@@ -514,8 +665,6 @@ class Address6 {
514
665
  }
515
666
  /**
516
667
  * Return a zero-padded base-2 string representation of the address
517
- * @memberof Address6
518
- * @instance
519
668
  * @returns {String}
520
669
  * @example
521
670
  * var address = new Address6('2001:4860:4001:803::1011');
@@ -524,27 +673,58 @@ class Address6 {
524
673
  * // 0000000000000000000000000000000000000000000000000001000000010001'
525
674
  */
526
675
  binaryZeroPad() {
527
- return this.bigInt().toString(2).padStart(constants6.BITS, '0');
676
+ if (this._binaryZeroPad === undefined) {
677
+ this._binaryZeroPad = this.bigInt().toString(2).padStart(constants6.BITS, '0');
678
+ }
679
+ return this._binaryZeroPad;
528
680
  }
681
+ /**
682
+ * Parses a v4-in-v6 string (e.g. `::ffff:192.168.0.1`) by extracting the
683
+ * trailing IPv4 address into `this.address4` / `this.parsedAddress4` and
684
+ * returning the address with the v4 portion converted to two v6 groups.
685
+ * Used internally by `parse()`.
686
+ */
529
687
  // TODO: Improve the semantics of this helper function
530
688
  parse4in6(address) {
689
+ if (address.indexOf('.') === -1) {
690
+ return address;
691
+ }
531
692
  const groups = address.split(':');
532
693
  const lastGroup = groups.slice(-1)[0];
694
+ // RE_ADDRESS rejects octets with a leading zero, so a dotted-quad tail is
695
+ // matched permissively first: that way this notation still gets its own
696
+ // message with the offending octet highlighted, rather than falling
697
+ // through as an unrecognized group.
698
+ const v4Octets = lastGroup.split('.');
699
+ if (v4Octets.length === constants4.GROUPS &&
700
+ v4Octets.every((octet) => /^\d{1,3}$/.test(octet))) {
701
+ if (v4Octets.some((octet) => /^0\d/.test(octet))) {
702
+ // The prefix groups haven't been through the bad-character check
703
+ // yet, so escape them before including in the error HTML.
704
+ const highlighted = v4Octets.map(spanLeadingZeroes4).join('.');
705
+ const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':');
706
+ const separator = groups.length > 1 ? ':' : '';
707
+ throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`);
708
+ }
709
+ }
533
710
  const address4 = lastGroup.match(constants4.RE_ADDRESS);
534
711
  if (address4) {
535
712
  this.parsedAddress4 = address4[0];
536
- this.address4 = new ipv4_1.Address4(this.parsedAddress4);
537
- for (let i = 0; i < this.address4.groups; i++) {
538
- if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
539
- throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", address.replace(constants4.RE_ADDRESS, this.address4.parsedAddress.map(spanLeadingZeroes4).join('.')));
540
- }
541
- }
713
+ const v4Suffix = this.subnetMask >= 96 ? `/${this.subnetMask - 96}` : '';
714
+ this.address4 = new ipv4_1.Address4(`${this.parsedAddress4}${v4Suffix}`);
542
715
  this.v4 = true;
543
716
  groups[groups.length - 1] = this.address4.toGroup6();
544
717
  address = groups.join(':');
545
718
  }
546
719
  return address;
547
720
  }
721
+ /**
722
+ * Parses an IPv6 address string into its 8 hexadecimal groups (expanding
723
+ * any `::` elision and any trailing v4-in-v6 portion) and stores the result
724
+ * on `this.parsedAddress`. Called automatically by the constructor; you
725
+ * typically don't need to call it directly. Throws `AddressError` if the
726
+ * input is malformed.
727
+ */
548
728
  // TODO: Make private?
549
729
  parse(address) {
550
730
  address = this.parse4in6(address);
@@ -594,18 +774,16 @@ class Address6 {
594
774
  return groups;
595
775
  }
596
776
  /**
597
- * Return the canonical form of the address
598
- * @memberof Address6
599
- * @instance
600
- * @returns {String}
777
+ * Returns the canonical (fully expanded) form of the address: all 8 groups,
778
+ * each padded to 4 hex digits, with no `::` collapsing
779
+ * (e.g. `2001:0db8:0000:0000:0000:0000:0000:0001`). Useful for sorting and
780
+ * byte-exact comparison.
601
781
  */
602
782
  canonicalForm() {
603
783
  return this.parsedAddress.map(paddedHex).join(':');
604
784
  }
605
785
  /**
606
786
  * Return the decimal form of the address
607
- * @memberof Address6
608
- * @instance
609
787
  * @returns {String}
610
788
  */
611
789
  decimal() {
@@ -613,17 +791,17 @@ class Address6 {
613
791
  }
614
792
  /**
615
793
  * Return the address as a BigInt
616
- * @memberof Address6
617
- * @instance
618
794
  * @returns {bigint}
619
795
  */
620
796
  bigInt() {
621
797
  return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`);
622
798
  }
623
799
  /**
624
- * Return the last two groups of this address as an IPv4 address string
625
- * @memberof Address6
626
- * @instance
800
+ * Return the last two groups of this address as an IPv4 address string.
801
+ * If this address carries a CIDR prefix that covers the trailing 32 bits
802
+ * (i.e. `subnetMask >= 96`), the resulting `Address4` inherits the
803
+ * corresponding v4 prefix (`subnetMask - 96`); otherwise it defaults to
804
+ * `/32`.
627
805
  * @returns {Address4}
628
806
  * @example
629
807
  * var address = new Address6('2001:4860:4001::1825:bf11');
@@ -631,12 +809,21 @@ class Address6 {
631
809
  */
632
810
  to4() {
633
811
  const binary = this.binaryZeroPad().split('');
634
- return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16));
812
+ const hex = BigInt(`0b${binary.slice(96, 128).join('')}`)
813
+ .toString(16)
814
+ .padStart(8, '0');
815
+ if (this.subnetMask >= 96) {
816
+ const v4Mask = this.subnetMask - 96;
817
+ const groups = [];
818
+ for (let i = 0; i < 8; i += 2) {
819
+ groups.push(parseInt(hex.slice(i, i + 2), 16));
820
+ }
821
+ return new ipv4_1.Address4(`${groups.join('.')}/${v4Mask}`);
822
+ }
823
+ return ipv4_1.Address4.fromHex(hex);
635
824
  }
636
825
  /**
637
826
  * Return the v4-in-v6 form of the address
638
- * @memberof Address6
639
- * @instance
640
827
  * @returns {String}
641
828
  */
642
829
  to4in6() {
@@ -647,13 +834,13 @@ class Address6 {
647
834
  if (!/:$/.test(correct)) {
648
835
  infix = ':';
649
836
  }
650
- return correct + infix + address4.address;
837
+ return correct + infix + address4.correctForm();
651
838
  }
652
839
  /**
653
- * Return an object containing the Teredo properties of the address
654
- * @memberof Address6
655
- * @instance
656
- * @returns {Object}
840
+ * Decodes the Teredo tunneling fields embedded in this address. Returns the
841
+ * Teredo prefix, server IPv4, client IPv4, raw flag bits, cone-NAT flag,
842
+ * UDP port, and Microsoft-format flag breakdown (reserved, universal/local,
843
+ * group/individual, nonce). Only meaningful for addresses in `2001::/32`.
657
844
  */
658
845
  inspectTeredo() {
659
846
  /*
@@ -679,12 +866,10 @@ class Address6 {
679
866
  */
680
867
  const prefix = this.getBitsBase16(0, 32);
681
868
  const bitsForUdpPort = this.getBits(80, 96);
682
- // eslint-disable-next-line no-bitwise
683
869
  const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString();
684
870
  const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64));
685
871
  const bitsForClient4 = this.getBits(96, 128);
686
- // eslint-disable-next-line no-bitwise
687
- const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16));
872
+ const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16).padStart(8, '0'));
688
873
  const flagsBase2 = this.getBitsBase2(64, 80);
689
874
  const coneNat = (0, common_1.testBit)(flagsBase2, 15);
690
875
  const reserved = (0, common_1.testBit)(flagsBase2, 14);
@@ -707,10 +892,9 @@ class Address6 {
707
892
  };
708
893
  }
709
894
  /**
710
- * Return an object containing the 6to4 properties of the address
711
- * @memberof Address6
712
- * @instance
713
- * @returns {Object}
895
+ * Decodes the 6to4 tunneling fields embedded in this address. Returns the
896
+ * 6to4 prefix and the embedded IPv4 gateway address. Only meaningful for
897
+ * addresses in `2002::/16`.
714
898
  */
715
899
  inspect6to4() {
716
900
  /*
@@ -726,8 +910,6 @@ class Address6 {
726
910
  }
727
911
  /**
728
912
  * Return a v6 6to4 address from a v6 v4inv6 address
729
- * @memberof Address6
730
- * @instance
731
913
  * @returns {Address6}
732
914
  */
733
915
  to6to4() {
@@ -744,15 +926,88 @@ class Address6 {
744
926
  return new Address6(addr6to4);
745
927
  }
746
928
  /**
747
- * Return a byte array
748
- * @memberof Address6
749
- * @instance
929
+ * Embed an IPv4 address into a NAT64 IPv6 address using the encoding
930
+ * defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
931
+ * The default prefix is the well-known prefix `64:ff9b::/96`. The prefix
932
+ * length must be one of 32, 40, 48, 56, 64, or 96; for prefixes shorter
933
+ * than /64 the IPv4 octets are split around the reserved bits 64–71.
934
+ * @example
935
+ * Address6.fromAddress4Nat64('192.0.2.33').correctForm(); // '64:ff9b::c000:221'
936
+ * Address6.fromAddress4Nat64('192.0.2.33', '2001:db8::/32').correctForm(); // '2001:db8:c000:221::'
937
+ */
938
+ static fromAddress4Nat64(address, prefix = '64:ff9b::/96') {
939
+ const v4 = new ipv4_1.Address4(address);
940
+ const prefix6 = new Address6(prefix);
941
+ const pl = prefix6.subnetMask;
942
+ if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
943
+ throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
944
+ }
945
+ const prefixBits = prefix6.binaryZeroPad();
946
+ const v4Bits = v4.binaryZeroPad();
947
+ let bits;
948
+ if (pl === 96) {
949
+ bits = prefixBits.slice(0, 96) + v4Bits;
950
+ }
951
+ else {
952
+ const beforeU = 64 - pl;
953
+ bits = [
954
+ prefixBits.slice(0, pl),
955
+ v4Bits.slice(0, beforeU),
956
+ // Bits 64 to 71 are the reserved u octet and are always zero.
957
+ '00000000',
958
+ v4Bits.slice(beforeU),
959
+ '0'.repeat(128 - 72 - (32 - beforeU)),
960
+ ].join('');
961
+ }
962
+ const hex = BigInt(`0b${bits}`).toString(16).padStart(32, '0');
963
+ const groups = [];
964
+ for (let i = 0; i < 8; i++) {
965
+ groups.push(hex.slice(i * 4, (i + 1) * 4));
966
+ }
967
+ return new Address6(groups.join(':'));
968
+ }
969
+ /**
970
+ * Extract the embedded IPv4 address from a NAT64 IPv6 address using the
971
+ * encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
972
+ * The default prefix is the well-known prefix `64:ff9b::/96`. Returns
973
+ * `null` if this address is not contained within the given prefix.
974
+ * @example
975
+ * new Address6('64:ff9b::c000:221').toAddress4Nat64()!.correctForm(); // '192.0.2.33'
976
+ */
977
+ toAddress4Nat64(prefix = '64:ff9b::/96') {
978
+ const prefix6 = new Address6(prefix);
979
+ const pl = prefix6.subnetMask;
980
+ if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
981
+ throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
982
+ }
983
+ if (!this.isHostInSubnet(prefix6)) {
984
+ return null;
985
+ }
986
+ const bits = this.binaryZeroPad();
987
+ let v4Bits;
988
+ if (pl === 96) {
989
+ v4Bits = bits.slice(96, 128);
990
+ }
991
+ else {
992
+ const beforeU = 64 - pl;
993
+ v4Bits = bits.slice(pl, pl + beforeU) + bits.slice(72, 72 + (32 - beforeU));
994
+ }
995
+ const octets = [];
996
+ for (let i = 0; i < 4; i++) {
997
+ octets.push(parseInt(v4Bits.slice(i * 8, (i + 1) * 8), 2).toString());
998
+ }
999
+ return new ipv4_1.Address4(octets.join('.'));
1000
+ }
1001
+ /**
1002
+ * Return a byte array.
1003
+ *
1004
+ * To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toByteArray())`.
750
1005
  * @returns {Array}
751
1006
  */
752
1007
  toByteArray() {
753
- const valueWithoutPadding = this.bigInt().toString(16);
754
- const leadingPad = '0'.repeat(valueWithoutPadding.length % 2);
755
- const value = `${leadingPad}${valueWithoutPadding}`;
1008
+ const value = this.bigInt()
1009
+ .toString(16)
1010
+ .padStart(constants6.BITS / 4, '0');
756
1011
  const bytes = [];
757
1012
  for (let i = 0, length = value.length; i < length; i += 2) {
758
1013
  bytes.push(parseInt(value.substring(i, i + 2), 16));
@@ -760,30 +1015,45 @@ class Address6 {
760
1015
  return bytes;
761
1016
  }
762
1017
  /**
763
- * Return an unsigned byte array
764
- * @memberof Address6
765
- * @instance
1018
+ * Return an unsigned byte array.
1019
+ *
1020
+ * To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toUnsignedByteArray())`.
766
1021
  * @returns {Array}
767
1022
  */
768
1023
  toUnsignedByteArray() {
1024
+ // toByteArray() emits 0 to 255, so unsigning it is an identity mapping and
1025
+ // the two methods return equal arrays. 11.0.0 keeps one of them and makes
1026
+ // this a deprecated alias; test/common-test.ts fails at that version.
769
1027
  return this.toByteArray().map(unsignByte);
770
1028
  }
771
1029
  /**
772
- * Convert a byte array to an Address6 object
773
- * @memberof Address6
774
- * @static
1030
+ * Convert a byte array to an Address6 object.
1031
+ *
1032
+ * Accepts unsigned bytes (0 to 255) or signed bytes (-128 to 127, as an
1033
+ * `Int8Array` or a Java `byte[]` holds them), folding signed values to their
1034
+ * unsigned equivalent. Throws `AddressError` unless given exactly 16
1035
+ * integers from -128 to 255.
1036
+ *
1037
+ * To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`.
775
1038
  * @returns {Address6}
776
1039
  */
777
1040
  static fromByteArray(bytes) {
1041
+ // Address4.fromByteArray takes unsigned bytes only. 11.0.0 aligns this
1042
+ // method with it, at which point the -128 floor here, unsignByte, and the
1043
+ // mapping below all go; test/common-test.ts fails at that version.
1044
+ common.assertByteArray(bytes, 16, 'IPv6', -128);
778
1045
  return this.fromUnsignedByteArray(bytes.map(unsignByte));
779
1046
  }
780
1047
  /**
781
- * Convert an unsigned byte array to an Address6 object
782
- * @memberof Address6
783
- * @static
1048
+ * Convert an unsigned byte array to an Address6 object.
1049
+ *
1050
+ * Throws `AddressError` unless given exactly 16 integers from 0 to 255.
1051
+ *
1052
+ * To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`.
784
1053
  * @returns {Address6}
785
1054
  */
786
1055
  static fromUnsignedByteArray(bytes) {
1056
+ common.assertByteArray(bytes, 16, 'IPv6', 0);
787
1057
  const BYTE_MAX = BigInt('256');
788
1058
  let result = BigInt('0');
789
1059
  let multiplier = BigInt('1');
@@ -795,76 +1065,234 @@ class Address6 {
795
1065
  }
796
1066
  /**
797
1067
  * Returns true if the address is in the canonical form, false otherwise
798
- * @memberof Address6
799
- * @instance
800
1068
  * @returns {boolean}
801
1069
  */
802
1070
  isCanonical() {
803
1071
  return this.addressMinusSuffix === this.canonicalForm();
804
1072
  }
805
1073
  /**
806
- * Returns true if the address is a link local address, false otherwise
807
- * @memberof Address6
808
- * @instance
1074
+ * Returns true if the address is a link-local unicast address in `fe80::/10`
1075
+ * ([RFC 4291 §2.4](https://datatracker.ietf.org/doc/html/rfc4291#section-2.4))
1076
+ * or an IPv4-mapped / NAT64 address whose embedded IPv4 address is link-local
1077
+ * (`169.254.0.0/16`, e.g. `::ffff:169.254.169.254`), false otherwise.
809
1078
  * @returns {boolean}
810
1079
  */
811
1080
  isLinkLocal() {
812
- // Zeroes are required, i.e. we can't check isInSubnet with 'fe80::/10'
813
- if (this.getBitsBase2(0, 64) ===
814
- '1111111010000000000000000000000000000000000000000000000000000000') {
815
- return true;
1081
+ const embedded = this.embeddedIPv4();
1082
+ if (embedded) {
1083
+ return embedded.isLinkLocal();
816
1084
  }
817
- return false;
1085
+ return this.isHostInSubnet(LINK_LOCAL_SUBNET);
818
1086
  }
819
1087
  /**
820
1088
  * Returns true if the address is a multicast address, false otherwise
821
- * @memberof Address6
822
- * @instance
823
1089
  * @returns {boolean}
824
1090
  */
825
1091
  isMulticast() {
826
- return this.getType() === 'Multicast';
1092
+ const embedded = this.embeddedIPv4();
1093
+ if (embedded) {
1094
+ return embedded.isMulticast();
1095
+ }
1096
+ const type = this.getType();
1097
+ return type === 'Multicast' || type.startsWith('Multicast ');
827
1098
  }
828
1099
  /**
829
- * Returns true if the address is a v4-in-v6 address, false otherwise
830
- * @memberof Address6
831
- * @instance
1100
+ * Returns true if the address was written in v4-in-v6 dotted-quad notation
1101
+ * (e.g. `::ffff:127.0.0.1`), false otherwise. This is a notation-level flag
1102
+ * and does not reflect whether the address bits lie in the IPv4-mapped
1103
+ * (`::ffff:0:0/96`) subnet — for that, see {@link isMapped4}.
832
1104
  * @returns {boolean}
833
1105
  */
834
1106
  is4() {
835
1107
  return this.v4;
836
1108
  }
1109
+ /**
1110
+ * Returns true if the address is an IPv4-mapped IPv6 address in
1111
+ * `::ffff:0:0/96` ([RFC 4291 §2.5.5.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2)),
1112
+ * false otherwise. Unlike {@link is4}, this checks the underlying address
1113
+ * bits rather than the textual notation, so `::ffff:127.0.0.1` and
1114
+ * `::ffff:7f00:1` both return true.
1115
+ * @returns {boolean}
1116
+ */
1117
+ isMapped4() {
1118
+ return this.isHostInSubnet(IPV4_MAPPED_SUBNET);
1119
+ }
1120
+ /**
1121
+ * If this address embeds a routable IPv4 address — i.e. it is IPv4-mapped
1122
+ * (`::ffff:0:0/96`) or sits in the NAT64 well-known prefix (`64:ff9b::/96`,
1123
+ * [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052)) — return that
1124
+ * embedded address as an {@link Address4}; otherwise return null.
1125
+ *
1126
+ * The special-property checks (`isLoopback`, `isLinkLocal`, `isMulticast`,
1127
+ * `isUnspecified`, `isPrivate`, `isCGNAT`, `isBroadcast`) call this first and
1128
+ * delegate to the embedded {@link Address4} when present, so a literal such as
1129
+ * `::ffff:127.0.0.1` is classified by what it actually reaches (loopback)
1130
+ * rather than by its IPv6 wrapper (which `getType()` reports as IPv4-mapped).
1131
+ * This matters wherever the checks back a trust-boundary decision (e.g. an
1132
+ * SSRF allow/deny filter): without normalization, `::ffff:10.0.0.1`,
1133
+ * `::ffff:169.254.169.254`, `64:ff9b::7f00:1`, etc. would all read as
1134
+ * non-internal.
1135
+ * @returns {Address4 | null}
1136
+ */
1137
+ embeddedIPv4() {
1138
+ if (this.isMapped4() || this.isHostInSubnet(NAT64_WELL_KNOWN_SUBNET)) {
1139
+ return this.to4();
1140
+ }
1141
+ return null;
1142
+ }
837
1143
  /**
838
1144
  * Returns true if the address is a Teredo address, false otherwise
839
- * @memberof Address6
840
- * @instance
841
1145
  * @returns {boolean}
842
1146
  */
843
1147
  isTeredo() {
844
- return this.isInSubnet(new Address6('2001::/32'));
1148
+ return this.isHostInSubnet(TEREDO_SUBNET);
845
1149
  }
846
1150
  /**
847
1151
  * Returns true if the address is a 6to4 address, false otherwise
848
- * @memberof Address6
849
- * @instance
850
1152
  * @returns {boolean}
851
1153
  */
852
1154
  is6to4() {
853
- return this.isInSubnet(new Address6('2002::/16'));
1155
+ return this.isHostInSubnet(SIX_TO_FOUR_SUBNET);
854
1156
  }
855
1157
  /**
856
1158
  * Returns true if the address is a loopback address, false otherwise
857
- * @memberof Address6
858
- * @instance
859
1159
  * @returns {boolean}
860
1160
  */
861
1161
  isLoopback() {
1162
+ const embedded = this.embeddedIPv4();
1163
+ if (embedded) {
1164
+ return embedded.isLoopback();
1165
+ }
862
1166
  return this.getType() === 'Loopback';
863
1167
  }
1168
+ /**
1169
+ * 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.
1170
+ * @returns {boolean}
1171
+ */
1172
+ isULA() {
1173
+ return this.isHostInSubnet(ULA_SUBNET);
1174
+ }
1175
+ /**
1176
+ * Returns true if the address is private, i.e. a Unique Local Address in
1177
+ * `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)), an
1178
+ * address in the NAT64 local-use range `64:ff9b:1::/48`
1179
+ * ([RFC 8215](https://datatracker.ietf.org/doc/html/rfc8215)), or an
1180
+ * IPv4-mapped / NAT64 well-known address whose embedded IPv4 address is in
1181
+ * one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918)
1182
+ * private ranges (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to
1183
+ * {@link Address4.isPrivate}; use it instead of {@link isULA} when you need to
1184
+ * catch mapped RFC 1918 addresses as well as native ULAs.
1185
+ *
1186
+ * The local-use NAT64 range is reported private as a whole rather than by
1187
+ * its embedded IPv4 address: an operator may carve a prefix of any RFC 6052
1188
+ * length out of `64:ff9b:1::/48`, so the same bits decode to different IPv4
1189
+ * addresses under different deployments and no single decoding is correct.
1190
+ * Use {@link toAddress4Nat64} with the deployment's prefix to decode one.
1191
+ * @returns {boolean}
1192
+ */
1193
+ isPrivate() {
1194
+ const embedded = this.embeddedIPv4();
1195
+ if (embedded) {
1196
+ return embedded.isPrivate();
1197
+ }
1198
+ return this.isULA() || this.isHostInSubnet(NAT64_LOCAL_USE_SUBNET);
1199
+ }
1200
+ /**
1201
+ * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded
1202
+ * IPv4 address is in the carrier-grade NAT range `100.64.0.0/10`
1203
+ * ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)), false
1204
+ * otherwise. There is no native IPv6 CGNAT range, so this only ever returns
1205
+ * true for an embedded IPv4 address (e.g. `::ffff:100.64.0.1`).
1206
+ * @returns {boolean}
1207
+ */
1208
+ isCGNAT() {
1209
+ const embedded = this.embeddedIPv4();
1210
+ if (embedded) {
1211
+ return embedded.isCGNAT();
1212
+ }
1213
+ return false;
1214
+ }
1215
+ /**
1216
+ * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded
1217
+ * IPv4 address is the limited broadcast address `255.255.255.255`
1218
+ * ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)), false otherwise.
1219
+ * There is no IPv6 broadcast, so this only ever returns true for an embedded
1220
+ * IPv4 address (e.g. `::ffff:255.255.255.255`).
1221
+ * @returns {boolean}
1222
+ */
1223
+ isBroadcast() {
1224
+ const embedded = this.embeddedIPv4();
1225
+ if (embedded) {
1226
+ return embedded.isBroadcast();
1227
+ }
1228
+ return false;
1229
+ }
1230
+ /**
1231
+ * Returns true if the address is the unspecified address `::`.
1232
+ * @returns {boolean}
1233
+ */
1234
+ isUnspecified() {
1235
+ const embedded = this.embeddedIPv4();
1236
+ if (embedded) {
1237
+ return embedded.isUnspecified();
1238
+ }
1239
+ return this.getType() === 'Unspecified';
1240
+ }
1241
+ /**
1242
+ * Returns true if the address is in the documentation prefix `2001:db8::/32` ([RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849)).
1243
+ * @returns {boolean}
1244
+ */
1245
+ isDocumentation() {
1246
+ return DOCUMENTATION_SUBNETS.some((subnet) => this.isHostInSubnet(subnet));
1247
+ }
1248
+ /**
1249
+ * Returns true if the address is in the benchmarking range `2001:2::/48`
1250
+ * ([RFC 5180](https://datatracker.ietf.org/doc/html/rfc5180)) or is an
1251
+ * IPv4-mapped / NAT64 address whose embedded IPv4 address is in
1252
+ * `198.18.0.0/15`, false otherwise.
1253
+ * @returns {boolean}
1254
+ */
1255
+ isBenchmarking() {
1256
+ const embedded = this.embeddedIPv4();
1257
+ if (embedded) {
1258
+ return embedded.isBenchmarking();
1259
+ }
1260
+ return this.isHostInSubnet(BENCHMARKING_SUBNET);
1261
+ }
1262
+ /**
1263
+ * Returns true if the address is globally reachable: inside the global
1264
+ * unicast allocation `2000::/3` (the only range the [IANA IPv6 Address Space
1265
+ * Registry](https://www.iana.org/assignments/ipv6-address-space/) assigns
1266
+ * for global unicast; everything else is reserved, ULA, link-local, or
1267
+ * multicast) and not in any block the [IANA IPv6 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv6-special-registry/)
1268
+ * marks as not globally reachable. An IPv4-mapped or NAT64 well-known
1269
+ * address answers for its embedded IPv4 address, so `::ffff:10.0.0.1` and
1270
+ * `64:ff9b::7f00:1` are not global. Teredo (`2001::/32`) and 6to4
1271
+ * (`2002::/16`) are not global either: the registry lists them as N/A and a
1272
+ * packet to one needs a relay.
1273
+ *
1274
+ * This covers everything the individual classifiers name and the blocks they
1275
+ * do not: the discard-only prefix `100::/64`, the IETF protocol assignments
1276
+ * in `2001::/23`, the deprecated site-local `fec0::/10` and IPv4-compatible
1277
+ * `::/96` ranges, and unallocated space such as `4000::/3`. It is the single
1278
+ * predicate to use where a request must not reach an internal or
1279
+ * special-purpose destination; see SECURITY.md.
1280
+ * @returns {boolean}
1281
+ */
1282
+ isGlobal() {
1283
+ const embedded = this.embeddedIPv4();
1284
+ if (embedded) {
1285
+ return embedded.isGlobal();
1286
+ }
1287
+ return (this.isHostInSubnet(GLOBAL_UNICAST_SUBNET) &&
1288
+ common.isGloballyReachable.call(this, SPECIAL_PURPOSE_V6));
1289
+ }
864
1290
  // #endregion
865
1291
  // #region HTML
866
1292
  /**
867
- * @returns {String} the address in link form with a default port of 80
1293
+ * Returns the address as an HTTP URL with the host bracketed, e.g.
1294
+ * `http://[2001:db8::1]/`. If `optionalPort` is provided it is appended,
1295
+ * e.g. `http://[2001:db8::1]:8080/`.
868
1296
  */
869
1297
  href(optionalPort) {
870
1298
  if (optionalPort === undefined) {
@@ -876,7 +1304,12 @@ class Address6 {
876
1304
  return `http://[${this.correctForm()}]${optionalPort}/`;
877
1305
  }
878
1306
  /**
879
- * @returns {String} a link suitable for conveying the address via a URL hash
1307
+ * Returns an HTML `<a>` element whose `href` encodes the address in a URL
1308
+ * hash fragment (default prefix `/#address=`). Useful for linking between
1309
+ * pages of an address-inspector UI.
1310
+ * @param options.className - CSS class for the rendered `<a>` element
1311
+ * @param options.prefix - hash prefix prepended to the address (default `/#address=`)
1312
+ * @param options.v4 - when true, render the address in v4-in-v6 form
880
1313
  */
881
1314
  link(options) {
882
1315
  if (!options) {
@@ -896,25 +1329,33 @@ class Address6 {
896
1329
  formFunction = this.to4in6;
897
1330
  }
898
1331
  const form = formFunction.call(this);
1332
+ const safeHref = helpers.escapeHtml(`${options.prefix}${form}`);
1333
+ const safeForm = helpers.escapeHtml(form);
899
1334
  if (options.className) {
900
- return `<a href="${options.prefix}${form}" class="${options.className}">${form}</a>`;
1335
+ const safeClass = helpers.escapeHtml(options.className);
1336
+ return `<a href="${safeHref}" class="${safeClass}">${safeForm}</a>`;
901
1337
  }
902
- return `<a href="${options.prefix}${form}">${form}</a>`;
1338
+ return `<a href="${safeHref}">${safeForm}</a>`;
903
1339
  }
904
1340
  /**
905
- * Groups an address
1341
+ * Groups an address.
1342
+ *
1343
+ * Returns an HTML fragment: each group is wrapped in a `<span>` carrying
1344
+ * the group classes an address-inspector UI hovers on. The address content
1345
+ * is HTML-escaped; anything you concatenate around it is your
1346
+ * responsibility.
906
1347
  * @returns {String}
907
1348
  */
908
1349
  group() {
909
1350
  if (this.elidedGroups === 0) {
910
1351
  // The simple case
911
- return helpers.simpleGroup(this.address).join(':');
1352
+ return helpers.simpleGroup(this.addressMinusSuffix).join(':');
912
1353
  }
913
1354
  assert(typeof this.elidedGroups === 'number');
914
1355
  assert(typeof this.elisionBegin === 'number');
915
1356
  // The elided case
916
1357
  const output = [];
917
- const [left, right] = this.address.split('::');
1358
+ const [left, right] = this.addressMinusSuffix.split('::');
918
1359
  if (left.length) {
919
1360
  output.push(...helpers.simpleGroup(left));
920
1361
  }
@@ -944,8 +1385,6 @@ class Address6 {
944
1385
  /**
945
1386
  * Generate a regular expression string that can be used to find or validate
946
1387
  * all variations of this address
947
- * @memberof Address6
948
- * @instance
949
1388
  * @param {boolean} substringSearch
950
1389
  * @returns {string}
951
1390
  */
@@ -990,8 +1429,6 @@ class Address6 {
990
1429
  /**
991
1430
  * Generate a regular expression that can be used to find or validate all
992
1431
  * variations of this address.
993
- * @memberof Address6
994
- * @instance
995
1432
  * @param {boolean} substringSearch
996
1433
  * @returns {RegExp}
997
1434
  */
@@ -1000,4 +1437,22 @@ class Address6 {
1000
1437
  }
1001
1438
  }
1002
1439
  exports.Address6 = Address6;
1440
+ const TYPE_SUBNETS = Object.keys(constants6.TYPES).map((subnet) => [
1441
+ new Address6(subnet),
1442
+ constants6.TYPES[subnet],
1443
+ ]);
1444
+ const TEREDO_SUBNET = new Address6('2001::/32');
1445
+ const SIX_TO_FOUR_SUBNET = new Address6('2002::/16');
1446
+ const ULA_SUBNET = new Address6('fc00::/7');
1447
+ const LINK_LOCAL_SUBNET = new Address6('fe80::/10');
1448
+ const DOCUMENTATION_SUBNETS = [new Address6('2001:db8::/32'), new Address6('3fff::/20')];
1449
+ const BENCHMARKING_SUBNET = new Address6('2001:2::/48');
1450
+ const GLOBAL_UNICAST_SUBNET = new Address6('2000::/3');
1451
+ const SPECIAL_PURPOSE_V6 = constants6.SPECIAL_PURPOSE.map(([cidr, , reachable]) => ({
1452
+ subnet: new Address6(cidr),
1453
+ reachable,
1454
+ }));
1455
+ const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96');
1456
+ const NAT64_WELL_KNOWN_SUBNET = new Address6('64:ff9b::/96');
1457
+ const NAT64_LOCAL_USE_SUBNET = new Address6('64:ff9b:1::/48');
1003
1458
  //# sourceMappingURL=ipv6.js.map