@mark1russell7/netdiag 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +244 -2
- package/build/address.d.ts +56 -0
- package/build/address.d.ts.map +1 -0
- package/build/address.js +172 -0
- package/build/address.js.map +1 -0
- package/build/diagnosis-a4hRzFxp.js +922 -0
- package/build/diagnosis-a4hRzFxp.js.map +1 -0
- package/build/diagnosis.d.ts +8 -0
- package/build/diagnosis.js +1 -0
- package/build/index-K2dGiYxW.d.ts +259 -0
- package/build/index-K2dGiYxW.d.ts.map +1 -0
- package/build/index.d.ts +392 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +1359 -0
- package/build/index.js.map +1 -0
- package/build/kinds-CQv769lK.js +341 -0
- package/build/kinds-CQv769lK.js.map +1 -0
- package/build/kinds-_NShfZpU.d.ts +377 -0
- package/build/kinds-_NShfZpU.d.ts.map +1 -0
- package/build/layer-BNg0VYuK.d.ts +22 -0
- package/build/layer-BNg0VYuK.d.ts.map +1 -0
- package/build/tools.d.ts +206 -0
- package/build/tools.d.ts.map +1 -0
- package/build/tools.js +896 -0
- package/build/tools.js.map +1 -0
- package/package.json +81 -3
- package/src/address/index.ts +155 -0
- package/src/diagnosis/catalog.ts +357 -0
- package/src/diagnosis/index.ts +47 -0
- package/src/diagnosis/model.ts +322 -0
- package/src/diagnosis/score.ts +160 -0
- package/src/fact.ts +87 -0
- package/src/index.ts +68 -0
- package/src/kinds.ts +460 -0
- package/src/layer.ts +31 -0
- package/src/probe.ts +61 -0
- package/src/probes/native.ts +354 -0
- package/src/probes/socket.ts +520 -0
- package/src/readers.ts +367 -0
- package/src/run.ts +163 -0
- package/src/scope.ts +152 -0
- package/src/target.ts +116 -0
- package/src/tools/common.ts +47 -0
- package/src/tools/host.ts +91 -0
- package/src/tools/index.ts +38 -0
- package/src/tools/licenses.ts +37 -0
- package/src/tools/mtr.ts +156 -0
- package/src/tools/ping.ts +231 -0
- package/src/tools/traceroute.ts +313 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Russell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,245 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mark1russell7/netdiag
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This package has the core model of netdiag. It has the typed facts at each layer of the stack, the contract of a probe and the scope rule. It also has the catalog of faults and the probability model.
|
|
4
|
+
|
|
5
|
+
**State: ND-1, ND-2, ND-4, and the lab score and the loopback lab of ND-7 of the [roadmap](../../docs/ROADMAP.md).** The kernel lab and the bars of the gate come next.
|
|
6
|
+
|
|
7
|
+
## Facts
|
|
8
|
+
|
|
9
|
+
A fact has a layer, a time, a source and a value. Its kind names the type of its value, for example `tcp.connect`.
|
|
10
|
+
|
|
11
|
+
| Field | What it is |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `id` | An id that is unique in the facts of one runner, for example `f12`. |
|
|
14
|
+
| `kind` | The name of the fact kind. The kind has the check of the value. |
|
|
15
|
+
| `layer` | One of `link`, `wifi`, `ip`, `route`, `path`, `dns`, `tcp`, `tls` and `http`. |
|
|
16
|
+
| `subject` | The target of the fact: a host and an optional port. |
|
|
17
|
+
| `time` | The start and the end, in milliseconds on the monotonic clock of the runner. |
|
|
18
|
+
| `source` | The probe, its version, and the outside tool when the probe starts one. |
|
|
19
|
+
| `value` | The value. It passes the check of its kind. |
|
|
20
|
+
|
|
21
|
+
A fact is plain data, so a record of facts is JSON.
|
|
22
|
+
|
|
23
|
+
## Probes
|
|
24
|
+
|
|
25
|
+
A probe is an adapter. It tells if it is available, it does its work, and it parses its raw output into observations.
|
|
26
|
+
|
|
27
|
+
- `peers(input)` gives each host that the probe sends packets to. A passive probe gives no peers.
|
|
28
|
+
- `run(input, context)` does the effects and gives the raw output.
|
|
29
|
+
- `parse(raw, input)` is a pure function. A record of a raw output thus gives the same facts again.
|
|
30
|
+
|
|
31
|
+
A network failure is a fact, not an error. For example, a TCP connect that times out gives the outcome `timeout`.
|
|
32
|
+
|
|
33
|
+
## The scope rule
|
|
34
|
+
|
|
35
|
+
An active probe sends packets only to a peer that the user owns or names. The runner examines each peer before the probe starts.
|
|
36
|
+
|
|
37
|
+
| Basis | Why the target is in the scope |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `named` | The user named the target for this diagnosis. |
|
|
40
|
+
| `owned` | The user declares that the user owns the network or the host. |
|
|
41
|
+
| `configured` | The configuration of the computer names it, for example the gateway or the DNS resolver. |
|
|
42
|
+
| `resolved` | A DNS answer for a granted name gave this address. |
|
|
43
|
+
|
|
44
|
+
The scope also limits the number of ports of each host. The default limit is 8. Thus netdiag does no port scan.
|
|
45
|
+
|
|
46
|
+
## IP addresses
|
|
47
|
+
|
|
48
|
+
The entry `@mark1russell7/netdiag/address` has plain functions for IP addresses and networks. It uses only the module `node:net`.
|
|
49
|
+
|
|
50
|
+
| Function | What it gives |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `addressFamily(address)` | `ipv4`, `ipv6`, or `undefined` for a text that is not an address. |
|
|
53
|
+
| `addressClass(address)` | The class of the address, from the special-purpose ranges of RFC 6890. |
|
|
54
|
+
| `addressKind(address)` | `private` for an address that the internet does not route to, else `public`. |
|
|
55
|
+
| `parseCidr(text)` | A CIDR range, or the problem of the text. |
|
|
56
|
+
| `cidrSet(ranges)` | A set of ranges. Its `has(address)` tells if an address is in a range of the set. |
|
|
57
|
+
|
|
58
|
+
- The classes are `unspecified`, `loopback`, `private`, `shared`, `link-local`, `unique-local`, `documentation`, `benchmarking`, `multicast`, `reserved` and `public`.
|
|
59
|
+
- The table `ADDRESS_CLASSES` holds the ranges of the classes as data.
|
|
60
|
+
- An IPv4 address in IPv6 form, for example `::ffff:192.0.2.1`, is its IPv4 address.
|
|
61
|
+
- An address for documentation is `public`. Thus a lab can use `203.0.113.0/24` for hosts on the internet.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { addressClass, cidrSet, parseCidr } from "@mark1russell7/netdiag/address";
|
|
65
|
+
|
|
66
|
+
addressClass("100.64.0.1"); // "shared": the carrier-grade NAT range
|
|
67
|
+
const lab = parseCidr("203.0.113.0/24");
|
|
68
|
+
if (lab.tag === "ok") cidrSet([lab.value]).has("::ffff:203.0.113.7"); // true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Native probes
|
|
72
|
+
|
|
73
|
+
The native probes use only the `node:` modules. They need no outside tool.
|
|
74
|
+
|
|
75
|
+
| Probe | Id | Facts |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `DNS_PROBE` | `dns.query` | `dns.answer`: the outcome, the addresses and the lowest TTL of one query for `A` or `AAAA` records. |
|
|
78
|
+
| `tcpProbe()` | `tcp.connect` | `tcp.connect`: the outcome and the time of one connect. |
|
|
79
|
+
| `tlsProbe()` | `tls.handshake` | `tcp.connect` and `tls.handshake`: the time of the handshake, the version, the cipher, ALPN and the certificate. |
|
|
80
|
+
| `httpProbe()` | `http.request` | `tcp.connect`, `tls.handshake` for `https`, and `http.response`: the status, some fields of the head and the time to the first byte. |
|
|
81
|
+
|
|
82
|
+
- A probe connects only to an IP address. The DNS probe gives the addresses of a name first. Thus the runner examines each address against the scope.
|
|
83
|
+
- The peer of the DNS probe is the resolver, not the name.
|
|
84
|
+
- A time limit or a stop of the signal closes the socket. The probe then gives the outcome `timeout`, or the runner gives `aborted`.
|
|
85
|
+
- The TLS probe does not refuse a certificate that is not valid. It records the problem, for example `expired`, `name` or `untrusted`.
|
|
86
|
+
- The trust tells the root of the chain. `public` is a root of Node.js. `private` is a different root, for example the root of a proxy that examines TLS.
|
|
87
|
+
- The option `roots` adds roots that the user trusts, for example the root of a company. Their trust is `private`.
|
|
88
|
+
- The option `publicRoots` adds roots with the trust `public`. A lab gives its own root here, so its servers stand for servers on the internet.
|
|
89
|
+
- The HTTP probe follows no redirect. It reads the body up to 64 KiB.
|
|
90
|
+
- For `https`, the HTTP probe sends its request only when the certificate passes its check. Thus an interceptor does not get the path.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { realClock } from "@mark1russell7/algebra/clock";
|
|
94
|
+
import { TCP_PROBE, createRunner, scopeOf } from "@mark1russell7/netdiag";
|
|
95
|
+
|
|
96
|
+
const scope = scopeOf([{ kind: "range", cidr: "192.168.1.0/24", basis: "owned" }]);
|
|
97
|
+
if (scope.tag === "err") throw new Error(scope.error.join(" "));
|
|
98
|
+
|
|
99
|
+
const runner = createRunner({ scope: scope.value, clock: realClock });
|
|
100
|
+
const outcome = await runner.run(TCP_PROBE, { address: "192.168.1.1", port: 443 });
|
|
101
|
+
// The fact tcp.connect has the outcome connected, refused, timeout, unreachable, reset or error.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Outside tools
|
|
105
|
+
|
|
106
|
+
The entry `@mark1russell7/netdiag/tools` has optional adapters of outside tools. netdiag bundles no tool. The user installs each tool.
|
|
107
|
+
|
|
108
|
+
| Probe | Id | Tool | Facts |
|
|
109
|
+
| --- | --- | --- | --- |
|
|
110
|
+
| `pingProbe(host)` | `icmp.ping` | `ping` | `icmp.echo`: the echo replies, their round-trip times, and the messages of an unreachable address. |
|
|
111
|
+
| `tracerouteProbe(host)` | `path.traceroute` | `traceroute`, `tracert` on Windows | `path.trace`: the routers that answered each TTL, their round-trip times, and the messages of an unreachable address. |
|
|
112
|
+
| `mtrProbe(host)` | `path.mtr` | `mtr` (Linux and macOS) | `path.report`: the loss and the round-trip times of each hop over many cycles. |
|
|
113
|
+
|
|
114
|
+
- A host starts each tool. Its `run(command, args, options)` starts a program with a list of arguments and no shell.
|
|
115
|
+
- The function `runCommand` of `@procedures/shell` is a host with no change. A test gives a fake host.
|
|
116
|
+
- An adapter reads only English output. On Linux and macOS, it sets `LC_ALL=C`. On Windows, the language of the system sets the output.
|
|
117
|
+
- On Linux, the adapter reads the `ping` of iputils. On macOS, it uses `ping6` for an IPv6 address.
|
|
118
|
+
- On Linux, the trace adapter reads the Modern traceroute (the package `traceroute`). On macOS, it uses `traceroute6` for an IPv6 address.
|
|
119
|
+
- The `mtr` adapter reads the JSON report of `mtr --json` (version 0.92 or later). The default of 20 cycles takes approximately 20 seconds.
|
|
120
|
+
- Output that netdiag cannot read gives the outcome `error` with the reason. It does not give a reply that did not occur.
|
|
121
|
+
- The table `TOOL_LICENSES` holds the license of each tool as data. A tool with a restrictive license has no adapter.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { runCommand } from "@procedures/shell";
|
|
125
|
+
import { pingProbe, type ToolHost } from "@mark1russell7/netdiag/tools";
|
|
126
|
+
|
|
127
|
+
const host: ToolHost = {
|
|
128
|
+
platform: process.platform,
|
|
129
|
+
run: (command, args, options) => runCommand(command, { args: [...args], timeout: options.timeoutMs, signal: options.signal, env: options.env }),
|
|
130
|
+
};
|
|
131
|
+
// The runner comes from the example of the native probes.
|
|
132
|
+
const outcome = await runner.run(pingProbe(host), { address: "192.168.1.1", count: 20 });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Diagnosis
|
|
136
|
+
|
|
137
|
+
The entry `@mark1russell7/netdiag/diagnosis` has the probability model. It imports no `node:` module, so a browser can also use it.
|
|
138
|
+
|
|
139
|
+
- A feature is a discrete observation. For example, `tcp.connect` has the values `connected`, `refused`, `timeout`, `unreachable` and `reset`.
|
|
140
|
+
- Each feature has a healthy distribution: the probability of each value when there is no fault.
|
|
141
|
+
- A fault names a part of the network and the feature values that it predicts. The other features keep their healthy distributions.
|
|
142
|
+
- The hypotheses are `none` (no fault), each fault of the catalog, and `unknown` (a cause that the catalog does not have).
|
|
143
|
+
- The hypothesis `unknown` gives the same probability to each value. Thus it wins when no fault of the catalog explains the observations.
|
|
144
|
+
|
|
145
|
+
| Function | What it gives |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `diagnose(catalog, evidence)` | The probability of each hypothesis, from the most probable. The model is naive Bayes with logarithms. |
|
|
148
|
+
| `explain(catalog, evidence, id)` | The weight of each observation for a cause: the `log10` of its likelihood ratio against `none`. |
|
|
149
|
+
| `predictive(catalog, diagnosis, feature, primary)` | The probability of each value of a feature before a probe observes it. The choice of the next probe (ND-5) uses it. |
|
|
150
|
+
|
|
151
|
+
Evidence with `primary: false` comes from a reference host, for example a second public server. A fault with the reach `target` does not change the reference host. A fault with the reach `shared` changes both hosts. Thus a slow TLS handshake on both hosts points to a TLS proxy. A slow handshake on the target only points to a slow server.
|
|
152
|
+
|
|
153
|
+
The standard catalog `STANDARD_CATALOG` has 15 features and 20 faults. The rules of practice give its first likelihoods, and each hypothesis has the same prior. The lab runs (ND-7) then fit these numbers.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { STANDARD_CATALOG, diagnose } from "@mark1russell7/netdiag/diagnosis";
|
|
157
|
+
|
|
158
|
+
const result = diagnose(STANDARD_CATALOG, [
|
|
159
|
+
{ feature: "icmp.reply", value: "none", primary: true },
|
|
160
|
+
{ feature: "tcp.connect", value: "connected", primary: true },
|
|
161
|
+
]);
|
|
162
|
+
// result.causes[0].id is "filter.icmp": a filter blocks ICMP, and the host is up.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Readers
|
|
166
|
+
|
|
167
|
+
`readEvidence(facts, options)` changes the facts of the native probes and of the adapters into evidence for the model. Each value that it gives is a value of a feature of `STANDARD_CATALOG`.
|
|
168
|
+
|
|
169
|
+
| Feature | The source of the value |
|
|
170
|
+
| --- | --- |
|
|
171
|
+
| `dns.answer` | The outcome of each DNS query. One answer `ok` for a name and a resolver decides. `nodata` gives `ok`, because the name exists. |
|
|
172
|
+
| `dns.address` | The addresses of a public name. One private address decides. A local name, for example `nas.local`, gives no value. |
|
|
173
|
+
| `ip.family` | The TCP connects to the IPv4 and the IPv6 addresses of one name. |
|
|
174
|
+
| `tcp.connect`, `tcp.time` | The outcome and the time of each TCP connect. A time of 900 ms or more tells that the SYN went again. |
|
|
175
|
+
| `tls.handshake`, `tls.trust` | The outcome of the handshake and the root of the certificate chain. A chain with no root of the store gives `private`. |
|
|
176
|
+
| `tls.ratio` | The time of the handshake divided by the time of the last TCP connect to the same address and port. |
|
|
177
|
+
| `http.status`, `http.ttfb` | The class of the status, and the time to the first byte divided by the time of the TCP connect. |
|
|
178
|
+
| `gateway.reply` | The echo requests to the address of the option `gateway`. An unreachable gateway gives `none`. |
|
|
179
|
+
| `icmp.reply` | The echo requests to each other address: `reply`, `none` or `unreachable`. |
|
|
180
|
+
| `path.loss` | The share of the echo requests with a reply, for 20 requests or more: `none`, `some` (up to 5 % loss) or `high`. A report of `mtr` that reached the address gives it from its last hop. |
|
|
181
|
+
|
|
182
|
+
- The reader gives one evidence for each feature, subject and role. When a subject has more facts, the last fact decides.
|
|
183
|
+
- A fact that gives no evidence goes in `skipped`, with the reason. For example, the catalog has no value for the TLS outcome `not-tls` yet.
|
|
184
|
+
- A fact `path.trace` gives no evidence yet. The catalog has no feature for the hops of a path. The lab runs (ND-7) fit one.
|
|
185
|
+
- The option `primary` tells which facts are about the target. The other facts are about a reference host.
|
|
186
|
+
- The option `gateway` gives the IP address of the default gateway.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { STANDARD_CATALOG, TCP_PROBE, diagnose, readEvidence } from "@mark1russell7/netdiag";
|
|
190
|
+
|
|
191
|
+
// The runner comes from the example of the native probes.
|
|
192
|
+
const outcome = await runner.run(TCP_PROBE, { address: "192.168.1.1", port: 443 });
|
|
193
|
+
if (outcome.status === "done") {
|
|
194
|
+
const { evidence, skipped } = readEvidence(outcome.facts);
|
|
195
|
+
const result = diagnose(STANDARD_CATALOG, evidence);
|
|
196
|
+
// A connect with the outcome refused gives result.causes[0].id "port.closed".
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## The lab score
|
|
201
|
+
|
|
202
|
+
A lab makes one fault on purpose. Then netdiag must name it. The entry `@mark1russell7/netdiag/diagnosis` has the score of these runs.
|
|
203
|
+
|
|
204
|
+
| Function | What it gives |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| `labRecordOf(catalog, case, truth, diagnosis)` | The record of one run: the true hypothesis, the predicted cause, the rank of the truth and the probability of each cause. |
|
|
207
|
+
| `scoreLab(catalog, records)` | The confusion table, the recall of each hypothesis with its Wilson interval, the accuracy and the mean Brier score. |
|
|
208
|
+
| `labGate(score, bars)` | A verdict for each hypothesis with a bar: `pass`, `fail` or `no-read`. |
|
|
209
|
+
|
|
210
|
+
- The recall of a hypothesis is the part of its runs where the diagnosis names it.
|
|
211
|
+
- A hypothesis passes when the lower end of its recall interval is its bar or more.
|
|
212
|
+
- A hypothesis with no runs gets `no-read`, and the gate does not pass.
|
|
213
|
+
- A hypothesis with no bar goes in `unbarred`, for example a fault that no lab can make.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { STANDARD_CATALOG, diagnose, labGate, labRecordOf, scoreLab } from "@mark1russell7/netdiag/diagnosis";
|
|
217
|
+
|
|
218
|
+
const diagnosis = diagnose(STANDARD_CATALOG, [{ feature: "tcp.connect", value: "refused", primary: true }]);
|
|
219
|
+
const record = labRecordOf(STANDARD_CATALOG, "port.closed/refused", "port.closed", diagnosis);
|
|
220
|
+
if (record.tag === "err") throw new Error(record.error);
|
|
221
|
+
|
|
222
|
+
const score = scoreLab(STANDARD_CATALOG, [record.value]);
|
|
223
|
+
if (score.tag === "err") throw new Error(score.error.join(" "));
|
|
224
|
+
const gate = labGate(score.value, { "port.closed": 0.1 });
|
|
225
|
+
// gate.value.verdicts[0].status is "pass": the lower end of the recall of 1 run of 1 is 0.21.
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Example
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { fakeClock } from "@mark1russell7/algebra/clock";
|
|
232
|
+
import { createRunner, namedGrant, parseTarget, scopeOf } from "@mark1russell7/netdiag";
|
|
233
|
+
|
|
234
|
+
const named = parseTarget("https://example.com");
|
|
235
|
+
if (named.tag === "err") throw new Error(named.error);
|
|
236
|
+
const scope = scopeOf([namedGrant(named.value)]);
|
|
237
|
+
if (scope.tag === "err") throw new Error(scope.error.join(" "));
|
|
238
|
+
|
|
239
|
+
const runner = createRunner({ scope: scope.value, clock: fakeClock() });
|
|
240
|
+
// runner.run(probe, input) gives the outcome: done, refused, unavailable, failed or aborted.
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Result } from "@mark1russell7/optional";
|
|
2
|
+
/** The family of an IP address. */
|
|
3
|
+
export type AddressFamily = "ipv4" | "ipv6";
|
|
4
|
+
/**
|
|
5
|
+
* The class of an IP address, from the special-purpose ranges of RFC 6890.
|
|
6
|
+
*
|
|
7
|
+
* - `unspecified`: `0.0.0.0/8` and `::`.
|
|
8
|
+
* - `loopback`: `127.0.0.0/8` and `::1`.
|
|
9
|
+
* - `private`: the ranges of RFC 1918.
|
|
10
|
+
* - `shared`: `100.64.0.0/10`, the carrier-grade NAT range of RFC 6598.
|
|
11
|
+
* - `link-local`: `169.254.0.0/16` and `fe80::/10`.
|
|
12
|
+
* - `unique-local`: `fc00::/7`, the private range of IPv6 (RFC 4193).
|
|
13
|
+
* - `documentation`: the ranges of RFC 5737 and RFC 3849 for examples.
|
|
14
|
+
* - `benchmarking`: `198.18.0.0/15` (RFC 2544).
|
|
15
|
+
* - `multicast`: `224.0.0.0/4` and `ff00::/8`.
|
|
16
|
+
* - `reserved`: `240.0.0.0/4`, with the broadcast address.
|
|
17
|
+
* - `public`: each other address.
|
|
18
|
+
*/
|
|
19
|
+
export type AddressClass = "unspecified" | "loopback" | "private" | "shared" | "link-local" | "unique-local" | "documentation" | "benchmarking" | "multicast" | "reserved" | "public";
|
|
20
|
+
/** A CIDR range: an address of the network and the length of its prefix. */
|
|
21
|
+
export interface Cidr {
|
|
22
|
+
readonly address: string;
|
|
23
|
+
readonly prefix: number;
|
|
24
|
+
readonly family: AddressFamily;
|
|
25
|
+
}
|
|
26
|
+
/** The special-purpose ranges, as data. An address that is in no range is `public`. */
|
|
27
|
+
export declare const ADDRESS_CLASSES: readonly {
|
|
28
|
+
readonly cidr: string;
|
|
29
|
+
readonly class: Exclude<AddressClass, "public">;
|
|
30
|
+
}[];
|
|
31
|
+
/** This function gives the canonical form of an address: no spaces, no brackets, lower case, and IPv4 for a mapped IPv4 address. */
|
|
32
|
+
export declare function canonicalAddress(address: string): string;
|
|
33
|
+
/** This function gives the family of an IP address, or `undefined` for a text that is not an IP address. */
|
|
34
|
+
export declare function addressFamily(address: string): AddressFamily | undefined;
|
|
35
|
+
/** This function gives the CIDR range of a text, for example `192.0.2.0/24` or `2001:db8::/32`. An address alone is a range of one address. */
|
|
36
|
+
export declare function parseCidr(text: string): Result<Cidr, string>;
|
|
37
|
+
/** This function gives the text of a CIDR range. */
|
|
38
|
+
export declare function formatCidr(cidr: Cidr): string;
|
|
39
|
+
/** A set of CIDR ranges. */
|
|
40
|
+
export interface CidrSet {
|
|
41
|
+
readonly ranges: readonly Cidr[];
|
|
42
|
+
/** This function tells if an address is in a range of the set. A text that is not an IP address is in no set. */
|
|
43
|
+
has(address: string): boolean;
|
|
44
|
+
}
|
|
45
|
+
/** This function gives the set of some CIDR ranges. */
|
|
46
|
+
export declare function cidrSet(ranges: readonly Cidr[]): CidrSet;
|
|
47
|
+
/** This function tells if an address is in a CIDR range. */
|
|
48
|
+
export declare function cidrContains(cidr: Cidr, address: string): boolean;
|
|
49
|
+
/** This function gives the class of an IP address, or `undefined` for a text that is not an IP address. */
|
|
50
|
+
export declare function addressClass(address: string): AddressClass | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* This function tells if the internet routes to an address. A `private` address is unspecified, loopback, private,
|
|
53
|
+
* shared, link-local or unique-local. Each other address is `public`, also an address for documentation.
|
|
54
|
+
*/
|
|
55
|
+
export declare function addressKind(address: string): "public" | "private" | undefined;
|
|
56
|
+
//# sourceMappingURL=address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.d.ts","names":[],"sources":["../src/address/index.ts"],"mappings":";;YASY;;;;;;;;;;;;;;;;YAiBA;;iBAcK;WACN;WACA;WACA,QAAQ;;;qBAIN;WAAqC;WAAuB,OAAO,QAAQ;;;wBA0BxE,iBAAiB;;wBAQjB,cAAc,kBAAkB;;wBAMhC,UAAU,eAAe,OAAO;;wBAYhC,WAAW,MAAM;;iBAKhB;WACN,iBAAiB;;EAE1B,IAAI;;;wBAIU,QAAQ,iBAAiB,SAAS;;wBAclC,aAAa,MAAM,MAAM;;wBAgBzB,aAAa,kBAAkB;;;;;wBAS/B,YAAY"}
|
package/build/address.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { BlockList, isIP } from "node:net";
|
|
2
|
+
import { err, ok } from "@mark1russell7/optional";
|
|
3
|
+
// src/address/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* The IP addresses and the networks of netdiag: the family and the class of an address, CIDR ranges and sets of ranges.
|
|
6
|
+
*
|
|
7
|
+
* This entry uses only the module `node:net`. An IPv4 address in IPv6 form (`::ffff:192.0.2.1`) is its IPv4 address.
|
|
8
|
+
*/
|
|
9
|
+
/** The special-purpose ranges, as data. An address that is in no range is `public`. */
|
|
10
|
+
const ADDRESS_CLASSES = [
|
|
11
|
+
{
|
|
12
|
+
cidr: "0.0.0.0/8",
|
|
13
|
+
class: "unspecified"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
cidr: "10.0.0.0/8",
|
|
17
|
+
class: "private"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
cidr: "100.64.0.0/10",
|
|
21
|
+
class: "shared"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
cidr: "127.0.0.0/8",
|
|
25
|
+
class: "loopback"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
cidr: "169.254.0.0/16",
|
|
29
|
+
class: "link-local"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
cidr: "172.16.0.0/12",
|
|
33
|
+
class: "private"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
cidr: "192.0.2.0/24",
|
|
37
|
+
class: "documentation"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
cidr: "192.168.0.0/16",
|
|
41
|
+
class: "private"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
cidr: "198.18.0.0/15",
|
|
45
|
+
class: "benchmarking"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
cidr: "198.51.100.0/24",
|
|
49
|
+
class: "documentation"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
cidr: "203.0.113.0/24",
|
|
53
|
+
class: "documentation"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
cidr: "224.0.0.0/4",
|
|
57
|
+
class: "multicast"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
cidr: "240.0.0.0/4",
|
|
61
|
+
class: "reserved"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
cidr: "::/128",
|
|
65
|
+
class: "unspecified"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
cidr: "::1/128",
|
|
69
|
+
class: "loopback"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
cidr: "2001:db8::/32",
|
|
73
|
+
class: "documentation"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
cidr: "fc00::/7",
|
|
77
|
+
class: "unique-local"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
cidr: "fe80::/10",
|
|
81
|
+
class: "link-local"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
cidr: "ff00::/8",
|
|
85
|
+
class: "multicast"
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
/** The classes of the addresses that the internet does not route to a host. */
|
|
89
|
+
const NOT_ROUTED = /* @__PURE__ */ new Set([
|
|
90
|
+
"unspecified",
|
|
91
|
+
"loopback",
|
|
92
|
+
"private",
|
|
93
|
+
"shared",
|
|
94
|
+
"link-local",
|
|
95
|
+
"unique-local"
|
|
96
|
+
]);
|
|
97
|
+
/** This function gives the canonical form of an address: no spaces, no brackets, lower case, and IPv4 for a mapped IPv4 address. */
|
|
98
|
+
function canonicalAddress(address) {
|
|
99
|
+
let text = address.trim().toLowerCase();
|
|
100
|
+
if (text.startsWith("[") && text.endsWith("]")) text = text.slice(1, -1);
|
|
101
|
+
return /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(text)?.[1] ?? text;
|
|
102
|
+
}
|
|
103
|
+
/** This function gives the family of an IP address, or `undefined` for a text that is not an IP address. */
|
|
104
|
+
function addressFamily(address) {
|
|
105
|
+
const family = isIP(canonicalAddress(address));
|
|
106
|
+
return family === 4 ? "ipv4" : family === 6 ? "ipv6" : void 0;
|
|
107
|
+
}
|
|
108
|
+
/** This function gives the CIDR range of a text, for example `192.0.2.0/24` or `2001:db8::/32`. An address alone is a range of one address. */
|
|
109
|
+
function parseCidr(text) {
|
|
110
|
+
const slash = text.indexOf("/");
|
|
111
|
+
const address = canonicalAddress(slash === -1 ? text : text.slice(0, slash));
|
|
112
|
+
const family = addressFamily(address);
|
|
113
|
+
if (family === void 0) return err(`"${text}" is not an IP address or a CIDR range.`);
|
|
114
|
+
const max = family === "ipv4" ? 32 : 128;
|
|
115
|
+
const prefix = slash === -1 ? String(max) : text.slice(slash + 1).trim();
|
|
116
|
+
if (!/^\d{1,3}$/.test(prefix) || Number(prefix) > max) return err(`"${text}" has no prefix from 0 to ${max}.`);
|
|
117
|
+
return ok({
|
|
118
|
+
address,
|
|
119
|
+
prefix: Number(prefix),
|
|
120
|
+
family
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** This function gives the text of a CIDR range. */
|
|
124
|
+
function formatCidr(cidr) {
|
|
125
|
+
return `${cidr.address}/${cidr.prefix}`;
|
|
126
|
+
}
|
|
127
|
+
/** This function gives the set of some CIDR ranges. */
|
|
128
|
+
function cidrSet(ranges) {
|
|
129
|
+
const list = new BlockList();
|
|
130
|
+
for (const range of ranges) list.addSubnet(range.address, range.prefix, range.family);
|
|
131
|
+
return {
|
|
132
|
+
ranges: [...ranges],
|
|
133
|
+
has(address) {
|
|
134
|
+
const host = canonicalAddress(address);
|
|
135
|
+
const family = addressFamily(host);
|
|
136
|
+
return family !== void 0 && list.check(host, family);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** This function tells if an address is in a CIDR range. */
|
|
141
|
+
function cidrContains(cidr, address) {
|
|
142
|
+
return cidrSet([cidr]).has(address);
|
|
143
|
+
}
|
|
144
|
+
let classSets;
|
|
145
|
+
function classes() {
|
|
146
|
+
classSets ??= ADDRESS_CLASSES.map((entry) => {
|
|
147
|
+
const range = parseCidr(entry.cidr);
|
|
148
|
+
if (range.tag === "err") throw new Error(range.error);
|
|
149
|
+
return {
|
|
150
|
+
class: entry.class,
|
|
151
|
+
set: cidrSet([range.value])
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
return classSets;
|
|
155
|
+
}
|
|
156
|
+
/** This function gives the class of an IP address, or `undefined` for a text that is not an IP address. */
|
|
157
|
+
function addressClass(address) {
|
|
158
|
+
if (addressFamily(address) === void 0) return void 0;
|
|
159
|
+
return classes().find((entry) => entry.set.has(address))?.class ?? "public";
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* This function tells if the internet routes to an address. A `private` address is unspecified, loopback, private,
|
|
163
|
+
* shared, link-local or unique-local. Each other address is `public`, also an address for documentation.
|
|
164
|
+
*/
|
|
165
|
+
function addressKind(address) {
|
|
166
|
+
const found = addressClass(address);
|
|
167
|
+
if (found === void 0) return void 0;
|
|
168
|
+
return NOT_ROUTED.has(found) ? "private" : "public";
|
|
169
|
+
}
|
|
170
|
+
export { ADDRESS_CLASSES, addressClass, addressFamily, addressKind, canonicalAddress, cidrContains, cidrSet, formatCidr, parseCidr };
|
|
171
|
+
|
|
172
|
+
//# sourceMappingURL=address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.js","names":[],"sources":["../src/address/index.ts"],"sourcesContent":["/**\n * The IP addresses and the networks of netdiag: the family and the class of an address, CIDR ranges and sets of ranges.\n *\n * This entry uses only the module `node:net`. An IPv4 address in IPv6 form (`::ffff:192.0.2.1`) is its IPv4 address.\n */\nimport { BlockList, isIP } from \"node:net\";\nimport { err, ok, type Result } from \"@mark1russell7/optional\";\n\n/** The family of an IP address. */\nexport type AddressFamily = \"ipv4\" | \"ipv6\";\n\n/**\n * The class of an IP address, from the special-purpose ranges of RFC 6890.\n *\n * - `unspecified`: `0.0.0.0/8` and `::`.\n * - `loopback`: `127.0.0.0/8` and `::1`.\n * - `private`: the ranges of RFC 1918.\n * - `shared`: `100.64.0.0/10`, the carrier-grade NAT range of RFC 6598.\n * - `link-local`: `169.254.0.0/16` and `fe80::/10`.\n * - `unique-local`: `fc00::/7`, the private range of IPv6 (RFC 4193).\n * - `documentation`: the ranges of RFC 5737 and RFC 3849 for examples.\n * - `benchmarking`: `198.18.0.0/15` (RFC 2544).\n * - `multicast`: `224.0.0.0/4` and `ff00::/8`.\n * - `reserved`: `240.0.0.0/4`, with the broadcast address.\n * - `public`: each other address.\n */\nexport type AddressClass =\n | \"unspecified\"\n | \"loopback\"\n | \"private\"\n | \"shared\"\n | \"link-local\"\n | \"unique-local\"\n | \"documentation\"\n | \"benchmarking\"\n | \"multicast\"\n | \"reserved\"\n | \"public\";\n\n/** A CIDR range: an address of the network and the length of its prefix. */\nexport interface Cidr {\n readonly address: string;\n readonly prefix: number;\n readonly family: AddressFamily;\n}\n\n/** The special-purpose ranges, as data. An address that is in no range is `public`. */\nexport const ADDRESS_CLASSES: readonly { readonly cidr: string; readonly class: Exclude<AddressClass, \"public\"> }[] = [\n { cidr: \"0.0.0.0/8\", class: \"unspecified\" },\n { cidr: \"10.0.0.0/8\", class: \"private\" },\n { cidr: \"100.64.0.0/10\", class: \"shared\" },\n { cidr: \"127.0.0.0/8\", class: \"loopback\" },\n { cidr: \"169.254.0.0/16\", class: \"link-local\" },\n { cidr: \"172.16.0.0/12\", class: \"private\" },\n { cidr: \"192.0.2.0/24\", class: \"documentation\" },\n { cidr: \"192.168.0.0/16\", class: \"private\" },\n { cidr: \"198.18.0.0/15\", class: \"benchmarking\" },\n { cidr: \"198.51.100.0/24\", class: \"documentation\" },\n { cidr: \"203.0.113.0/24\", class: \"documentation\" },\n { cidr: \"224.0.0.0/4\", class: \"multicast\" },\n { cidr: \"240.0.0.0/4\", class: \"reserved\" },\n { cidr: \"::/128\", class: \"unspecified\" },\n { cidr: \"::1/128\", class: \"loopback\" },\n { cidr: \"2001:db8::/32\", class: \"documentation\" },\n { cidr: \"fc00::/7\", class: \"unique-local\" },\n { cidr: \"fe80::/10\", class: \"link-local\" },\n { cidr: \"ff00::/8\", class: \"multicast\" },\n];\n\n/** The classes of the addresses that the internet does not route to a host. */\nconst NOT_ROUTED: ReadonlySet<AddressClass> = new Set([\"unspecified\", \"loopback\", \"private\", \"shared\", \"link-local\", \"unique-local\"]);\n\n/** This function gives the canonical form of an address: no spaces, no brackets, lower case, and IPv4 for a mapped IPv4 address. */\nexport function canonicalAddress(address: string): string {\n let text = address.trim().toLowerCase();\n if (text.startsWith(\"[\") && text.endsWith(\"]\")) text = text.slice(1, -1);\n const mapped = /^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/.exec(text)?.[1];\n return mapped ?? text;\n}\n\n/** This function gives the family of an IP address, or `undefined` for a text that is not an IP address. */\nexport function addressFamily(address: string): AddressFamily | undefined {\n const family = isIP(canonicalAddress(address));\n return family === 4 ? \"ipv4\" : family === 6 ? \"ipv6\" : undefined;\n}\n\n/** This function gives the CIDR range of a text, for example `192.0.2.0/24` or `2001:db8::/32`. An address alone is a range of one address. */\nexport function parseCidr(text: string): Result<Cidr, string> {\n const slash = text.indexOf(\"/\");\n const address = canonicalAddress(slash === -1 ? text : text.slice(0, slash));\n const family = addressFamily(address);\n if (family === undefined) return err(`\"${text}\" is not an IP address or a CIDR range.`);\n const max = family === \"ipv4\" ? 32 : 128;\n const prefix = slash === -1 ? String(max) : text.slice(slash + 1).trim();\n if (!/^\\d{1,3}$/.test(prefix) || Number(prefix) > max) return err(`\"${text}\" has no prefix from 0 to ${max}.`);\n return ok({ address, prefix: Number(prefix), family });\n}\n\n/** This function gives the text of a CIDR range. */\nexport function formatCidr(cidr: Cidr): string {\n return `${cidr.address}/${cidr.prefix}`;\n}\n\n/** A set of CIDR ranges. */\nexport interface CidrSet {\n readonly ranges: readonly Cidr[];\n /** This function tells if an address is in a range of the set. A text that is not an IP address is in no set. */\n has(address: string): boolean;\n}\n\n/** This function gives the set of some CIDR ranges. */\nexport function cidrSet(ranges: readonly Cidr[]): CidrSet {\n const list = new BlockList();\n for (const range of ranges) list.addSubnet(range.address, range.prefix, range.family);\n return {\n ranges: [...ranges],\n has(address) {\n const host = canonicalAddress(address);\n const family = addressFamily(host);\n return family !== undefined && list.check(host, family);\n },\n };\n}\n\n/** This function tells if an address is in a CIDR range. */\nexport function cidrContains(cidr: Cidr, address: string): boolean {\n return cidrSet([cidr]).has(address);\n}\n\nlet classSets: readonly { readonly class: Exclude<AddressClass, \"public\">; readonly set: CidrSet }[] | undefined;\n\nfunction classes(): NonNullable<typeof classSets> {\n classSets ??= ADDRESS_CLASSES.map((entry) => {\n const range = parseCidr(entry.cidr);\n if (range.tag === \"err\") throw new Error(range.error);\n return { class: entry.class, set: cidrSet([range.value]) };\n });\n return classSets;\n}\n\n/** This function gives the class of an IP address, or `undefined` for a text that is not an IP address. */\nexport function addressClass(address: string): AddressClass | undefined {\n if (addressFamily(address) === undefined) return undefined;\n return classes().find((entry) => entry.set.has(address))?.class ?? \"public\";\n}\n\n/**\n * This function tells if the internet routes to an address. A `private` address is unspecified, loopback, private,\n * shared, link-local or unique-local. Each other address is `public`, also an address for documentation.\n */\nexport function addressKind(address: string): \"public\" | \"private\" | undefined {\n const found = addressClass(address);\n if (found === undefined) return undefined;\n return NOT_ROUTED.has(found) ? \"private\" : \"public\";\n}\n"],"mappings":";;;;;;;;;AA+CA,MAAa,kBAAyG;CACpH;EAAE,MAAM;EAAa,OAAO;CAAc;CAC1C;EAAE,MAAM;EAAc,OAAO;CAAU;CACvC;EAAE,MAAM;EAAiB,OAAO;CAAS;CACzC;EAAE,MAAM;EAAe,OAAO;CAAW;CACzC;EAAE,MAAM;EAAkB,OAAO;CAAa;CAC9C;EAAE,MAAM;EAAiB,OAAO;CAAU;CAC1C;EAAE,MAAM;EAAgB,OAAO;CAAgB;CAC/C;EAAE,MAAM;EAAkB,OAAO;CAAU;CAC3C;EAAE,MAAM;EAAiB,OAAO;CAAe;CAC/C;EAAE,MAAM;EAAmB,OAAO;CAAgB;CAClD;EAAE,MAAM;EAAkB,OAAO;CAAgB;CACjD;EAAE,MAAM;EAAe,OAAO;CAAY;CAC1C;EAAE,MAAM;EAAe,OAAO;CAAW;CACzC;EAAE,MAAM;EAAU,OAAO;CAAc;CACvC;EAAE,MAAM;EAAW,OAAO;CAAW;CACrC;EAAE,MAAM;EAAiB,OAAO;CAAgB;CAChD;EAAE,MAAM;EAAY,OAAO;CAAe;CAC1C;EAAE,MAAM;EAAa,OAAO;CAAa;CACzC;EAAE,MAAM;EAAY,OAAO;CAAY;AACzC;;AAGA,MAAM,6BAAwC,IAAI,IAAI;CAAC;CAAe;CAAY;CAAW;CAAU;CAAc;AAAc,CAAC;;AAGpI,SAAgB,iBAAiB,SAAyB;CACxD,IAAI,OAAO,QAAQ,KAAK,CAAC,CAAC,YAAY;CACtC,IAAI,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,GAAG,OAAO,KAAK,MAAM,GAAG,EAAE;CAEvE,OADe,gCAAgC,KAAK,IAAI,CAAC,GAAG,MAC3C;AACnB;;AAGA,SAAgB,cAAc,SAA4C;CACxE,MAAM,SAAS,KAAK,iBAAiB,OAAO,CAAC;CAC7C,OAAO,WAAW,IAAI,SAAS,WAAW,IAAI,SAAS,KAAA;AACzD;;AAGA,SAAgB,UAAU,MAAoC;CAC5D,MAAM,QAAQ,KAAK,QAAQ,GAAG;CAC9B,MAAM,UAAU,iBAAiB,UAAU,KAAK,OAAO,KAAK,MAAM,GAAG,KAAK,CAAC;CAC3E,MAAM,SAAS,cAAc,OAAO;CACpC,IAAI,WAAW,KAAA,GAAW,OAAO,IAAI,IAAI,KAAK,wCAAwC;CACtF,MAAM,MAAM,WAAW,SAAS,KAAK;CACrC,MAAM,SAAS,UAAU,KAAK,OAAO,GAAG,IAAI,KAAK,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK;CACvE,IAAI,CAAC,YAAY,KAAK,MAAM,KAAK,OAAO,MAAM,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,4BAA4B,IAAI,EAAE;CAC7G,OAAO,GAAG;EAAE;EAAS,QAAQ,OAAO,MAAM;EAAG;CAAO,CAAC;AACvD;;AAGA,SAAgB,WAAW,MAAoB;CAC7C,OAAO,GAAG,KAAK,QAAQ,GAAG,KAAK;AACjC;;AAUA,SAAgB,QAAQ,QAAkC;CACxD,MAAM,OAAO,IAAI,UAAU;CAC3B,KAAK,MAAM,SAAS,QAAQ,KAAK,UAAU,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM;CACpF,OAAO;EACL,QAAQ,CAAC,GAAG,MAAM;EAClB,IAAI,SAAS;GACX,MAAM,OAAO,iBAAiB,OAAO;GACrC,MAAM,SAAS,cAAc,IAAI;GACjC,OAAO,WAAW,KAAA,KAAa,KAAK,MAAM,MAAM,MAAM;EACxD;CACF;AACF;;AAGA,SAAgB,aAAa,MAAY,SAA0B;CACjE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO;AACpC;AAEA,IAAI;AAEJ,SAAS,UAAyC;CAChD,cAAc,gBAAgB,KAAK,UAAU;EAC3C,MAAM,QAAQ,UAAU,MAAM,IAAI;EAClC,IAAI,MAAM,QAAQ,OAAO,MAAM,IAAI,MAAM,MAAM,KAAK;EACpD,OAAO;GAAE,OAAO,MAAM;GAAO,KAAK,QAAQ,CAAC,MAAM,KAAK,CAAC;EAAE;CAC3D,CAAC;CACD,OAAO;AACT;;AAGA,SAAgB,aAAa,SAA2C;CACtE,IAAI,cAAc,OAAO,MAAM,KAAA,GAAW,OAAO,KAAA;CACjD,OAAO,QAAQ,CAAC,CAAC,MAAM,UAAU,MAAM,IAAI,IAAI,OAAO,CAAC,CAAC,EAAE,SAAS;AACrE;;;;;AAMA,SAAgB,YAAY,SAAmD;CAC7E,MAAM,QAAQ,aAAa,OAAO;CAClC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,OAAO,WAAW,IAAI,KAAK,IAAI,YAAY;AAC7C"}
|