diff --git a/.gitattributes b/.gitattributes index 027012e27..b945ad625 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,4 +17,11 @@ kernel/config-microvm-debug text eol=lf # Linux hosts read the setup tool manifest, including copies staged from Windows. scripts/setup/tool-versions.conf text eol=lf -.github/workflows/*.lock.yml linguist-generated=true \ No newline at end of file +.github/workflows/*.lock.yml linguist-generated=true +# Generated from Python by scripts/nvx_rs_fixtures.py. +nvx-rs/src/host/python/unicode.rs linguist-generated=true +nvx-rs/tests/fixtures/cli.json linguist-generated=true +nvx-rs/tests/fixtures/egress-policy.json linguist-generated=true +nvx-rs/tests/fixtures/python.json linguist-generated=true +nvx-rs/tests/fixtures/sandbox.json linguist-generated=true +nvx-rs/tests/fixtures/sandbox_options.json linguist-generated=true \ No newline at end of file diff --git a/.github/actions/build-guest-artifacts/action.yml b/.github/actions/build-guest-artifacts/action.yml index c90dfeba2..1fb6039f2 100644 --- a/.github/actions/build-guest-artifacts/action.yml +++ b/.github/actions/build-guest-artifacts/action.yml @@ -73,21 +73,23 @@ runs: AZURELINUX_VERSION: ${{ steps.guest-versions.outputs.azurelinux-version }} KERNEL_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'kernel/config-microvm', 'kernel/patches/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py') }} DEBUG_KERNEL_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'kernel/config-microvm', 'kernel/config-microvm-debug', 'kernel/patches/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py') }} - ALPINE_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/alpine/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/guests.py') }} - UBUNTU_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/ubuntu/**', 'ubuntu/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/guests.py', 'scripts/nvx_tools/ubuntu.py') }} - AZURELINUX_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'azurelinux/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/azurelinux.py', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/guests.py') }} + ALPINE_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/alpine/**', 'nvx-rs/**', '!nvx-rs/target/**', 'rust-toolchain.toml', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py') }} + UBUNTU_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/ubuntu/**', 'ubuntu/**', 'nvx-rs/**', '!nvx-rs/target/**', 'rust-toolchain.toml', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py', 'scripts/nvx_tools/ubuntu.py') }} + AZURELINUX_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'azurelinux/**', 'nvx-rs/**', '!nvx-rs/target/**', 'rust-toolchain.toml', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/azurelinux.py', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py') }} run: | set -euo pipefail # hashFiles hashes contents only, so the kernel keys also name each patch - # path and its order. + # path and its order, and the guest image keys the path-aware digest of + # the Rust guest sources that their package manifests record. patch_identity=$(python3 scripts/nvx.py kernel-patch-identity) + rust_source_identity=$(python3 scripts/nvx.py guest-binary-source-identity) { echo "kernel=linux-kernel-v1-${KERNEL_VERSION}-${KERNEL_INPUT_HASH}-${patch_identity}" echo "kernel-debug=linux-kernel-debug-v1-${KERNEL_VERSION}-${DEBUG_KERNEL_INPUT_HASH}-${patch_identity}" - echo "alpine=alpine-image-v1-${ALPINE_VERSION}-${ALPINE_INPUT_HASH}" - echo "ubuntu=ubuntu-initramfs-v1-${UBUNTU_VERSION}-${UBUNTU_INPUT_HASH}" - echo "ubuntu-distro=ubuntu-distro-v1-${UBUNTU_VERSION}-${UBUNTU_INPUT_HASH}" - echo "azurelinux=azurelinux-initramfs-v1-${AZURELINUX_VERSION}-${AZURELINUX_INPUT_HASH}" + echo "alpine=alpine-image-v2-${ALPINE_VERSION}-${ALPINE_INPUT_HASH}-${rust_source_identity}" + echo "ubuntu=ubuntu-initramfs-v2-${UBUNTU_VERSION}-${UBUNTU_INPUT_HASH}-${rust_source_identity}" + echo "ubuntu-distro=ubuntu-distro-v2-${UBUNTU_VERSION}-${UBUNTU_INPUT_HASH}-${rust_source_identity}" + echo "azurelinux=azurelinux-initramfs-v2-${AZURELINUX_VERSION}-${AZURELINUX_INPUT_HASH}-${rust_source_identity}" } >> "${GITHUB_OUTPUT}" - name: Restore Linux kernel (zstd) diff --git a/.github/actions/validate-nvx/action.yml b/.github/actions/validate-nvx/action.yml index 68ad94130..a3cd1b88d 100644 --- a/.github/actions/validate-nvx/action.yml +++ b/.github/actions/validate-nvx/action.yml @@ -10,7 +10,7 @@ runs: run: | set -euo pipefail python3 -m compileall -q scripts - python3 -m unittest scripts/test_performance.py scripts/test_nvx_tools.py scripts/test_guest_binaries.py scripts/test_microvm_tests.py scripts/test_development_release.py scripts/test_egress_policy.py scripts/test_control_session.py scripts/test_managed_agent.py scripts/test_guest_time.py scripts/test_guest_ingress.py scripts/test_host_telemetry.py scripts/test_time_abi.py scripts/test_tool_versions.py scripts/test_markdown_links.py -v + python3 -m unittest scripts/test_performance.py scripts/test_nvx_tools.py scripts/test_guest_binaries.py scripts/test_microvm_tests.py scripts/test_development_release.py scripts/test_egress_policy.py scripts/test_control_session.py scripts/test_host_telemetry.py scripts/test_time_abi.py scripts/test_tool_versions.py scripts/test_markdown_links.py -v python3 scripts/test_adversarial.py -v python3 .github/skills/nvx-host-connect/scripts/test_hosts.py -v python3 scripts/nvx.py --help @@ -30,7 +30,7 @@ runs: $ErrorActionPreference = "Stop" python -m compileall -q scripts if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - python -m unittest scripts/test_performance.py scripts/test_nvx_tools.py scripts/test_guest_binaries.py scripts/test_microvm_tests.py scripts/test_development_release.py scripts/test_egress_policy.py scripts/test_control_session.py scripts/test_managed_agent.py scripts/test_guest_time.py scripts/test_guest_ingress.py scripts/test_host_telemetry.py scripts/test_time_abi.py scripts/test_tool_versions.py scripts/test_markdown_links.py -v + python -m unittest scripts/test_performance.py scripts/test_nvx_tools.py scripts/test_guest_binaries.py scripts/test_microvm_tests.py scripts/test_development_release.py scripts/test_egress_policy.py scripts/test_control_session.py scripts/test_host_telemetry.py scripts/test_time_abi.py scripts/test_tool_versions.py scripts/test_markdown_links.py -v if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } python scripts\test_adversarial.py -v if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c45fdd946..7b60f5fa1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -50,9 +50,10 @@ [copilot-setup-steps](./workflows/copilot-setup-steps.yml) prepares. It initializes `openvmm/`; installs `requirements-dev.txt` into a Python virtual environment on `PATH`, the Rust release that `rust-toolchain.toml` pins with - the OpenVMM test targets, the minimum supported Rust versions of the - `aci_edge_sandboxes` and `nvx-rs` crates, cargo-nextest, and the native guest build - prerequisites; grants `/dev/kvm` access; pulls the pinned shell linter images; + the OpenVMM test targets and the `x86_64-unknown-linux-musl` target used by + the Rust guest binaries, the minimum supported Rust versions of the + `aci_edge_sandboxes` and `nvx-rs` crates, cargo-nextest, and the remaining + native guest build prerequisites; grants `/dev/kvm` access; pulls the pinned shell linter images; and stages the pinned Linux and Ubuntu Base archives in `.cache/downloads`. When CI has cached them for the checked-out inputs, it also restores the guest artifacts into `build/` and the KVM OpenVMM binary into diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index d5962e5da..3ff091f8a 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -22,7 +22,7 @@ on: - .gitmodules - SOURCE-MANIFEST.json - aci_edge_sandboxes/Cargo.toml - - nvx-rs/Cargo.toml + - nvx-rs/** - openvmm - requirements-dev.txt - rust-toolchain.toml diff --git a/README.md b/README.md index 459cfc18a..d725e482c 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,11 @@ NVX was jointly developed by the [MSR Systems Research Group][msr-systems] and [msr-systems]: https://www.microsoft.com/en-us/research/group/systems-research-group-redmond/ [azure-systems]: https://www.microsoft.com/en-us/research/group/azure-research-systems/ -This repository includes version pins for Linux and OpenVMM, along with the patches, guest source -files, build tools, and benchmarks needed to use NVX. +This repository includes version pins for Linux, OpenVMM, and the Rust guest +toolchain, along with the patches, guest source files, build tools, and +benchmarks needed to use NVX. Alpine, Ubuntu, and Azure Linux ship the nine +static guest binaries from [`nvx-rs`](nvx-rs/README.md#guest-binaries) at +their common `/sbin/nvx-*` paths. ## Quick Start diff --git a/SOURCE-MANIFEST.json b/SOURCE-MANIFEST.json index a15f52395..86cb8a7d7 100644 --- a/SOURCE-MANIFEST.json +++ b/SOURCE-MANIFEST.json @@ -9,6 +9,10 @@ "control_session_protocol_version": 1, "control_contract_revision": "nvx-microvm-v2-control-v2" }, + "guest_rust": { + "toolchain": "1.95.0", + "target": "x86_64-unknown-linux-musl" + }, "linux": { "version": "6.18.38", "upstream_url": "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.38.tar.xz", @@ -31,7 +35,9 @@ "minirootfs_sha256": "41f73e3cf5fa919b8aa5ca6b30dc48f0da2720776d7423e2a7748211456fe081", "guest_sources": [ "guest/common", - "guest/alpine" + "guest/alpine", + "nvx-rs", + "rust-toolchain.toml" ], "package_manifests": [ "build/initramfs.cpio.gz.packages.json" @@ -52,7 +58,9 @@ "guest_sources": [ "guest/common", "guest/ubuntu", - "ubuntu/packages.lock.json" + "ubuntu/packages.lock.json", + "nvx-rs", + "rust-toolchain.toml" ], "package_manifests": [ "build/initramfs-ubuntu.cpio.gz.packages.json", @@ -70,7 +78,9 @@ "package_lock_sha256": "f2014a858f46482778f138b374f80c36df389206ae677870d58a7e919bec182b", "guest_sources": [ "guest/common", - "azurelinux/packages.lock.json" + "azurelinux/packages.lock.json", + "nvx-rs", + "rust-toolchain.toml" ], "package_manifests": [ "build/initramfs-azurelinux.cpio.gz.packages.json" diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 2990dad39..43e15f921 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -8,8 +8,16 @@ the distribution tooling. | --- | --- | --- | | Linux 6.18.38 | Generated under `.cache/linux` | GPL-2.0-only; the upstream archive and patch series are pinned in `SOURCE-MANIFEST.json` and `kernel/patches`. Each build exports its exact config, and `collect-sources` emits the complete patched tree. | | OpenVMM | `openvmm` | MIT; see `openvmm/LICENSE`. Source is a private Git submodule and is available only to authorized users. | -| NVX guest/build sources | `guest`, `ubuntu`, `kernel`, `scripts/nvx_tools` | MIT; see `LICENSE`. | -| musl libc (Debian 12 `musl`) | statically linked into `/sbin/nvx-time`, `/sbin/nvx-time-probe`, and `/sbin/nvx-exit` in generated initramfs images | MIT; Copyright © 2005-2020 Rich Felker, et al. The build image's `/usr/share/doc/musl/copyright` carries the full notice, and the source is Debian's `musl` source package. | +| NVX guest/build sources | `guest`, `nvx-rs`, `ubuntu`, `kernel`, `scripts/nvx_tools` | MIT; see `LICENSE`. | +| Rust core and standard libraries | linked into all nine Rust guest binaries listed in the musl row | MIT OR Apache-2.0; the exact release is pinned by `rust-toolchain.toml`. Source and license files are under [`rust-lang/rust/library`](https://github.com/rust-lang/rust/tree/master/library), with `LICENSE-MIT` and `LICENSE-APACHE` at the repository root. | +| Rust `compiler_builtins` | Rust target runtime routines used by the guest binaries | MIT OR Apache-2.0 for `rust-lang/compiler-builtins`; routines derived from LLVM compiler-rt are Apache-2.0 WITH LLVM-exception. Source is the revision bundled by the pinned Rust release under [`rust-lang/compiler-builtins`](https://github.com/rust-lang/compiler-builtins) and its [`llvm-project/compiler-rt`](https://github.com/llvm/llvm-project/tree/main/compiler-rt) sources. | +| Rust `libc` crate 0.2.189 | linked into the guest binaries through `nix` and `getrandom` | MIT OR Apache-2.0; the exact crate checksum is pinned in `nvx-rs/Cargo.lock`, and source and license files are at [`rust-lang/libc` tag 0.2.189](https://github.com/rust-lang/libc/tree/0.2.189). | +| Rust `nix` crate 0.31.3 | system-call wrappers used by the guest binaries | MIT; the exact crate checksum is pinned in `nvx-rs/Cargo.lock`, and the source is published by [`nix-rust/nix`](https://crates.io/crates/nix/0.31.3). | +| Rust `getrandom` crate 0.4.3 | random bytes for the guest binaries, such as `nvx-reseed --sample` | MIT OR Apache-2.0; the exact crate checksum is pinned in `nvx-rs/Cargo.lock`, and the source is published by [`rust-random/getrandom`](https://crates.io/crates/getrandom/0.4.3). | +| Rust `bitflags` crate 2.13.2 | transitive `nix` dependency in the guest binaries | MIT OR Apache-2.0; the exact crate checksum is pinned in `nvx-rs/Cargo.lock`, and the source is published by [`bitflags/bitflags`](https://crates.io/crates/bitflags/2.13.2). | +| Rust `cfg-if` crate 1.0.5 | transitive `nix` and `getrandom` dependency in the guest binaries | MIT OR Apache-2.0; the exact crate checksum is pinned in `nvx-rs/Cargo.lock`, and the source is published by [`rust-lang/cfg-if`](https://crates.io/crates/cfg-if/1.0.5). | +| Rust `memoffset` crate 0.9.1 | transitive `nix` dependency in the guest binaries | MIT; the exact crate checksum is pinned in `nvx-rs/Cargo.lock`, and the source is published by [`Gilnaa/memoffset`](https://crates.io/crates/memoffset/0.9.1). | +| musl libc | statically linked into `/sbin/nvx-console-pending`, `/sbin/nvx-mmio-write`, `/sbin/nvx-reseed`, `/sbin/nvx-exit`, `/sbin/nvx-device-io`, `/sbin/nvx-managed-agent`, `/sbin/nvx-ingress`, `/sbin/nvx-time`, and `/sbin/nvx-time-probe` in every generated initramfs | MIT; Copyright © 2005-2020 Rich Felker, et al. The exact implementation is supplied by the pinned Rust release's `x86_64-unknown-linux-musl` target. Upstream source and the full copyright notice are at [musl libc](https://musl.libc.org/). | | Alpine Linux packages | generated initramfs images | Per-package licenses. Every build emits `*.packages.json`; retain that manifest and collect the corresponding aports recipes and upstream sources before distributing an image. | | Azure Linux 3.0 packages | generated Azure Linux initramfs | Per-package licenses, including GPLv2 BusyBox. The base image is digest-pinned in `SOURCE-MANIFEST.json`, every supplemental RPM is SHA-256 pinned in `azurelinux/packages.lock.json`, and every build emits `initramfs-azurelinux.cpio.gz.packages.json`. `collect-sources` does not collect this corresponding source yet, so `--include-source` packages omit the Azure Linux initramfs; publish the matching source before distributing a binary-only package that contains it. | | Ubuntu Base 26.04.1 LTS and supplemental packages | generated Ubuntu initramfs and EROFS images | Per-package licenses. The base archive and every supplemental `.deb` are SHA-256 pinned. Retain the generated Ubuntu manifests and publish the matching `.dsc` plus all referenced source members before distributing an image. Ubuntu artifacts contain Ubuntu userland with the NVX kernel, not the Ubuntu kernel. | diff --git a/aci_edge_sandboxes/README.md b/aci_edge_sandboxes/README.md index fcc2cc1ff..9ce05ce14 100644 --- a/aci_edge_sandboxes/README.md +++ b/aci_edge_sandboxes/README.md @@ -866,10 +866,19 @@ through `endPort`, an `icmp` entry matches ICMP alone, or ICMPv6 for IPv6 networks, and `any` matches every protocol or, with a port or a port range, TCP and UDP on those ports. -Rules are expanded into OpenVMM rules exactly: exceptions are subtracted from -their networks, a port range becomes one OpenVMM range rule for each network, -and protocol `any` with a port or a port range becomes a TCP and a UDP rule. -Rules that would expand to more than 256 OpenVMM rules are rejected. Egress +Rules are lowered into OpenVMM rules exactly, by the lowering that compiles +NVX's egress policy files +([`nvx::host::network::policy`](../nvx-rs/src/host/network/policy/mod.rs)): +exceptions are subtracted from their networks, protocol `any` with a port or a +port range becomes a TCP and a UDP rule, the destinations of each action merge +into the fewest networks that cover them, a rule for every port of a protocol, +or for every protocol, subsumes the narrower rules that it covers, and +overlapping and adjacent port ranges of a network merge into one range rule. +The rules of each action are passed in order of family, address, prefix length, +protocol, and ports, and an action that still needs more than 256 OpenVMM rules +is rejected. Networks are read as policy files read them, so host bits are +cleared and IPv4 netmasks such as `/255.255.0.0` are accepted, but request +validation still refuses both before the backend sees them. Egress denied without allow rules, with ingress and host loopback denied, attaches no network device; otherwise the guest gets `10.0.0.2/24` behind the profile's NAT gateway @@ -1073,6 +1082,49 @@ affect Rust callers and builds: - A relative `process.cwd` is still rejected with `policy_validation`, and the message now points at `nvx::guest_path`. +The OpenVMM backend lowers egress rules with the lowering that compiles NVX's +egress policy files +([`nvx::host::network::policy`](../nvx-rs/src/host/network/policy/mod.rs)), +instead of expanding each rule on its own. What the rules match is unchanged, +and so are the error codes, but these change: + +- **Rule order.** The `--network-egress-allow` and `--network-egress-deny` + values follow family, address, prefix length, protocol, and ports, instead of + string order: `::/0` precedes `2001:db8::/32`, and `tcp:80` precedes + `tcp:443`. OpenVMM's matching does not depend on the order. +- **Rule count.** Each action's destinations merge into the fewest networks, a + rule for every port of a protocol, or for every protocol, subsumes the + narrower rules that it covers, and overlapping and adjacent port ranges of a + network merge. Policies usually need fewer OpenVMM rules, so some that + exceeded the limit of 256 are now accepted: 256 adjacent `/24` destinations + become one `/16` rule. Port ranges that overlap only partly across networks + can need a rule for their union, so a policy near the limit can also need + more rules than before. +- **Messages.** The backend's own rule checks, which `Backend` callers reach + without the request validation of `AciEdgeSandbox`, report the policy + compiler's messages, which name the field: + - `network.egress.allow[0].ports[0]: ICMP has no ports` became + `network.egress.allow[0].ports[0].port is not supported with icmp`; + - `....endPort must not be below port` became `....endPort 80 cannot be + below port 90`, and ports outside 1 through 65535 report `....port must be + between 1 and 65535`; + - `...to[0]: the destination is not a network` and `...to[0].except: an + exception is not a network within the destination` became + `...to[0].cidr is not a valid IPv4 or IPv6 CIDR: VALUE`, + `...to[0].cidr must not name an IPv6 scope: VALUE`, and + `...to[0].except[1] must be contained in NETWORK`; and + - `network.egress.allow expands to 300 OpenVMM rules; the openvmm backend + accepts at most 256`, and the rule-level `expands to more than 256 OpenVMM + rules` and `covers more than 256 networks after removing its exceptions`, + became `network.egress.allow emits at most 256 native rules`. This is the + one message that requests through `AciEdgeSandbox` can also reach. +- **Accepted rules.** Without request validation, the backend now reads a + network as policy files do: host bits are cleared and IPv4 netmasks are + accepted, so `10.0.0.7/24` is `10.0.0.0/24`, while port 0, which the backend + passed to OpenVMM before, is refused. Request validation still refuses such + networks with `malformed_request` first. A deny rule that covers the guest + network of host-loopback forwards is detected after the same normalization. + ## Limitations Live standard input requires guest control-protocol work and is rejected today. diff --git a/aci_edge_sandboxes/src/openvmm/mod.rs b/aci_edge_sandboxes/src/openvmm/mod.rs index eefd01b36..0bc983c8c 100644 --- a/aci_edge_sandboxes/src/openvmm/mod.rs +++ b/aci_edge_sandboxes/src/openvmm/mod.rs @@ -58,9 +58,10 @@ //! or read-write. OpenVMM enforces each mapping's access on the host, except that of a read-only //! path inside a read-write mapping, and hides denied paths and everything that is not mapped //! beside mapped files. A whole volume, or a file directly in a volume's root, is not mapped. -//! Egress rules are expanded -//! into OpenVMM's IPv4 and IPv6 rules exactly; rules that would need more than 256 OpenVMM rules -//! are rejected. Egress denied without allow rules, with ingress and host loopback denied, +//! Egress rules are lowered into OpenVMM's IPv4 and IPv6 rules exactly, by the lowering that +//! compiles NVX's egress policy files ([`nvx::host::network::policy`]): destinations and port +//! ranges merge into the fewest rules, and an action that still needs more than 256 OpenVMM rules +//! is rejected. Egress denied without allow rules, with ingress and host loopback denied, //! attaches no network device. With `network.ingress.default: allow`, OpenVMM listens on every //! host address at the TCP and UDP ports where the workload listens, and admits clients on the //! host's private networks, whose addresses are not globally reachable, but not clients inside @@ -924,7 +925,8 @@ mod tests { guest_port: 8080, }] ); - // Messages name the contract's fields. + // Messages name the contract's fields, as the shared lowering of NVX's egress policy + // files words them. let icmp_port = NetworkPolicy { egress: EgressPolicy::new(Access::Deny) .with_allow(NetworkRule::to("10.0.0.0/8").on_port(Protocol::Icmp, 8)), @@ -937,7 +939,43 @@ mod tests { assert_eq!(error.code(), ErrorCode::PolicyValidation); assert_eq!( error.message(), - "network.egress.allow[0].ports[0]: ICMP has no ports" + "network.egress.allow[0].ports[0].port is not supported with icmp" + ); + let scoped = NetworkPolicy { + egress: EgressPolicy::new(Access::Allow) + .with_deny(NetworkRule::to("192.0.2.0/24")) + .with_deny(NetworkRule::to("fe80::1%eth0/64")), + ..NetworkPolicy::deny_all() + }; + let error = Error::from( + network::openvmm_arguments(Some(&network_policy(&scoped, &[])), "10.0.0.2/24") + .unwrap_err(), + ); + assert_eq!(error.code(), ErrorCode::PolicyValidation); + assert_eq!( + error.message(), + "network.egress.deny[1].to[0].cidr must not name an IPv6 scope: fe80::1%eth0/64" + ); + let mut crowded = NetworkRule::to("192.0.2.0/24"); + crowded.ports = (0..257) + .map(|index| NetworkPort { + protocol: Protocol::Tcp, + port: Some(2 * index + 1), + end_port: None, + }) + .collect(); + let crowded = NetworkPolicy { + egress: EgressPolicy::new(Access::Deny).with_allow(crowded), + ..NetworkPolicy::deny_all() + }; + let error = Error::from( + network::openvmm_arguments(Some(&network_policy(&crowded, &[])), "10.0.0.2/24") + .unwrap_err(), + ); + assert_eq!(error.code(), ErrorCode::PolicyValidation); + assert_eq!( + error.message(), + "network.egress.allow emits at most 256 native rules" ); } diff --git a/doc/benchmarks.md b/doc/benchmarks.md index a7002fddd..d886326b3 100644 --- a/doc/benchmarks.md +++ b/doc/benchmarks.md @@ -214,8 +214,10 @@ MSHV, and WHP. They do not create TAP devices or require host firewall rules. Each workload directory includes `benchmark-metadata.json` with the platform, backend, ABI, processor count, host affinity set, memory sizes, artifact revisions, repository dirty state, warmups, measured run counts, and SHA-256 values for the VMM, kernel, initramfs, and coordinator. -Device metadata also records helper source and packaged-binary hashes. Collection rejects -mismatched lifecycle/workload metadata and duplicate topology rows. +Device metadata also records the aggregate Rust guest-source digest, pinned +toolchain and target, and the packaged `nvx-device-io` binary hash from the +initramfs manifest. Collection rejects mismatched lifecycle/workload metadata +and duplicate topology rows. Use a fixed affinity set containing one logical processor per physical core and at least `N+2` processors for an `N`-vCPU guest. The additional processors @@ -398,8 +400,9 @@ measures complete process wall time while host and guest exchange files through The dedicated suite always uses the canonical microVM and attaches the block backing object as the writable `scratch` role. There is no ABI selector or -legacy unroled `--virtio-blk` control. Every guest has 256 MiB RAM and runs the same static, dependency-free -x86-64 helper from the reproducible initramfs. Canonical runs execute five warmups followed by 30 +legacy unroled `--virtio-blk` control. Every guest has 256 MiB RAM and runs the +same static Rust x86-64 binary from the reproducible initramfs. +Canonical runs execute five warmups followed by 30 retained attempts for each device. Every operation window lasts ten seconds against a 512 MiB backing object. diff --git a/doc/build.md b/doc/build.md index ac30ffbca..8a94ce107 100644 --- a/doc/build.md +++ b/doc/build.md @@ -31,6 +31,22 @@ python3 scripts/nvx.py build-guest --native python3 scripts/nvx.py build-guest --native --guest ubuntu ``` +Both native image builds call the same guest-binary routine that Docker and +the `nvx-rs` CI job use. It can also be run directly: + +```bash +python3 scripts/nvx.py build-guest-binaries --output build/guest-binaries +``` + +The routine takes the exact toolchain from `rust-toolchain.toml`, the +`x86_64-unknown-linux-musl` target, and the `guest` profile and feature. It +uses `--locked`, a fresh target directory, disabled incremental compilation, +stable source and Cargo-home path remapping, and no linker build IDs. One +Cargo invocation builds all nine programs, and every result must pass +`check-static-elf`. The output's `manifest.json` binds the exact build pins, +one digest over the `nvx-rs` crate, the shared Python build recipe, and +`rust-toolchain.toml`, and each binary SHA-256. + The native build keeps the Linux archive, the Ubuntu Base archive, and the supplemental Ubuntu packages under `downloads/` in the cache directory, `.cache/` or `$NVX_CACHE_DIR`. It reuses a file there whose SHA-256 matches its @@ -95,8 +111,8 @@ recording its provenance. Host OS detection and backend validation live in Fixed build inputs and defaults live in [`build_constants.py`](../scripts/nvx_tools/build_constants.py). Its namespace -classes group kernel, OpenVMM, Alpine, Ubuntu, initramfs, Docker, cache-tool, -and release settings. For example, Python callers use +classes group kernel, OpenVMM, Rust guest-binary, Alpine, Ubuntu, initramfs, +Docker, cache-tool, and release settings. For example, Python callers use `KernelBuildConstants.VERSION` and `AlpineBuildConstants.MINIROOTFS_SHA256`; the previous module-level constants are not re-exported. Shared repository and artifact directories belong to `BuildConstants`. @@ -108,10 +124,10 @@ not frozen into the constants module. The provenance sidecars bind the kernel to its pinned archive, patch set, input configuration, generated configuration, and output hash; bind the -initramfs and package manifest to the pinned Alpine inputs and source files; -and bind OpenVMM to the exact clean gitlink revision and executable hash. -The Alpine source-file inputs include the constants module. Packaging rejects -missing, dirty, stale, or mismatched provenance. +initramfs and package manifest to the pinned Alpine inputs, Rust guest inputs, +and source files; and bind OpenVMM to the exact clean gitlink revision and +executable hash. Packaging rejects missing, dirty, stale, or mismatched +provenance. Ubuntu adds: @@ -164,12 +180,19 @@ Both commands need the standard guest build outputs above. The second writes complete per-scenario logs under `build/test-results/microvm` by default. -The Alpine initramfs includes the sandbox PID-1 bootstrap, its container namespace -helpers, the static `nvx-device-io` benchmark helper, the static `nvx-time-probe` -guest time probe, and the static `nvx-time` guest time component under `/sbin`. -The probe measures TSC and clock read latency, reports the guest's clock, -clocksource, TSC flags, and hypervisor identity, and runs the cross-vCPU warp -test that the +All three initramfses install the same nine Rust executables under `/sbin`: +`nvx-console-pending`, `nvx-device-io`, `nvx-exit`, `nvx-ingress`, +`nvx-managed-agent`, `nvx-mmio-write`, `nvx-reseed`, `nvx-time`, and +`nvx-time-probe`. Docker builds them once in a Rust +1.95.0 image pinned by digest and copies the identical bytes into Alpine, +Ubuntu, and Azure Linux; the kernel targets do not depend on that build. The +Ubuntu determinism target instead rebuilds them from fresh targets in both +attempts. Native image builds also rebuild them fresh. + +The Alpine initramfs additionally includes the sandbox PID-1 bootstrap and +its container namespace scripts. `nvx-time-probe` measures TSC and +clock read latency, reports the guest's clock, clocksource, TSC flags, and +hypervisor identity, and runs the cross-vCPU warp test that the [time ABI](design/3.2-clock-and-time.md#327-cross-processor-consistency) uses to enforce its 1 µs skew bound. `nvx-time` implements the time ABI's [guest obligations](design/3.2-clock-and-time.md#3210-guest-obligations): the conformance @@ -186,18 +209,18 @@ executable mapping. `nvx-time exhaustive` runs the CI-only exhaustive check boots never run it. The `--report-only` flag (or the `nvx_time_abi=report-only` kernel command-line token) reports violations without powering off, for runs against VMMs that -predate the ABI. Both time tools are linked statically against musl -(`musl-gcc`): with static glibc each would add about 700 KB to the initramfs, -and every unpacked kilobyte delays cold boot. The +predate the ABI. All nine executables link the musl supplied by the pinned +Rust target. Every package manifest records the shared Rust source digest and +each installed binary digest. The [kernel configuration checks](#kernel-configuration-checks) cover the kernel features that the sandbox bootstrap needs. The APK manifest records the -`blkid` and `util-linux` tools used by the bootstrap plus the device helper's -source and binary SHA-256 values. +`blkid` and `util-linux` tools used by the bootstrap along with every +guest-binary digest. The Ubuntu initramfs uses Ubuntu userland with the NVX kernel. It is not an Ubuntu-kernel or systemd VM. Its distribution-neutral package manifest records the Ubuntu Base and supplemental binary/source identities, license metadata, -rootfs SHA-256, and NVX helper provenance. +rootfs SHA-256, and NVX guest-binary provenance. The kernel build, `build-kernel`, which the Docker build also runs, caches the verified and patched source under `.cache/linux`, uses `O=build/linux`, runs @@ -309,7 +332,9 @@ complete output before replacing an existing `dist/` version, which requires `--force`. Its `SOURCE-MANIFEST.json` records the package version and exact hashes for OpenVMM, Linux, the generated kernel config, each packaged guest initramfs and its package manifest, and the Ubuntu distro layer and its -manifest. The OpenVMM section advertises microVM ABI 2, +manifest. Its `guest_rust` section pins Rust 1.95.0 and +`x86_64-unknown-linux-musl`, and each guest's `guest_sources` includes +`nvx-rs` and `rust-toolchain.toml`. The OpenVMM section advertises microVM ABI 2, control-session protocol 1, and contract `nvx-microvm-v2-control-v2`; product guest-agent metadata is intentionally not part of this platform manifest. @@ -342,6 +367,8 @@ SHA256SUMS Packages built with `--include-source` additionally contain `source/` and omit the Azure Linux guest artifacts, whose corresponding source is not collected. +The project source archive includes `nvx-rs` and excludes its generated +`target/` tree. `SHA256SUMS` has sorted `SHA256 relative/path` entries using POSIX separators for every packaged file, including `SOURCE-MANIFEST.json`, except `SHA256SUMS` itself. diff --git a/doc/ci.md b/doc/ci.md index a84061ff4..fe2ffb3d4 100644 --- a/doc/ci.md +++ b/doc/ci.md @@ -231,10 +231,15 @@ process exits, so delayed final output chunks cannot create a false failure. Shared guest artifacts are built with Docker on a GitHub-hosted Ubuntu runner. The kernel, Alpine initramfs, Ubuntu initramfs, and Ubuntu EROFS layer use -separate cache keys. Ubuntu keys include the Canonical archive pin, -supplemental package lock, common guest sources, shared download and guest -descriptor modules, converter implementation, Dockerfile, and `.dockerignore`. -Artifact upload +separate cache keys. Every guest key includes `nvx-rs/**` except its `target` +directory, `rust-toolchain.toml`, the shared binary-build modules, the +Dockerfile, and `.dockerignore`; the Rust builder image itself is pinned by +digest. Because `hashFiles` hashes contents only, every guest key also ends +with the path-aware Rust source digest that the guest package manifests +record, which `nvx.py guest-binary-source-identity` prints. Ubuntu keys also +include the Canonical archive pin, supplemental +package lock, common guest sources, shared download and guest descriptor +modules, and converter implementation. Artifact upload retains the Alpine filenames and adds the distinct Ubuntu filenames. Each backend also boots the Ubuntu initramfs and runs `/sbin/nvx-sandbox-smoke` from the Ubuntu EROFS layer as UID/GID 65534 over a @@ -590,8 +595,9 @@ the `guest` profile. The action delegates the musl lint and tests to path-remapped build, static-ELF check, and digest manifest to [`nvx.py build-guest-binaries`](usage.md#build-guest-binaries), and the prebuilt-binary integration run to -`nvx.py test-guest-binaries`. The guests ship the C programs in -`guest/common/` until their build switches to these binaries. +`nvx.py test-guest-binaries`. The same shared routine supplies every native +and Docker guest build, and Alpine, Ubuntu, and Azure Linux ship its nine +executables. The development release job depends on `aci-edge-sandboxes` and `nvx-rs` and requires their combined Linux/Windows results to be successful; failed, @@ -657,7 +663,8 @@ restore repeats the key, paths, and gzip compression of the cache entry that they differ, because a mismatch would otherwise only cause a silent cache miss. Because the agent firewall blocks `cdn.kernel.org` and `cdimage.ubuntu.com`, even inside containers, the workflow also installs the native guest build -prerequisites, allows the unprivileged user namespaces that Alpine's `apk` uses +prerequisites, including the pinned Rust toolchain's +`x86_64-unknown-linux-musl` target, allows the unprivileged user namespaces that Alpine's `apk` uses for package triggers, and stages the pinned Linux and Ubuntu Base archives, so agents can rebuild guest artifacts with `build-guest --native`. When a runner has a separate `/mnt` disk with more free space than `/`, it mounts the @@ -669,7 +676,7 @@ Copilot always runs the version on `dev`, even for sessions based on other branches, so changes reach agent sessions only after they merge. Pushes run it as a normal workflow for validation when they change the workflow or a file that its steps take versions, requirements, metadata, or code from: the -`build-guest-artifacts` action, the files that pin its Rust, cargo-nextest, +`build-guest-artifacts` action, all `nvx-rs/**` files, the files that pin its Rust, cargo-nextest, and shell linter versions, `requirements-dev.txt`, `SOURCE-MANIFEST.json`, `.gitmodules`, the OpenVMM submodule pin, and the NVX CLI and its modules. diff --git a/doc/contribute.md b/doc/contribute.md index 4aeb0243c..256ee3ce2 100644 --- a/doc/contribute.md +++ b/doc/contribute.md @@ -92,7 +92,7 @@ and snapshot test, which boots NVX's kernel and Alpine initramfs when [`test-openvmm`](usage.md#test-openvmm) runs it. NVX owns behavior that depends on its patched Linux kernel, its Alpine, -Ubuntu, and Azure Linux userspaces, guest helpers, SMP behavior, or virtio +Ubuntu, and Azure Linux userspaces, guest binaries and scripts, SMP behavior, or virtio devices. Add those scenarios to `scripts/nvx_tools/microvm_tests.py` and run them with `scripts/nvx.py test-microvm --backend BACKEND`. Keep guest workloads in @@ -104,7 +104,8 @@ registers them. Unit tests for the host tooling live in `scripts/test_*.py`. The `aci_edge_sandboxes` and `nvx-rs` crates keep unit tests alongside their Rust -source and integration tests in their `tests` directories. Tests of the NVX +source and integration tests in their `tests` directories. The latter also +runs `tests/guest_tools.rs` against the exact guest-profile binaries. Tests of the NVX mechanics, such as the golden fixtures of the control protocol, belong in `nvx-rs`, and tests of the ACI Edge Sandboxes contract in `aci_edge_sandboxes`. The `scripts/nvx.py test-aci-edge-sandboxes --backend diff --git a/doc/guests/azurelinux-guest.md b/doc/guests/azurelinux-guest.md index 111601a66..baa028f63 100644 --- a/doc/guests/azurelinux-guest.md +++ b/doc/guests/azurelinux-guest.md @@ -60,9 +60,9 @@ The build then: checks every RPM signature, installs the RPMs, and requires `ldd`, `setpriv`, and BusyBox to be present; 3. records the installed RPM inventory for the package manifest; -4. installs the common NVX `/init`, the guest scripts it needs, and - statically linked helpers built from `guest/common`, so the helpers do not - depend on Azure Linux libraries; +4. installs the common NVX `/init`, the guest scripts it needs, and the same + nine static Rust binaries as Alpine and Ubuntu, so they do not depend on + Azure Linux libraries; 5. links the BusyBox applets that the shared guest scripts expect, including `/bin/sh`; every other command resolves to the Azure Linux packages; 6. gives the `nobody` account, UID 65534, the empty home `/nonexistent` @@ -99,11 +99,12 @@ initramfs into a RAM filesystem capped at half of guest memory; see The package manifest `initramfs-azurelinux.cpio.gz.packages.json` records the release, architecture, base image, input digest, and artifact SHA-256. Each package entry names the RPM's version, release, architecture, license, and -source RPM. The manifest also records the source and binary digests of the -freestanding device-I/O helper. The input digest is a domain-separated SHA-256 -over the release, architecture, base image, and the checkout files that define -the build: the Dockerfile, the package lock, the build modules that download -the RPMs and compute the digest, and `guest/common`. +source RPM. The manifest also records the shared aggregate Rust source digest +and each of the nine installed guest-binary digests. The input digest is a +domain-separated SHA-256 over the release, architecture, base image, and the +checkout files that define the build: the Dockerfile, package lock, build +modules, guest scripts, complete `nvx-rs` guest input set, and +`rust-toolchain.toml`. Release staging rejects a manifest that does not match the artifact, the base image, or the current inputs. Only binary-only packages carry the two Azure diff --git a/doc/guests/ubuntu-guest.md b/doc/guests/ubuntu-guest.md index afebde1a5..169a8b474 100644 --- a/doc/guests/ubuntu-guest.md +++ b/doc/guests/ubuntu-guest.md @@ -85,7 +85,8 @@ The builder then: 6. requires one `root` account, a `nobody` account with UID 65534 and home `/nonexistent`, a `nogroup` group, and usr-merged `/bin`, `/sbin`, `/lib`, and `/lib64` links; and -7. installs the NVX guest files from `guest/common` and `guest/ubuntu`. +7. installs the NVX scripts from `guest/common` and `guest/ubuntu`, and the + nine Rust guest binaries from `nvx-rs`. The builder never executes a binary from the Ubuntu tree on the host. Both artifacts are packed with root ownership, fixed timestamps, and a stable file @@ -114,6 +115,12 @@ snapshots, including the guest obligations of the time ABI. The test harness rejects the `console-snapshot` scenario for Ubuntu instead of substituting Alpine. +It installs the same nine Rust guest binaries as Alpine and Azure Linux under +their `/sbin/nvx-*` names. Docker copies all nine from one digest-pinned Rust +builder, while native and determinism builds use the shared fresh-target +routine. The package manifest records one aggregate `nvx-rs` source digest +and each installed binary's digest. + ## Ubuntu sandbox layer [`build-distro-layer`](../usage.md#build-distro-layer) turns the prepared tree diff --git a/doc/project-structure.md b/doc/project-structure.md index 818ce554d..535a88a48 100644 --- a/doc/project-structure.md +++ b/doc/project-structure.md @@ -16,7 +16,7 @@ are build products or caches and are not part of the tracked source tree. The | `doc` | User and contributor documentation | | `docker` | Reproducible guest build environment | | `kernel` | Reproducible configs and complete Linux patch series | -| `guest` | Common guest sources plus Alpine-control and Ubuntu shell helpers | +| `guest` | Common and distribution-specific guest scripts | | `ubuntu` | Pinned Ubuntu supplemental binary-package lock | | `azurelinux` | Checksum-pinned Azure Linux supplemental RPM lock | | `aci_edge_sandboxes` | Rust crate `aci_edge_sandboxes`: state-aware sandbox API with an OpenVMM backend and an opt-in native-agent backend | @@ -52,9 +52,9 @@ nvx/ | |-- actionlint.yaml Self-hosted runner labels for actionlint | `-- copilot-instructions.md Repository-wide Copilot instructions |-- .vscode/settings.json Shared VS Code settings -|-- guest/ Guest-owned scripts and static helpers -| |-- common/ Shared init, agent, snapshot, time, and test helpers -| |-- alpine/ Alpine-control container entry helpers +|-- guest/ Guest-owned scripts +| |-- common/ Shared init, agent, snapshot, and probe scripts +| |-- alpine/ Alpine-control container entry scripts | `-- ubuntu/ Ubuntu interactive-shell startup policy |-- ubuntu/ | `-- packages.lock.json Exact supplemental Ubuntu binary package closure @@ -101,11 +101,11 @@ nvx/ | | |-- protocol/ Control-console codec shared by the host and the guest | | |-- contract.rs Control-contract versions and release compatibility | | |-- net/ CIDR parsing and subtraction, and hostname and guest-network forms -| | |-- host/ Artifacts, launch, policy lowering, sessions, and VM instances +| | |-- host/ Artifacts, launch, network and egress-policy lowering, sessions, VM instances, the sandbox launch profile, and the `nvx-rs` CLI (feature `cli`) | | |-- testing/ Fake OpenVMM (feature `testing`) | | |-- guest/ Programs that run in Linux guests (feature `guest`) -| | `-- bin/ nvx-fake-openvmm test double and the guest binaries -| |-- tests/ Fake-OpenVMM instance lifecycle and guest-binary tests +| | `-- bin/ nvx-rs CLI, nvx-fake-openvmm test double, and the guest binaries +| |-- tests/ Fake-OpenVMM instance lifecycle, CLI, and guest-binary tests, and the golden fixtures that Python recorded | |-- build.rs Stages the bundled artifacts (feature `bundled`) | `-- artifacts.json Release package pinned for the bundled artifacts |-- openvmm/ OpenVMM Git submodule @@ -158,6 +158,7 @@ nvx/ | |-- nvx-hosts.example.json Template for the ignored .nvx-hosts.json | |-- nvx.py Supported command-line entry point | |-- nvx_adversarial_executor.py Restricted adversarial executor entry point +| |-- nvx_rs_fixtures.py Golden fixtures of the nvx-rs ports of Python code, recorded from that code | |-- publish_development_release.py Development release publisher run by CI | `-- test_*.py Python tooling unit tests |-- SECURITY.md Microsoft security vulnerability reporting policy @@ -201,9 +202,10 @@ OpenVMM release. ### `guest/`, `ubuntu/`, and `azurelinux/` -`guest/common` contains scripts and static helper sources shared by the Alpine, -Ubuntu, and Azure Linux initramfs builds, plus the Azure Linux variants of the -container-entry helpers. `init` controls early boot and then starts an +`guest/common` contains scripts shared by the Alpine, Ubuntu, and Azure Linux +initramfs builds, plus the Azure Linux variants of the container-entry +scripts. The nine compiled `/sbin/nvx-*` programs live under `nvx-rs/src/guest` +and `nvx-rs/src/bin`. `init` controls early boot and then starts an initramfs program named by `nvx_exec=`, the Alpine-only `nvx-init-agent` sandbox profile, `nvx-managed-agent` for a managed workload, a virtio restore probe for benchmark profiling, or the interactive shell. On an interactive @@ -213,14 +215,16 @@ the container launch and entry scripts that only the Alpine initramfs installs; the entry script stages Alpine's musl loader and `setpriv` inside the workload's root. -The sandbox helpers resolve fixed virtio-blk roles through +The sandbox scripts resolve fixed virtio-blk roles through sysfs, assemble EROFS lower layers over ext4 scratch, place the workload in its cgroup before release, construct its mount/PID/UTS namespaces, enter its filesystem root after dropping capabilities, and retain the agent as the outer PID 1. In a managed sandbox, the agent supervises `nvx-managed-agent` and performs the ordered unmount teardown after a stop request. The remaining common helpers handle shutdown, virtio-fs mounting, snapshot preparation, and -the guest side of the time ABI, or provide device-access and diagnostic probes. +the guest side of the time ABI. The Rust guest binaries provide shutdown, +managed control, restore repair, device access, ingress announcements, and +diagnostic probes. `ubuntu/packages.lock.json` pins the complete supplemental `.deb` closure installed without maintainer-script execution. @@ -268,10 +272,14 @@ API, the policy honor matrix, and tests. The Rust crate `nvx-rs`, whose library `nvx` holds the NVX mechanics: the authenticated control protocol of the guest agent, artifact discovery and bundled-release staging, OpenVMM's command line, the lowering of network and -filesystem policies, process and platform primitives for Linux and Windows -hosts, the supervision of VM instances, and a fake OpenVMM for tests. Its -`guest` feature adds the programs that will replace the C programs of -`guest/common/`, built as static musl executables. It is a +filesystem policies, the compiler of `nvx.py`'s egress policy files, process +and platform primitives for Linux and Windows +hosts, the supervision of VM instances, the sandbox launch profile of +`nvx.py sandbox`, the `nvx-rs` command-line interface that ports +`nvx.py sandbox`, egress policy compilation, and artifact lookup, and a fake +OpenVMM for tests. Its +`guest` feature builds the nine static executables that every guest image +installs at `/sbin/nvx-*`. It is a standalone crate with its own `Cargo.lock`, like `aci_edge_sandboxes`, and builds independently of the OpenVMM submodule. See its [README](../nvx-rs/README.md). @@ -326,7 +334,7 @@ see [Automated environment bootstrap](setup.md#automated-environment-bootstrap). | `pyproject.toml` | Strict Pyright policy plus Ruff lint and format settings | | `requirements-dev.txt` | Pinned Python tools used by contributors and CI | | `rust-toolchain.toml` | Rust release that builds OpenVMM, its test guests, and the repository's Rust code | -| `SOURCE-MANIFEST.json` | Distribution version, OpenVMM microVM ABI and control-protocol versions, and exact Linux, Alpine, Ubuntu, and Azure Linux input identities and output locations | +| `SOURCE-MANIFEST.json` | Distribution version, OpenVMM microVM ABI and control-protocol versions, Rust guest toolchain and source roots, and exact Linux, Alpine, Ubuntu, and Azure Linux input identities and output locations | | `VERSION` | Distribution version consumed by packaging tools | | `.gitmodules` | OpenVMM repository URL, path, and tracking branch | | `.gitignore` | Excludes build products, caches, virtual environments, logs, and platform metadata | diff --git a/doc/setup.md b/doc/setup.md index 40c15572d..aebe36311 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -52,11 +52,21 @@ For Debian/Ubuntu hosts: sudo apt-get update sudo apt-get install -y \ bc binutils bison build-essential ca-certificates cpio curl erofs-utils flex \ - gzip libelf-dev libssl-dev musl-tools python3 rsync tar xz-utils zstd + gzip libelf-dev libssl-dev python3 rsync tar xz-utils zstd ``` -`musl-tools` provides `musl-gcc`, which native initramfs builds use for the -guest time tools; Docker builds install it in the build image. +Native guest builds use the exact Rust release in `rust-toolchain.toml` and +its musl standard-library target: + +```bash +toolchain=$(sed -n 's/^channel = "\([^"]*\)".*/\1/p' rust-toolchain.toml) +rustup toolchain install "$toolchain" --profile minimal +rustup target add --toolchain "$toolchain" x86_64-unknown-linux-musl +``` + +The Linux runner setup scripts and Copilot setup install this target +automatically. `build-essential` remains necessary for the kernel and as +Rust's linker driver; no separate guest libc compiler is required. ### Linux / KVM diff --git a/doc/usage.md b/doc/usage.md index d25010063..ffe672133 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -37,6 +37,7 @@ python3 scripts/nvx.py performance gate --help | `setup-cross-os-cache` | Install GNU tar and zstd for GitHub Actions cross-OS caches. | | `classify-ci-changes` | Select the CI suites that a list of changed files runs. | | `kernel-patch-identity` | Print the digest of the kernel patch paths and contents that keys the kernel cache. | +| `guest-binary-source-identity` | Print the digest of the Rust guest binary source paths and contents that keys the guest image caches. | | `check-required-ci` | Validate required GitHub Actions job results. | | `check-static-elf` | Check that executables are static ELF files without an interpreter. | | `test-openvmm-unit` | Run the OpenVMM workspace unit and documentation tests. | @@ -97,6 +98,20 @@ order, together with its content. The guest build appends it to both kernel cache keys, because `hashFiles` hashes contents only: a renamed patch changes the key even when its bytes do not. +### `guest-binary-source-identity` + +```text +python3 scripts/nvx.py guest-binary-source-identity [--root PATH] +``` + +Prints the source digest that guest package manifests record for the Rust guest +binaries: a digest of the relative path and content of every file under +`nvx-rs/src/`, the crate's manifest, lockfile, and build script, +`rust-toolchain.toml`, and the `build_constants.py` and `guest_binaries.py` +build modules. The guest build appends it to the Alpine, Ubuntu, and Azure +Linux cache keys, because `hashFiles` hashes contents only: moving a module +into its own `mod.rs` changes the key even when no bytes change. + ### `classify-ci-changes` ```text diff --git a/docker/Dockerfile b/docker/Dockerfile index 030c3eb8d..8fdeac9a4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,10 +1,26 @@ # syntax=docker/dockerfile:1.7 ARG DEBIAN_IMAGE=debian:12-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 +ARG RUST_BUILDER_IMAGE=rust:1.95.0-slim-bookworm@sha256:d7482085ff5b415f84dba5647ae71606650bdef00db7aeb69f4b3d170c3e4082 ARG EROFS_UTILS_VERSION=1.5-1 ARG AZURELINUX_VERSION=3.0 ARG AZURELINUX_IMAGE=mcr.microsoft.com/azurelinux/base/core@sha256:c877612270d1ee2d6ab2bc1f64bfe38ab697ac50be325154ee5129fce89c17e4 +FROM ${RUST_BUILDER_IMAGE} AS guest-binaries +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + python3 \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /repo +COPY rust-toolchain.toml /repo/ +COPY nvx-rs/ /repo/nvx-rs/ +COPY scripts/ /repo/scripts/ +RUN set -eux; \ + toolchain="$(sed -n 's/^channel = "\([^"]*\)".*/\1/p' rust-toolchain.toml)"; \ + test -n "$toolchain"; \ + rustup target add --toolchain "$toolchain" x86_64-unknown-linux-musl; \ + python3 scripts/nvx.py build-guest-binaries --output /out/guest-binaries + FROM ${DEBIAN_IMAGE} AS base ARG EROFS_UTILS_VERSION RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -20,7 +36,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ gzip \ libelf-dev \ libssl-dev \ - musl-tools \ patch \ python3 \ rsync \ @@ -43,10 +58,18 @@ RUN python3 scripts/nvx.py build-kernel FROM base AS kernel-debug RUN python3 scripts/nvx.py build-kernel --debug -FROM base AS initramfs +# Only the guest images install the Rust guest binaries and record their sources, +# so the kernel and Linux source targets never build them. +FROM base AS guest-base +COPY nvx-rs/ /repo/nvx-rs/ +COPY rust-toolchain.toml /repo/ +COPY --from=guest-binaries /out/guest-binaries/ /repo/build/guest-binaries/ +ENV NVX_GUEST_BINARIES=/repo/build/guest-binaries + +FROM guest-base AS initramfs RUN python3 scripts/nvx.py build-initramfs --guest alpine -FROM base AS ubuntu-initramfs +FROM guest-base AS ubuntu-initramfs RUN python3 scripts/nvx.py build-initramfs --guest ubuntu FROM ubuntu-initramfs AS ubuntu-distro-layer @@ -54,26 +77,16 @@ RUN python3 scripts/nvx.py build-distro-layer \ --guest ubuntu \ --output build/ubuntu-distro.erofs -FROM base AS ubuntu-determinism -RUN python3 scripts/nvx.py verify-guest-determinism \ +FROM guest-base AS ubuntu-determinism +COPY --from=guest-binaries /usr/local/cargo/ /usr/local/cargo/ +COPY --from=guest-binaries /usr/local/rustup/ /usr/local/rustup/ +ENV CARGO_HOME=/usr/local/cargo +ENV RUSTUP_HOME=/usr/local/rustup +ENV PATH=/usr/local/cargo/bin:${PATH} +RUN NVX_GUEST_BINARIES= python3 scripts/nvx.py verify-guest-determinism \ --guest ubuntu \ --work-dir build/guest-determinism -FROM base AS azurelinux-helpers -RUN set -eux; \ - mkdir -p /out/helpers; \ - for helper in nvx-reseed nvx-mmio-write nvx-console-pending nvx-managed-agent; do \ - cc -static -Os -s -Wall -Wextra -Werror -o "/out/helpers/$helper" "/repo/guest/common/$helper.c"; \ - done; \ - for helper in nvx-time nvx-exit nvx-ingress; do \ - musl-gcc -static -Os -s -Wall -Wextra -Werror -o "/out/helpers/$helper" "/repo/guest/common/$helper.c"; \ - done; \ - musl-gcc -std=gnu11 -static -O2 -pthread -s -Wall -Wextra -Werror -o /out/helpers/nvx-time-probe \ - /repo/guest/common/nvx-time-probe.c; \ - cc -nostdlib -static -Os -fno-builtin -fno-pie -fno-stack-protector -no-pie \ - -Wl,--build-id=none -Wl,-z,noexecstack -s -o /out/helpers/nvx-device-io \ - /repo/guest/common/nvx-device-io.c - FROM base AS azurelinux-packages RUN python3 - <<'PY' import sys @@ -97,12 +110,11 @@ RUN rpm --import /etc/pki/rpm-gpg/MICROSOFT-RPM-GPG-KEY \ && rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\t%{ARCH}\t%{LICENSE}\t%{SOURCERPM}\n' \ | sort > /tmp/azurelinux-rpm-packages.tsv -FROM base AS azurelinux-initramfs +FROM guest-base AS azurelinux-initramfs ARG AZURELINUX_IMAGE ARG AZURELINUX_VERSION COPY --from=azurelinux-rootfs / /rootfs/ COPY --from=azurelinux-rootfs /tmp/azurelinux-rpm-packages.tsv /tmp/azurelinux-rpm-packages.tsv -COPY --from=azurelinux-helpers /out/helpers/ /rootfs/sbin/ RUN <<'EOF' set -eux install -m 0755 /repo/guest/common/init /rootfs/init @@ -113,6 +125,22 @@ install -m 0755 /repo/guest/common/nvx-hostmount \ /repo/guest/common/nvx-container-enter-azurelinux /rootfs/sbin/ install -m 0755 /repo/guest/common/nvx-container-launch-azurelinux \ /rootfs/sbin/nvx-container-launch +python3 <<'PY' +import shutil +import sys +from pathlib import Path + +sys.path.insert(0, '/repo/scripts') +from nvx_tools.build_constants import GuestRustBuildConstants +from nvx_tools.guest_binaries import inspect_guest_binaries + +source = Path('/repo/build/guest-binaries') +inspect_guest_binaries(source) +for name in GuestRustBuildConstants.BINARIES: + destination = Path('/rootfs/sbin') / name + shutil.copyfile(source / name, destination) + destination.chmod(0o755) +PY # Link the BusyBox applets that the shared guest scripts expect; every other # command resolves through PATH to the Azure Linux packages. ln -sf ../sbin/busybox /rootfs/usr/bin/busybox @@ -171,10 +199,23 @@ from pathlib import Path sys.path.insert(0, '/repo/scripts') from nvx_tools.azurelinux import input_sha256 +from nvx_tools.build_constants import GuestRustBuildConstants +from nvx_tools.guest_binaries import inspect_guest_binaries def sha256(path: str) -> str: return hashlib.sha256(Path(path).read_bytes()).hexdigest() +guest_build = inspect_guest_binaries(Path('/repo/build/guest-binaries')) +helpers = {} +for name in GuestRustBuildConstants.BINARIES: + binary_sha256 = sha256(f'/rootfs/sbin/{name}') + if binary_sha256 != guest_build.binaries[name]: + raise ValueError(f'installed Rust guest binary changed: {name}') + helpers[name] = { + 'source_sha256': guest_build.source_sha256, + 'binary_sha256': binary_sha256, + } + manifest = { 'format': 1, 'guest': 'azurelinux', @@ -190,12 +231,7 @@ manifest = { ), 'package_manifest_format': 'azurelinux-v1', 'packages': json.loads(Path('/tmp/azurelinux-packages.json').read_text(encoding='utf-8')), - 'helpers': { - 'nvx-device-io': { - 'source_sha256': sha256('/repo/guest/common/nvx-device-io.c'), - 'binary_sha256': sha256('/rootfs/sbin/nvx-device-io'), - }, - }, + 'helpers': helpers, } Path('/tmp/initramfs-azurelinux.cpio.gz.packages.json').write_text( json.dumps(manifest, indent=2, sort_keys=True) + '\n', diff --git a/guest/common/nvx-console-pending.c b/guest/common/nvx-console-pending.c deleted file mode 100644 index 1c556d3a6..000000000 --- a/guest/common/nvx-console-pending.c +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include -#include -#include -#include - -int main(int argc, char **argv) -{ - int descriptor; - int pending; - - if (argc != 2) { - fprintf(stderr, "usage: nvx-console-pending DEVICE\n"); - return 2; - } - - descriptor = open(argv[1], O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (descriptor < 0) { - fprintf(stderr, "nvx-console-pending: open %s: %s\n", argv[1], - strerror(errno)); - return 1; - } - if (ioctl(descriptor, FIONREAD, &pending) != 0) { - fprintf(stderr, "nvx-console-pending: ioctl %s: %s\n", argv[1], - strerror(errno)); - close(descriptor); - return 1; - } - if (close(descriptor) != 0) { - fprintf(stderr, "nvx-console-pending: close %s: %s\n", argv[1], - strerror(errno)); - return 1; - } - if (printf("%d\n", pending) < 0) { - fprintf(stderr, "nvx-console-pending: write: %s\n", strerror(errno)); - return 1; - } - return 0; -} \ No newline at end of file diff --git a/guest/common/nvx-device-io.c b/guest/common/nvx-device-io.c deleted file mode 100644 index 752b613be..000000000 --- a/guest/common/nvx-device-io.c +++ /dev/null @@ -1,376 +0,0 @@ -typedef unsigned long u64; - -#define SYS_READ 0 -#define SYS_WRITE 1 -#define SYS_CLOSE 3 -#define SYS_POLL 7 -#define SYS_PREAD64 17 -#define SYS_PWRITE64 18 -#define SYS_SOCKET 41 -#define SYS_CONNECT 42 -#define SYS_FSYNC 74 -#define SYS_FTRUNCATE 77 -#define SYS_CLOCK_GETTIME 228 -#define SYS_OPENAT 257 -#define SYS_EXIT_GROUP 231 - -#define AT_FDCWD -100 -#define O_RDONLY 0 -#define O_RDWR 2 -#define O_CREAT 64 -#define O_DIRECT 16384 -#define O_CLOEXEC 524288 -#define AF_INET 2 -#define SOCK_DGRAM 2 -#define SOCK_CLOEXEC 524288 -#define POLLIN 1 -#define CLOCK_MONOTONIC 1 - -struct timespec { - long seconds; - long nanoseconds; -}; - -struct sockaddr_in { - unsigned short family; - unsigned short port; - unsigned int address; - unsigned char padding[8]; -}; - -struct pollfd { - int fd; - short events; - short returned_events; -}; - -static unsigned char io_buffer[4096] __attribute__((aligned(4096))); -static char output[384]; -static u64 output_length; - -static long syscall6( - long number, - long argument1, - long argument2, - long argument3, - long argument4, - long argument5, - long argument6 -) { - register long register10 __asm__("r10") = argument4; - register long register8 __asm__("r8") = argument5; - register long register9 __asm__("r9") = argument6; - long result; - __asm__ volatile( - "syscall" - : "=a"(result) - : "a"(number), "D"(argument1), "S"(argument2), "d"(argument3), - "r"(register10), "r"(register8), "r"(register9) - : "rcx", "r11", "memory" - ); - return result; -} - -static long syscall4(long number, long a1, long a2, long a3, long a4) { - return syscall6(number, a1, a2, a3, a4, 0, 0); -} - -static long syscall3(long number, long a1, long a2, long a3) { - return syscall6(number, a1, a2, a3, 0, 0, 0); -} - -static long syscall2(long number, long a1, long a2) { - return syscall6(number, a1, a2, 0, 0, 0, 0); -} - -static long syscall1(long number, long a1) { - return syscall6(number, a1, 0, 0, 0, 0, 0); -} - -static int strings_equal(const char *left, const char *right) { - while (*left && *left == *right) { - left++; - right++; - } - return *left == *right; -} - -static int parse_u64(const char *text, u64 *value) { - u64 parsed = 0; - if (!*text) { - return -1; - } - while (*text) { - if (*text < '0' || *text > '9') { - return -1; - } - parsed = parsed * 10 + (u64)(*text - '0'); - text++; - } - *value = parsed; - return 0; -} - -static int parse_ipv4(const char *text, unsigned int *address) { - unsigned int octets[4] = {0, 0, 0, 0}; - int index = 0; - int digits = 0; - while (*text) { - if (*text == '.') { - if (!digits || index == 3) { - return -1; - } - index++; - digits = 0; - } else if (*text >= '0' && *text <= '9') { - octets[index] = octets[index] * 10 + (unsigned int)(*text - '0'); - if (octets[index] > 255) { - return -1; - } - digits++; - } else { - return -1; - } - text++; - } - if (index != 3 || !digits) { - return -1; - } - *address = octets[0] | (octets[1] << 8) | (octets[2] << 16) | - (octets[3] << 24); - return 0; -} - -static unsigned short network_u16(unsigned short value) { - return (unsigned short)((value << 8) | (value >> 8)); -} - -static u64 monotonic_ns(void) { - struct timespec value; - if (syscall2(SYS_CLOCK_GETTIME, CLOCK_MONOTONIC, (long)&value) < 0) { - return 0; - } - return (u64)value.seconds * 1000000000UL + (u64)value.nanoseconds; -} - -static void append_text(const char *text) { - while (*text && output_length < sizeof(output)) { - output[output_length++] = *text++; - } -} - -static void append_u64(u64 value) { - char digits[24]; - int length = 0; - do { - digits[length++] = (char)('0' + value % 10); - value /= 10; - } while (value); - while (length && output_length < sizeof(output)) { - output[output_length++] = digits[--length]; - } -} - -static int write_all(int descriptor, const void *data, u64 length) { - const char *current = data; - while (length) { - long written = syscall3(SYS_WRITE, descriptor, (long)current, (long)length); - if (written == -4) { - continue; - } - if (written <= 0) { - return -1; - } - current += written; - length -= (u64)written; - } - return 0; -} - -static int emit_result( - const char *device, - const char *operation, - u64 operations, - u64 bytes_per_operation, - u64 elapsed_ns -) { - output_length = 0; - append_text("{\"device\":\""); - append_text(device); - append_text("\",\"operation\":\""); - append_text(operation); - append_text("\",\"operations\":"); - append_u64(operations); - append_text(",\"bytes_per_operation\":"); - append_u64(bytes_per_operation); - append_text(",\"elapsed_ns\":"); - append_u64(elapsed_ns); - append_text("}\n"); - return write_all(1, output, output_length); -} - -static int run_file(int argc, char **argv) { - u64 duration_ms; - u64 size_bytes; - int writing; - int direct; - int create; - int flags; - long descriptor; - u64 started; - u64 ended; - u64 operations = 0; - u64 random = 0x9e3779b97f4a7c15UL; - u64 block_count; - int index; - - if (argc != 8 || parse_u64(argv[4], &duration_ms) || - parse_u64(argv[5], &size_bytes) || duration_ms == 0 || - size_bytes < sizeof(io_buffer)) { - return 2; - } - writing = strings_equal(argv[3], "write"); - if (!writing && !strings_equal(argv[3], "read")) { - return 2; - } - direct = strings_equal(argv[6], "direct"); - create = strings_equal(argv[7], "create"); - if ((!direct && !strings_equal(argv[6], "buffered")) || - (!create && !strings_equal(argv[7], "existing"))) { - return 2; - } - flags = (writing ? O_RDWR : O_RDONLY) | O_CLOEXEC; - if (direct) { - flags |= O_DIRECT; - } - if (create) { - flags |= O_CREAT; - } - descriptor = syscall4(SYS_OPENAT, AT_FDCWD, (long)argv[2], flags, 0600); - if (descriptor < 0) { - return 3; - } - if (create && syscall2(SYS_FTRUNCATE, descriptor, (long)size_bytes) < 0) { - syscall1(SYS_CLOSE, descriptor); - return 3; - } - for (index = 0; index < (int)sizeof(io_buffer); index++) { - io_buffer[index] = (unsigned char)(index * 31 + 17); - } - block_count = size_bytes / sizeof(io_buffer); - started = monotonic_ns(); - if (!started) { - syscall1(SYS_CLOSE, descriptor); - return 3; - } - ended = started; - while (ended - started < duration_ms * 1000000UL) { - u64 offset; - long transferred; - random = random * 6364136223846793005UL + 1442695040888963407UL; - offset = (random % block_count) * sizeof(io_buffer); - transferred = writing - ? syscall4(SYS_PWRITE64, descriptor, (long)io_buffer, sizeof(io_buffer), offset) - : syscall4(SYS_PREAD64, descriptor, (long)io_buffer, sizeof(io_buffer), offset); - if (transferred != (long)sizeof(io_buffer)) { - syscall1(SYS_CLOSE, descriptor); - return 4; - } - operations++; - if ((operations & 63) == 0) { - ended = monotonic_ns(); - } - } - ended = monotonic_ns(); - if (writing) { - syscall1(SYS_FSYNC, descriptor); - } - syscall1(SYS_CLOSE, descriptor); - return emit_result("file", argv[3], operations, sizeof(io_buffer), ended - started); -} - -static int run_network(int argc, char **argv) { - u64 port; - u64 duration_ms; - unsigned int address; - struct sockaddr_in peer = {0}; - struct pollfd poll_descriptor; - long descriptor; - u64 started; - u64 ended; - u64 operations = 0; - int index; - - if (argc != 5 || parse_ipv4(argv[2], &address) || - parse_u64(argv[3], &port) || port == 0 || port > 65535 || - parse_u64(argv[4], &duration_ms) || duration_ms == 0) { - return 2; - } - descriptor = syscall3(SYS_SOCKET, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (descriptor < 0) { - return 3; - } - peer.family = AF_INET; - peer.port = network_u16((unsigned short)port); - peer.address = address; - if (syscall3(SYS_CONNECT, descriptor, (long)&peer, sizeof(peer)) < 0) { - syscall1(SYS_CLOSE, descriptor); - return 3; - } - for (index = 0; index < 64; index++) { - io_buffer[index] = (unsigned char)(index + 1); - } - poll_descriptor.fd = (int)descriptor; - poll_descriptor.events = POLLIN; - started = monotonic_ns(); - if (!started) { - syscall1(SYS_CLOSE, descriptor); - return 3; - } - ended = started; - while (ended - started < duration_ms * 1000000UL) { - long received; - if (syscall3(SYS_WRITE, descriptor, (long)io_buffer, 64) != 64) { - syscall1(SYS_CLOSE, descriptor); - return 4; - } - poll_descriptor.returned_events = 0; - if (syscall3(SYS_POLL, (long)&poll_descriptor, 1, 100) > 0 && - (poll_descriptor.returned_events & POLLIN)) { - received = syscall3(SYS_READ, descriptor, (long)io_buffer, 64); - if (received == 64) { - operations++; - } - } - ended = monotonic_ns(); - } - syscall1(SYS_CLOSE, descriptor); - return emit_result("network", "roundtrip", operations, 64, ended - started); -} - -static int benchmark_main(int argc, char **argv) { - if (argc > 1 && strings_equal(argv[1], "file")) { - return run_file(argc, argv); - } - if (argc > 1 && strings_equal(argv[1], "network")) { - return run_network(argc, argv); - } - return 2; -} - -void nvx_device_io_start(long *stack) { - int status = benchmark_main((int)stack[0], (char **)&stack[1]); - syscall1(SYS_EXIT_GROUP, status); - for (;;) { - } -} - -__asm__( - ".global _start\n" - ".type _start,@function\n" - "_start:\n" - "mov %rsp,%rdi\n" - "andq $-16,%rsp\n" - "call nvx_device_io_start\n" - "ud2\n" -); \ No newline at end of file diff --git a/guest/common/nvx-exit.c b/guest/common/nvx-exit.c deleted file mode 100644 index 6f581ad8a..000000000 --- a/guest/common/nvx-exit.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * nvx-exit [CODE]: stop the VM with an 8-bit exit code, by writing one byte - * to OpenVMM's control port 0x604 through /dev/port. CODE defaults to 1, and - * any CODE that isn't a decimal number from 0 to 255 becomes 1. It prints - * nothing; if the write fails, it exits with status 1. - * - * This is a single static exec rather than a shell script, because a guest's - * first command after a restore pays a cold first touch for every fork, exec - * and exit, and the script ran sh, a command substitution and a printf | dd - * pipeline. - */ -#include -#include -#include - -/* Tests build against a regular file; the guest always writes /dev/port. */ -#ifndef NVX_EXIT_DEVICE -#define NVX_EXIT_DEVICE "/dev/port" -#endif -#define NVX_EXIT_PORT 0x604 - -static unsigned char exit_code(const char *argument) -{ - unsigned int value = 0; - - if (argument == NULL || *argument == '\0') - return 1; - for (; *argument != '\0'; argument++) { - if (*argument < '0' || *argument > '9') - return 1; - value = value * 10 + (unsigned int)(*argument - '0'); - if (value > 255) - return 1; - } - return (unsigned char)value; -} - -int main(int argc, char **argv) -{ - unsigned char code = exit_code(argc > 1 ? argv[1] : NULL); - int port = open(NVX_EXIT_DEVICE, O_WRONLY); - - if (port < 0) - return 1; - return pwrite(port, &code, 1, NVX_EXIT_PORT) == 1 ? 0 : 1; -} diff --git a/guest/common/nvx-ingress.c b/guest/common/nvx-ingress.c deleted file mode 100644 index 2b021a0e2..000000000 --- a/guest/common/nvx-ingress.c +++ /dev/null @@ -1,661 +0,0 @@ -// nvx-ingress announces the guest's TCP and UDP listeners to the gateway of -// the portable network profile, which publishes them to the host's private -// networks or, for host-loopback ingress alone, on host loopback. The init -// system starts it, as root, when the kernel command line carries -// virtnet_ingress=allow or virtnet_ingress=host-loopback. -// -// Every 100 ms it reads the kernel's socket tables. A TCP socket in the LISTEN -// state is a listener, and so is an unconnected UDP socket that persists for a -// second, which skips short-lived client sockets, such as those of DNS queries. -// Each UDP socket settles on its own, identified by the kernel's inode for it, -// and stays announced while it exists, even once a server connects it to its -// client; another socket that reuses a port, such as a client's, settles anew, -// and a port announces the families of its announced sockets. Sockets bound to -// loopback addresses, or to IPv6 link-local ones, are unreachable through the -// gateway. A TCP listener on the unspecified IPv6 address also takes IPv4 -// connections unless the kernel's socket diagnostics report it IPv6-only; a UDP -// socket there, which the guest kernel's diagnostics do not cover, follows -// net.ipv6.bindv6only. It sends the canonical set of listeners to UDP port 1021 -// of the IPv4 gateway from a privileged port, which only root can bind, -// whenever the set changes and at least every 2 seconds. -// -// An announcement is the magic "NVXL", version 1, a flags byte whose bit 0 -// marks omitted listeners, a big-endian count, and that many entries in -// increasing order of protocol and port: the IP protocol number, a family -// mask (1 for IPv4, 2 for IPv6), and the big-endian port. -// -// "nvx-ingress test DIRECTORY" runs the numbered snapshots in DIRECTORY, from -// 0, as successive scans instead, and prints each scan's time and -// announcement in hexadecimal. A snapshot holds the socket tables under net/, -// optionally sys/net/ipv6/bindv6only, optionally a socket diagnostics dump in -// diag, and the scan time in milliseconds in time. - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ANNOUNCEMENT_PORT 1021 -#define FIRST_SOURCE_PORT 1023 -#define LAST_SOURCE_PORT 512 -#define MAX_ANNOUNCED 64 -#define MAX_TRACKED 1024 -#define SCAN_INTERVAL_MS 100 -#define REFRESH_INTERVAL_MS 2000 -#define UDP_SETTLE_MS 1000 -#define FAMILY_IPV4 1 -#define FAMILY_IPV6 2 -#define PROTOCOL_TCP 6 -#define PROTOCOL_UDP 17 -#define TCP_LISTEN 0x0a -#define UDP_CONNECTED 0x01 -#define UDP_UNCONNECTED 0x07 -#define HEADER_LENGTH 8 -#define ENTRY_LENGTH 4 - -// The kernel's netlink socket diagnostics ABI (linux/netlink.h, -// linux/sock_diag.h, and linux/inet_diag.h), whose headers musl lacks. -#define NETLINK_SOCK_DIAG_PROTOCOL 4 -#define NETLINK_REQUEST 0x01 -#define NETLINK_DUMP 0x300 -#define NETLINK_ERROR 2 -#define NETLINK_DONE 3 -#define SOCK_DIAG_BY_FAMILY 20 -#define INET_DIAG_SKV6ONLY 11 - -struct netlink_header { - uint32_t length; - uint16_t type; - uint16_t flags; - uint32_t sequence; - uint32_t pid; -}; - -struct netlink_address { - sa_family_t family; - unsigned short pad; - uint32_t pid; - uint32_t groups; -}; - -struct netlink_attribute { - uint16_t length; - uint16_t type; -}; - -struct inet_diag_socket_id { - uint16_t source_port; - uint16_t destination_port; - uint32_t source[4]; - uint32_t destination[4]; - uint32_t interface; - uint32_t cookie[2]; -}; - -struct inet_diag_request { - uint8_t family; - uint8_t protocol; - uint8_t extensions; - uint8_t pad; - uint32_t states; - struct inet_diag_socket_id id; -}; - -struct inet_diag_message { - uint8_t family; - uint8_t state; - uint8_t timer; - uint8_t retransmits; - struct inet_diag_socket_id id; - uint32_t expires; - uint32_t receive_queue; - uint32_t write_queue; - uint32_t uid; - uint32_t inode; -}; - -// Whether the IPv6 TCP listeners on the unspecified address at one port take -// IPv4 connections, as the kernel's socket diagnostics report them. -struct wildcard_listener { - uint16_t port; - uint8_t dual_stack; - uint8_t ipv6_only; -}; - -struct listener { - uint8_t protocol; - uint8_t families; - uint16_t port; -}; - -struct table { - struct listener entries[MAX_TRACKED]; - size_t count; -}; - -// A reachable UDP socket, which the kernel's inode identifies while it exists. -struct udp_socket { - uint16_t port; - uint8_t families; - uint8_t connected; - unsigned long inode; -}; - -// A UDP socket that is settling or, once it has persisted unconnected for -// UDP_SETTLE_MS, announced. -struct udp_state { - uint16_t port; - uint8_t families; - uint8_t announced; - unsigned long inode; - uint64_t first_seen_ms; -}; - -static struct table found; -static struct udp_socket udp_sockets[MAX_TRACKED]; -static size_t udp_socket_count; -static struct udp_state udp_states[MAX_TRACKED]; -static size_t udp_state_count; -static struct udp_state next_udp_states[MAX_TRACKED]; -static uint8_t payload[HEADER_LENGTH + MAX_ANNOUNCED * ENTRY_LENGTH]; -static uint8_t last_payload[sizeof(payload)]; -static size_t last_length; -static int ipv6_dual_stack = 1; -static struct wildcard_listener wildcard_tcp[MAX_TRACKED]; -static size_t wildcard_tcp_count; -static uint8_t diag_buffer[32768] __attribute__((aligned(8))); -// The root of the socket tables and sysctls, which test mode replaces. -static const char *proc_root = "/proc"; -static int test_mode; - -static uint64_t now_ms(void) -{ - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - return (uint64_t)now.tv_sec * 1000 + (uint64_t)now.tv_nsec / 1000000; -} - -static void sleep_ms(unsigned int milliseconds) -{ - struct timespec delay = { - .tv_sec = milliseconds / 1000, - .tv_nsec = (long)(milliseconds % 1000) * 1000000, - }; - while (nanosleep(&delay, &delay) != 0 && errno == EINTR) { - } -} - -// Records a listener, merging the address families of one protocol and port. -static void add(struct table *table, uint8_t protocol, uint16_t port, uint8_t families) -{ - for (size_t index = 0; index < table->count; ++index) { - struct listener *entry = &table->entries[index]; - if (entry->protocol == protocol && entry->port == port) { - entry->families |= families; - return; - } - } - if (table->count < MAX_TRACKED) { - table->entries[table->count++] = (struct listener){ - .protocol = protocol, - .families = families, - .port = port, - }; - } -} - -// Returns the tracked state of the UDP socket with `inode` at `port`, if any. -static const struct udp_state *find_udp_state(uint16_t port, unsigned long inode) -{ - for (size_t index = 0; index < udp_state_count; ++index) { - if (udp_states[index].port == port && udp_states[index].inode == inode) { - return &udp_states[index]; - } - } - return NULL; -} - -static int parse_hex_u32(const char *text, uint32_t *value) -{ - char digits[9]; - char *end; - memcpy(digits, text, 8); - digits[8] = '\0'; - errno = 0; - unsigned long parsed = strtoul(digits, &end, 16); - if (errno != 0 || *end != '\0' || end != digits + 8) { - return 0; - } - *value = (uint32_t)parsed; - return 1; -} - -// Records the IPV6_V6ONLY state of an IPv6 TCP listener on the unspecified -// address from one socket diagnostics message. -static void note_wildcard_tcp(const uint8_t *message, size_t length) -{ - struct inet_diag_message diag; - if (length < sizeof(diag)) { - return; - } - memcpy(&diag, message, sizeof(diag)); - if (diag.family != AF_INET6 || diag.state != TCP_LISTEN) { - return; - } - for (int word = 0; word < 4; ++word) { - if (diag.id.source[word] != 0) { - return; - } - } - uint16_t port = ntohs(diag.id.source_port); - for (size_t offset = sizeof(diag); offset + sizeof(struct netlink_attribute) <= length;) { - struct netlink_attribute attribute; - memcpy(&attribute, message + offset, sizeof(attribute)); - if (attribute.length < sizeof(attribute) || attribute.length > length - offset) { - return; - } - if (attribute.type == INET_DIAG_SKV6ONLY && attribute.length > sizeof(attribute)) { - uint8_t ipv6_only = message[offset + sizeof(attribute)] != 0; - struct wildcard_listener *entry = NULL; - for (size_t index = 0; index < wildcard_tcp_count; ++index) { - if (wildcard_tcp[index].port == port) { - entry = &wildcard_tcp[index]; - } - } - if (entry == NULL && wildcard_tcp_count < MAX_TRACKED) { - entry = &wildcard_tcp[wildcard_tcp_count++]; - *entry = (struct wildcard_listener){.port = port}; - } - if (entry != NULL) { - entry->dual_stack |= (uint8_t)!ipv6_only; - entry->ipv6_only |= ipv6_only; - } - return; - } - offset += (attribute.length + 3u) & ~3u; - } -} - -// Records the IPv6 TCP listeners on the unspecified address of one part of a -// socket diagnostics dump, returning 0 once the dump ends or is malformed. -static int parse_wildcard_tcp(const uint8_t *buffer, size_t length) -{ - for (size_t offset = 0; offset + sizeof(struct netlink_header) <= length;) { - struct netlink_header header; - memcpy(&header, buffer + offset, sizeof(header)); - if (header.length < sizeof(header) || header.length > length - offset || - header.type == NETLINK_DONE || header.type == NETLINK_ERROR) { - return 0; - } - if (header.type == SOCK_DIAG_BY_FAMILY) { - note_wildcard_tcp(buffer + offset + sizeof(header), header.length - sizeof(header)); - } - offset += (header.length + 3u) & ~3u; - } - return 1; -} - -// Asks the kernel's socket diagnostics, or in test mode the snapshot's dump, -// which IPv6 TCP listeners on the unspecified address are IPv6-only. Without -// an answer, none is known. -static void query_wildcard_tcp(void) -{ - wildcard_tcp_count = 0; - if (test_mode) { - char path[4096]; - if (snprintf(path, sizeof(path), "%s/diag", proc_root) >= (int)sizeof(path)) { - return; - } - FILE *file = fopen(path, "rbe"); - if (file != NULL) { - size_t length = fread(diag_buffer, 1, sizeof(diag_buffer), file); - fclose(file); - parse_wildcard_tcp(diag_buffer, length); - } - return; - } - int fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_SOCK_DIAG_PROTOCOL); - if (fd < 0) { - return; - } - // A dump that never ends must not stall the scans. - struct timeval timeout = {.tv_usec = SCAN_INTERVAL_MS * 1000}; - struct { - struct netlink_header header; - struct inet_diag_request request; - } query = { - .header = - { - .length = sizeof(query), - .type = SOCK_DIAG_BY_FAMILY, - .flags = NETLINK_REQUEST | NETLINK_DUMP, - }, - .request = - { - .family = AF_INET6, - .protocol = IPPROTO_TCP, - .states = 1u << TCP_LISTEN, - }, - }; - struct netlink_address kernel = {.family = AF_NETLINK}; - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 || - sendto(fd, &query, sizeof(query), 0, (struct sockaddr *)&kernel, sizeof(kernel)) != - (ssize_t)sizeof(query)) { - close(fd); - return; - } - for (;;) { - ssize_t received = recv(fd, diag_buffer, sizeof(diag_buffer), 0); - if (received <= 0 || !parse_wildcard_tcp(diag_buffer, (size_t)received)) { - break; - } - } - close(fd); -} - -// Returns the families through which an IPv6 socket of `protocol` on the -// unspecified address at `port` is reachable. -static uint8_t wildcard_families(uint8_t protocol, uint16_t port) -{ - if (protocol == PROTOCOL_TCP) { - for (size_t index = 0; index < wildcard_tcp_count; ++index) { - const struct wildcard_listener *entry = &wildcard_tcp[index]; - if (entry->port == port && (entry->dual_stack || entry->ipv6_only)) { - return entry->dual_stack ? FAMILY_IPV4 | FAMILY_IPV6 : FAMILY_IPV6; - } - } - } - return ipv6_dual_stack ? FAMILY_IPV4 | FAMILY_IPV6 : FAMILY_IPV6; -} - -// Returns the families through which a socket of `protocol` at `port`, bound -// to the kernel's hexadecimal rendering of an address, is reachable from the -// gateway, or 0. The kernel prints each 32-bit word of the address as it lies -// in memory. -static uint8_t reachable_families(const char *address, int ipv6, uint8_t protocol, uint16_t port) -{ - if (!ipv6) { - struct in_addr ipv4; - if (strlen(address) != 8 || !parse_hex_u32(address, &ipv4.s_addr)) { - return 0; - } - return (ntohl(ipv4.s_addr) >> 24) == 127 ? 0 : FAMILY_IPV4; - } - struct in6_addr ipv6_address; - if (strlen(address) != 32) { - return 0; - } - for (int word = 0; word < 4; ++word) { - uint32_t value; - if (!parse_hex_u32(address + word * 8, &value)) { - return 0; - } - memcpy(&ipv6_address.s6_addr[word * 4], &value, sizeof(value)); - } - if (IN6_IS_ADDR_UNSPECIFIED(&ipv6_address)) { - return wildcard_families(protocol, port); - } - if (IN6_IS_ADDR_LOOPBACK(&ipv6_address) || IN6_IS_ADDR_LINKLOCAL(&ipv6_address)) { - return 0; - } - if (IN6_IS_ADDR_V4MAPPED(&ipv6_address)) { - return ipv6_address.s6_addr[12] == 127 ? 0 : FAMILY_IPV4; - } - return FAMILY_IPV6; -} - -// Adds the TCP listeners of one of the kernel's socket tables, `name` under -// net/, to `found`, and its reachable UDP sockets, connected or not, to -// `udp_sockets`. -static void scan(const char *name, uint8_t protocol, int ipv6, uint16_t own_port) -{ - char line[512]; - char path[4096]; - if (snprintf(path, sizeof(path), "%s/net/%s", proc_root, name) >= (int)sizeof(path)) { - return; - } - FILE *file = fopen(path, "re"); - if (file == NULL) { - return; - } - if (fgets(line, sizeof(line), file) == NULL) { - fclose(file); - return; - } - while (fgets(line, sizeof(line), file) != NULL) { - char local[64]; - char remote[64]; - unsigned int state; - unsigned long inode; - if (sscanf(line, " %*u: %63s %63s %x %*s %*s %*s %*u %*u %lu", local, remote, &state, - &inode) != 4) { - continue; - } - char *local_port = strrchr(local, ':'); - char *remote_port = strrchr(remote, ':'); - if (local_port == NULL || remote_port == NULL) { - continue; - } - *local_port++ = '\0'; - *remote_port++ = '\0'; - unsigned long port = strtoul(local_port, NULL, 16); - if (port == 0 || port > UINT16_MAX) { - continue; - } - uint8_t connected = 0; - if (protocol == PROTOCOL_TCP) { - if (state != TCP_LISTEN) { - continue; - } - } else if (state == UDP_CONNECTED) { - connected = 1; - } else if (state != UDP_UNCONNECTED || strtoul(remote_port, NULL, 16) != 0 || - strspn(remote, "0") != strlen(remote) || port == own_port) { - continue; - } - uint8_t families = reachable_families(local, ipv6, protocol, (uint16_t)port); - if (families == 0) { - continue; - } - if (protocol == PROTOCOL_TCP) { - add(&found, protocol, (uint16_t)port, families); - } else if (udp_socket_count < MAX_TRACKED) { - udp_sockets[udp_socket_count++] = (struct udp_socket){ - .port = (uint16_t)port, - .families = families, - .connected = connected, - .inode = inode, - }; - } - } - fclose(file); -} - -static int compare_listeners(const void *left, const void *right) -{ - const struct listener *a = left; - const struct listener *b = right; - if (a->protocol != b->protocol) { - return a->protocol < b->protocol ? -1 : 1; - } - return a->port < b->port ? -1 : a->port > b->port; -} - -// Scans the socket tables and encodes the announcement into `payload`, -// returning its length. -static size_t collect(uint16_t own_port, uint64_t now) -{ - found.count = 0; - udp_socket_count = 0; - query_wildcard_tcp(); - scan("tcp", PROTOCOL_TCP, 0, own_port); - scan("tcp6", PROTOCOL_TCP, 1, own_port); - scan("udp", PROTOCOL_UDP, 0, own_port); - scan("udp6", PROTOCOL_UDP, 1, own_port); - - // An unconnected UDP socket is announced once it persists for - // UDP_SETTLE_MS, and stays announced, even once a server connects it to a - // client, while it exists. Each port announces the families of its - // announced sockets. - size_t next_count = 0; - for (size_t index = 0; index < udp_socket_count; ++index) { - const struct udp_socket *socket = &udp_sockets[index]; - const struct udp_state *previous = find_udp_state(socket->port, socket->inode); - struct udp_state next = { - .port = socket->port, - .families = socket->families, - .inode = socket->inode, - .first_seen_ms = previous != NULL ? previous->first_seen_ms : now, - }; - if (previous != NULL && previous->announced) { - next.announced = 1; - } else if (socket->connected) { - continue; - } else { - next.announced = now - next.first_seen_ms >= UDP_SETTLE_MS; - } - next_udp_states[next_count++] = next; - if (next.announced) { - add(&found, PROTOCOL_UDP, next.port, next.families); - } - } - memcpy(udp_states, next_udp_states, next_count * sizeof(udp_states[0])); - udp_state_count = next_count; - size_t announced = found.count; - - qsort(found.entries, announced, sizeof(found.entries[0]), compare_listeners); - size_t count = announced < MAX_ANNOUNCED ? announced : MAX_ANNOUNCED; - memcpy(payload, "NVXL", 4); - payload[4] = 1; - payload[5] = announced > MAX_ANNOUNCED; - payload[6] = (uint8_t)(count >> 8); - payload[7] = (uint8_t)count; - for (size_t index = 0; index < count; ++index) { - uint8_t *entry = &payload[HEADER_LENGTH + index * ENTRY_LENGTH]; - entry[0] = found.entries[index].protocol; - entry[1] = found.entries[index].families; - entry[2] = (uint8_t)(found.entries[index].port >> 8); - entry[3] = (uint8_t)found.entries[index].port; - } - return HEADER_LENGTH + count * ENTRY_LENGTH; -} - -static void read_bindv6only(void) -{ - char path[4096]; - if (snprintf(path, sizeof(path), "%s/sys/net/ipv6/bindv6only", proc_root) >= - (int)sizeof(path)) { - return; - } - FILE *file = fopen(path, "re"); - if (file == NULL) { - return; - } - int value; - if (fscanf(file, "%d", &value) == 1) { - ipv6_dual_stack = value == 0; - } - fclose(file); -} - -// Runs the snapshots in `directory` as successive scans; see the top of the -// file. -static int run_test(const char *directory) -{ - static char root[4096]; - test_mode = 1; - for (unsigned int index = 0;; ++index) { - char path[4200]; - if (snprintf(root, sizeof(root), "%s/%u", directory, index) >= (int)sizeof(root) || - snprintf(path, sizeof(path), "%s/time", root) >= (int)sizeof(path)) { - return 1; - } - FILE *file = fopen(path, "re"); - if (file == NULL) { - return index == 0; - } - unsigned long long now; - int parsed = fscanf(file, "%llu", &now); - fclose(file); - if (parsed != 1) { - return 1; - } - proc_root = root; - ipv6_dual_stack = 1; - read_bindv6only(); - size_t length = collect(FIRST_SOURCE_PORT, now); - printf("%llu ", now); - for (size_t offset = 0; offset < length; ++offset) { - printf("%02x", payload[offset]); - } - printf("\n"); - } -} - -int main(int argc, char **argv) -{ - if (argc == 3 && strcmp(argv[1], "test") == 0) { - return run_test(argv[2]); - } - struct sockaddr_in gateway = { - .sin_family = AF_INET, - .sin_port = htons(ANNOUNCEMENT_PORT), - }; - if (argc != 2 || inet_pton(AF_INET, argv[1], &gateway.sin_addr) != 1) { - fprintf(stderr, "usage: nvx-ingress GATEWAY_IPV4\n"); - return 2; - } - signal(SIGINT, SIG_IGN); - signal(SIGHUP, SIG_IGN); - read_bindv6only(); - - int sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (sock < 0) { - fprintf(stderr, "nvx-ingress: socket: %s\n", strerror(errno)); - return 1; - } - uint16_t own_port = 0; - for (int port = FIRST_SOURCE_PORT; port >= LAST_SOURCE_PORT && own_port == 0; --port) { - struct sockaddr_in source = { - .sin_family = AF_INET, - .sin_port = htons((uint16_t)port), - .sin_addr.s_addr = htonl(INADDR_ANY), - }; - if (bind(sock, (struct sockaddr *)&source, sizeof(source)) == 0) { - own_port = (uint16_t)port; - } else if (errno != EADDRINUSE) { - fprintf(stderr, "nvx-ingress: bind privileged port: %s\n", strerror(errno)); - return 1; - } - } - if (own_port == 0) { - fprintf(stderr, "nvx-ingress: no privileged source port is free\n"); - return 1; - } - - uint64_t last_sent = 0; - for (;;) { - uint64_t now = now_ms(); - size_t length = collect(own_port, now); - if (length != last_length || memcmp(payload, last_payload, length) != 0 || - now - last_sent >= REFRESH_INTERVAL_MS) { - if (sendto(sock, payload, length, 0, (struct sockaddr *)&gateway, - sizeof(gateway)) == (ssize_t)length) { - memcpy(last_payload, payload, length); - last_length = length; - last_sent = now; - } - } - sleep_ms(SCAN_INTERVAL_MS); - } -} diff --git a/guest/common/nvx-managed-agent.c b/guest/common/nvx-managed-agent.c deleted file mode 100644 index ac3b1e2b7..000000000 --- a/guest/common/nvx-managed-agent.c +++ /dev/null @@ -1,2431 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define OUTER_HEADER_LEN 44U -#define OUTER_MAX_PAYLOAD 65536U -#define OUTER_VERSION 1U -#define OUTER_GUEST_ATTACH 1U -#define OUTER_RESET 3U -#define OUTER_ACK 4U -#define OUTER_DATA 5U -#define OUTER_CREDIT 9U -#define OUTER_RECEIVE_WINDOW (1024U * 1024U) - -#define APP_HEADER_LEN 24U -#define APP_VERSION 1U -#define APP_PING 1U -#define APP_EXEC 2U -#define APP_STOP 3U -#define APP_CANCEL 4U -#define APP_FEATURES 5U -#define APP_MAPS 6U -#define APP_READY 0x81U -#define APP_STDOUT 0x82U -#define APP_STDERR 0x83U -#define APP_EXIT 0x84U -#define APP_STOPPED 0x85U -#define APP_ERROR 0xffU - -/* - * Control features that a host can rely on, advertised in the answer to - * FEATURES. The host refuses an image that lacks a feature it needs, so a - * behavior that a host depends on needs a bit here and in the host's copy - * (aci_edge_sandboxes, src/openvmm/protocol.rs). The init script of the image provides - * FEATURE_WORKLOAD_ACCOUNT; the agent speaks for it because both ship in one - * initramfs. An agent that predates FEATURES refuses the request as an - * unsupported operation, which the host reads as no features. Bit 1 announced - * bind mounts that nvx_map= kernel command-line tokens listed; it stays unused, - * so hosts that still send those tokens refuse this image. - */ -#define FEATURE_CANCEL (1U << 0) -#define FEATURE_WORKLOAD_ACCOUNT (1U << 2) -#define FEATURE_EXEC_CGROUP (1U << 3) -#define FEATURE_EXEC_ENVIRONMENT (1U << 4) -#define FEATURE_EXEC_CWD (1U << 5) -#define FEATURE_HOST_MAPPING_TABLE (1U << 6) - -#define MAX_ARGUMENTS 64U -#define MAX_ARGUMENT_LEN 4096U -#define MAX_ENVIRONMENT 256U -#define EXEC_EXTENDED 1U -#define EXEC_CWD_PRESENT 1U -#define EXEC_ENVIRONMENT_PRESENT 2U -#define EXEC_INHERIT_DEFAULT_ENV 4U -/* - * Set only in the configuration that a direct-mode child hands its launch - * helper, never on the wire: the child already entered the working directory, - * so the helper keeps it instead of looking the path up again. - */ -#define EXEC_CWD_ENTERED 0x8000U -#define MAX_OUTPUT_BYTES (1024U * 1024U) -#define OUTPUT_CHUNK_BYTES 32768U -#define CONTAINER_BARRIER_ATTEMPTS 500U -#define PORTB_CONSOLE 0xe9 -#define AGENT_STOPPED 1 -#ifndef CGROUP_ROOT -#define CGROUP_ROOT "/sys/fs/cgroup" -#endif -#define EXEC_CGROUP CGROUP_ROOT "/nvx-exec" -#ifndef HOSTFS_DIR -#define HOSTFS_DIR "/run/nvx/hostfs" -#endif -#define HOSTFS_ROOT HOSTFS_DIR "/root" -#define MAPS_TOKEN "nvx_maps=" -#define MAX_HOST_MAPPINGS 4096U -#define MAPS_HEADER_LEN 8U -#define MAP_ENTRY_HEADER_LEN 6U -#define MAP_READ_ONLY 1U -#define MAX_COMMAND_LINE 4096U -#define MAX_GUEST_PATH 4096U - -struct outer_record { - uint8_t type; - uint8_t instance_id[16]; - uint64_t epoch; - uint64_t sequence; - uint32_t payload_len; - uint8_t *payload; -}; - -struct control_session { - int fd; - uint8_t instance_id[16]; - uint64_t epoch; - uint64_t guest_sequence; - uint64_t host_sequence; -}; - -struct app_request { - uint8_t kind; - uint64_t request_id; - int32_t status; - uint32_t payload_len; - const uint8_t *payload; -}; - -struct agent_config { - const char *rootfs; - const char *hostname; - const char *uid; - const char *gid; - const char *user; - const char *home; - int direct; -}; - -struct exec_config { - char *cwd; - char **environment; - uint16_t environment_count; - int environment_present; - int inherit_default_env; -}; - -/* - * Host mappings that nvx_maps= announces and APP_MAPS requests deliver. Until - * every announced mapping is mounted, workloads are refused. - */ -struct host_mappings { - uint32_t expected; - uint32_t applied; - int failed; -}; - -#define LAUNCH_STAGE_CWD 1 -#define LAUNCH_STAGE_SETUP 2 - -/* What a direct-mode child reports when it fails before it executes setpriv. */ -struct launch_failure { - int32_t stage; - int32_t error; -}; - -static uint16_t read_u16(const uint8_t *bytes) -{ - return (uint16_t)bytes[0] | ((uint16_t)bytes[1] << 8); -} - -static uint32_t read_u32(const uint8_t *bytes) -{ - return (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8) | - ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24); -} - -static uint64_t read_u64(const uint8_t *bytes) -{ - uint64_t value = 0; - unsigned int index; - - for (index = 0; index < 8; ++index) { - value |= (uint64_t)bytes[index] << (index * 8); - } - return value; -} - -static void write_u16(uint8_t *bytes, uint16_t value) -{ - bytes[0] = (uint8_t)value; - bytes[1] = (uint8_t)(value >> 8); -} - -static void write_u32(uint8_t *bytes, uint32_t value) -{ - bytes[0] = (uint8_t)value; - bytes[1] = (uint8_t)(value >> 8); - bytes[2] = (uint8_t)(value >> 16); - bytes[3] = (uint8_t)(value >> 24); -} - -static void write_u64(uint8_t *bytes, uint64_t value) -{ - unsigned int index; - - for (index = 0; index < 8; ++index) { - bytes[index] = (uint8_t)(value >> (index * 8)); - } -} - -static int read_exact(int fd, void *buffer, size_t length) -{ - uint8_t *bytes = buffer; - size_t offset = 0; - - while (offset < length) { - ssize_t count = read(fd, bytes + offset, length - offset); - if (count > 0) { - offset += (size_t)count; - } else if (count == 0) { - return -1; - } else if (errno != EINTR) { - return -1; - } - } - return 0; -} - -static int write_all(int fd, const void *buffer, size_t length) -{ - const uint8_t *bytes = buffer; - size_t offset = 0; - - while (offset < length) { - ssize_t count = write(fd, bytes + offset, length - offset); - if (count > 0) { - offset += (size_t)count; - } else if (count < 0 && errno == EINTR) { - continue; - } else { - return -1; - } - } - return 0; -} - -static void portb_log(const char *message) -{ - int fd = open("/dev/port", O_WRONLY | O_CLOEXEC); - - if (fd < 0) { - return; - } - while (*message != '\0') { - if (pwrite(fd, message, 1, PORTB_CONSOLE) != 1) { - break; - } - ++message; - } - close(fd); -} - -static void portb_error(const char *stage, int status) -{ - char message[128]; - int length = snprintf( - message, - sizeof(message), - "NVX-MANAGED-ERROR: stage=%s status=%d\n", - stage, - status); - - if (length > 0 && (size_t)length < sizeof(message)) { - portb_log(message); - } -} - -static void free_outer_record(struct outer_record *record) -{ - free(record->payload); - record->payload = NULL; - record->payload_len = 0; -} - -static int read_outer_record(int fd, struct outer_record *record) -{ - uint8_t header[OUTER_HEADER_LEN]; - - memset(record, 0, sizeof(*record)); - if (read_exact(fd, header, sizeof(header)) != 0) { - return -1; - } - if (memcmp(header, "NVXS", 4) != 0 || read_u16(header + 4) != OUTER_VERSION || - header[7] != 0) { - return -1; - } - record->type = header[6]; - memcpy(record->instance_id, header + 8, sizeof(record->instance_id)); - record->epoch = read_u64(header + 24); - record->sequence = read_u64(header + 32); - record->payload_len = read_u32(header + 40); - if (record->payload_len > OUTER_MAX_PAYLOAD) { - return -1; - } - if (record->payload_len != 0) { - record->payload = malloc(record->payload_len); - if (record->payload == NULL || - read_exact(fd, record->payload, record->payload_len) != 0) { - free_outer_record(record); - return -1; - } - } - return 0; -} - -static int write_outer_record( - int fd, - uint8_t type, - const uint8_t instance_id[16], - uint64_t epoch, - uint64_t sequence, - const void *payload, - uint32_t payload_len) -{ - uint8_t header[OUTER_HEADER_LEN] = {0}; - - if (payload_len > OUTER_MAX_PAYLOAD) { - return -1; - } - memcpy(header, "NVXS", 4); - write_u16(header + 4, OUTER_VERSION); - header[6] = type; - memcpy(header + 8, instance_id, 16); - write_u64(header + 24, epoch); - write_u64(header + 32, sequence); - write_u32(header + 40, payload_len); - if (write_all(fd, header, sizeof(header)) != 0) { - return -1; - } - return payload_len == 0 || write_all(fd, payload, payload_len) == 0 ? 0 : -1; -} - -/* Accepts a /-separated path without empty, "." (unless alone), or ".." components. */ -static int safe_path(const char *path, int absolute) -{ - const char *component = path; - - if (absolute) { - if (path[0] != '/') { - return 0; - } - component = path + 1; - if (*component == '\0') { - return 0; - } - } else if (strcmp(path, ".") == 0) { - return 1; - } - while (*component != '\0') { - const char *end = strchr(component, '/'); - size_t length = end == NULL ? strlen(component) : (size_t)(end - component); - - if (length == 0 || (length == 1 && component[0] == '.') || - (length == 2 && component[0] == '.' && component[1] == '.')) { - return 0; - } - component += length; - if (*component == '/') { - ++component; - if (*component == '\0') { - return 0; - } - } - } - return 1; -} - -/* Creates every missing directory of `path` up to, and optionally including, its last component. */ -static int make_directories(const char *path, int include_last) -{ - char buffer[MAX_GUEST_PATH]; - size_t index; - size_t length = strlen(path); - - if (length >= sizeof(buffer)) { - return -1; - } - memcpy(buffer, path, length + 1); - for (index = 1; index <= length; ++index) { - if (buffer[index] == '/' || (buffer[index] == '\0' && include_last)) { - char saved = buffer[index]; - - buffer[index] = '\0'; - if (mkdir(buffer, 0755) != 0 && errno != EEXIST) { - return -1; - } - buffer[index] = saved; - } - } - return 0; -} - -/* - * Reads the number of host mappings that nvx_maps=COUNT announces into - * `count`, or 0 without the token. Tokens are split in place. Older hosts - * listed their mappings in nvx_map= tokens, which are ignored. - */ -static int parse_host_mapping_count(char *command_line, uint32_t *count) -{ - char *token; - char *cursor; - int found = 0; - - *count = 0; - for (token = strtok_r(command_line, " \t\n", &cursor); token != NULL; - token = strtok_r(NULL, " \t\n", &cursor)) { - const char *digit = token + strlen(MAPS_TOKEN); - uint32_t value = 0; - - if (strncmp(token, MAPS_TOKEN, strlen(MAPS_TOKEN)) != 0) { - continue; - } - if (found || *digit == '\0') { - errno = EINVAL; - return -1; - } - for (; *digit != '\0'; ++digit) { - if (*digit < '0' || *digit > '9' || value > MAX_HOST_MAPPINGS) { - errno = EINVAL; - return -1; - } - value = value * 10U + (uint32_t)(*digit - '0'); - } - if (value == 0 || value > MAX_HOST_MAPPINGS) { - errno = EINVAL; - return -1; - } - *count = value; - found = 1; - } - return 0; -} - -/* - * Reads the number of announced host mappings and, while the host's export is - * mounted at HOSTFS_ROOT, makes its parent a directory that only root may - * enter, so workloads reach mapped paths only through the bind mounts. The - * announced mappings need that export. - */ -static int prepare_host_mappings(struct host_mappings *mappings) -{ - char command_line[MAX_COMMAND_LINE]; - struct stat parent; - struct stat export; - ssize_t count; - int mounted; - int fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC); - - if (fd < 0) { - return -1; - } - count = read(fd, command_line, sizeof(command_line) - 1); - close(fd); - if (count < 0) { - return -1; - } - command_line[count] = '\0'; - if (parse_host_mapping_count(command_line, &mappings->expected) != 0) { - return -1; - } - mounted = stat(HOSTFS_DIR, &parent) == 0 && stat(HOSTFS_ROOT, &export) == 0 && - parent.st_dev != export.st_dev; - if (mounted && (chown(HOSTFS_DIR, 0, 0) != 0 || chmod(HOSTFS_DIR, 0700) != 0)) { - return -1; - } - if (mappings->expected != 0 && !mounted) { - errno = ENOENT; - return -1; - } - return 0; -} - -/* - * Bind-mounts SOURCE, a path relative to the export, at the guest path TARGET, - * read-only or read-write, without set-user-ID programs or device nodes. - */ -static int apply_host_mapping(const char *source, const char *target, int read_only) -{ - char host[MAX_GUEST_PATH + sizeof(HOSTFS_ROOT) + 1]; - struct stat status; - unsigned long flags = MS_BIND | MS_REMOUNT | MS_NOSUID | MS_NODEV; - - if (read_only) { - flags |= MS_RDONLY; - } - snprintf(host, sizeof(host), "%s/%s", HOSTFS_ROOT, source); - if (lstat(host, &status) != 0) { - return -1; - } - if (S_ISDIR(status.st_mode)) { - if (make_directories(target, 1) != 0) { - return -1; - } - } else if (S_ISREG(status.st_mode)) { - struct stat existing; - - if (make_directories(target, 0) != 0) { - return -1; - } - /* - * An existing target may lie inside an earlier read-only bind, where - * even O_CREAT with write access fails, so only a missing one is made. - */ - if (lstat(target, &existing) != 0) { - int file; - - if (errno != ENOENT) { - return -1; - } - file = open(target, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0644); - if (file < 0) { - return -1; - } - close(file); - } else if (!S_ISREG(existing.st_mode)) { - errno = EINVAL; - return -1; - } - } else { - errno = EINVAL; - return -1; - } - if (mount(host, target, NULL, MS_BIND, NULL) != 0 || - mount(NULL, target, NULL, flags, NULL) != 0) { - return -1; - } - return 0; -} - -/* - * Decodes the APP_MAPS entry at `*offset` and advances past it: 16-bit flags, - * source length, and target length, then the source, a relative path of the - * export, and the target, an absolute guest path. - */ -static int decode_host_mapping( - const uint8_t *payload, - uint32_t payload_len, - uint32_t *offset, - char *source, - char *target, - int *read_only) -{ - uint16_t flags; - uint16_t source_len; - uint16_t target_len; - - if (payload_len - *offset < MAP_ENTRY_HEADER_LEN) { - return -1; - } - flags = read_u16(payload + *offset); - source_len = read_u16(payload + *offset + 2); - target_len = read_u16(payload + *offset + 4); - *offset += MAP_ENTRY_HEADER_LEN; - if ((flags & ~MAP_READ_ONLY) != 0 || source_len == 0 || target_len == 0 || - source_len >= MAX_GUEST_PATH || target_len >= MAX_GUEST_PATH || - payload_len - *offset < (uint32_t)source_len + target_len) { - return -1; - } - memcpy(source, payload + *offset, source_len); - source[source_len] = '\0'; - *offset += source_len; - memcpy(target, payload + *offset, target_len); - target[target_len] = '\0'; - *offset += target_len; - if (strlen(source) != source_len || strlen(target) != target_len || - strcmp(source, ".") == 0 || !safe_path(source, 0) || !safe_path(target, 1)) { - return -1; - } - *read_only = (flags & MAP_READ_ONLY) != 0; - return 0; -} - -static int send_guest_attach(int fd) -{ - const uint8_t zero_instance[16] = {0}; - - return write_outer_record( - fd, OUTER_GUEST_ATTACH, zero_instance, 0, 0, NULL, 0); -} - -static int acknowledge_reset( - struct control_session *session, - const struct outer_record *record) -{ - uint8_t credit[4]; - - if (record->type != OUTER_RESET || record->payload_len != 0 || - record->epoch == 0) { - return -1; - } - memcpy(session->instance_id, record->instance_id, 16); - session->epoch = record->epoch; - session->guest_sequence = 0; - session->host_sequence = record->sequence + 1; - write_u32(credit, OUTER_RECEIVE_WINDOW); - if (write_outer_record( - session->fd, - OUTER_ACK, - session->instance_id, - session->epoch, - session->guest_sequence, - credit, - sizeof(credit)) != 0) { - return -1; - } - session->guest_sequence = 1; - return 0; -} - -static int send_guest_record( - struct control_session *session, - uint8_t type, - const void *payload, - uint32_t payload_len) -{ - if (write_outer_record( - session->fd, - type, - session->instance_id, - session->epoch, - session->guest_sequence, - payload, - payload_len) != 0) { - return -1; - } - ++session->guest_sequence; - return 0; -} - -static int send_credit(struct control_session *session, uint32_t bytes) -{ - uint8_t payload[4]; - - if (bytes == 0) { - return 0; - } - write_u32(payload, bytes); - return send_guest_record(session, OUTER_CREDIT, payload, sizeof(payload)); -} - -static int send_app_frame( - struct control_session *session, - uint8_t kind, - uint64_t request_id, - int32_t status, - const void *payload, - uint32_t payload_len) -{ - uint8_t *frame; - uint32_t frame_len; - int result; - - if (payload_len > OUTER_MAX_PAYLOAD - APP_HEADER_LEN) { - return -1; - } - frame_len = APP_HEADER_LEN + payload_len; - frame = malloc(frame_len); - if (frame == NULL) { - return -1; - } - memset(frame, 0, APP_HEADER_LEN); - memcpy(frame, "NVXC", 4); - frame[4] = APP_VERSION; - frame[5] = kind; - write_u64(frame + 8, request_id); - write_u32(frame + 16, (uint32_t)status); - write_u32(frame + 20, payload_len); - if (payload_len != 0) { - memcpy(frame + APP_HEADER_LEN, payload, payload_len); - } - result = send_guest_record(session, OUTER_DATA, frame, frame_len); - free(frame); - return result; -} - -static int parse_app_request( - const uint8_t *payload, - uint32_t payload_len, - struct app_request *request) -{ - uint32_t declared_len; - - if (payload_len < APP_HEADER_LEN || memcmp(payload, "NVXC", 4) != 0 || - payload[4] != APP_VERSION || read_u16(payload + 6) != 0) { - return -1; - } - declared_len = read_u32(payload + 20); - if (declared_len != payload_len - APP_HEADER_LEN) { - return -1; - } - request->kind = payload[5]; - request->request_id = read_u64(payload + 8); - request->status = (int32_t)read_u32(payload + 16); - request->payload_len = declared_len; - request->payload = payload + APP_HEADER_LEN; - return request->request_id != 0 && request->status == 0 ? 0 : -1; -} - -static int send_app_error( - struct control_session *session, - uint64_t request_id, - int32_t status, - const char *category) -{ - return send_app_frame( - session, - APP_ERROR, - request_id, - status, - category, - (uint32_t)strlen(category)); -} - -static uint64_t monotonic_milliseconds(void) -{ - struct timespec value; - - if (clock_gettime(CLOCK_MONOTONIC, &value) != 0) { - return 0; - } - return (uint64_t)value.tv_sec * 1000U + (uint64_t)value.tv_nsec / 1000000U; -} - -static int open_control_tty(const char *path) -{ - const struct timespec delay = { - .tv_sec = 0, - .tv_nsec = 50U * 1000U * 1000U, - }; - unsigned int attempt; - - for (attempt = 0; attempt < 600; ++attempt) { - int fd = open(path, O_RDWR | O_CLOEXEC); - if (fd >= 0) { - return fd; - } - if (errno != ENOENT && errno != ENXIO && errno != ENODEV && errno != EIO) { - return -1; - } - nanosleep(&delay, NULL); - } - errno = ETIMEDOUT; - return -1; -} - -static int configure_control_tty(int fd) -{ - struct termios settings; - - if (tcgetattr(fd, &settings) != 0) { - return -1; - } - cfmakeraw(&settings); - settings.c_cflag |= CLOCAL; - return tcsetattr(fd, TCSANOW, &settings); -} - -static int make_nonblocking(int fd) -{ - int flags = fcntl(fd, F_GETFL); - - return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0 ? 0 : -1; -} - -static int write_pid_to_cgroup(pid_t pid) -{ - char buffer[32]; - int fd; - int length; - - fd = open("/sys/fs/cgroup/container/cgroup.procs", O_WRONLY | O_CLOEXEC); - if (fd < 0) { - return -1; - } - length = snprintf(buffer, sizeof(buffer), "%ld\n", (long)pid); - if (length <= 0 || (size_t)length >= sizeof(buffer) || - write_all(fd, buffer, (size_t)length) != 0) { - close(fd); - return -1; - } - return close(fd); -} - -static int write_container_barrier(int fd) -{ - struct sigaction ignored = {0}; - struct sigaction previous; - int result; - int status; - - ignored.sa_handler = SIG_IGN; - if (sigemptyset(&ignored.sa_mask) != 0 || - sigaction(SIGPIPE, &ignored, &previous) != 0) { - return -1; - } - result = write_all(fd, "start\n", 6); - status = errno; - if (sigaction(SIGPIPE, &previous, NULL) != 0) { - return -1; - } - if (result != 0) { - errno = status; - } - return result; -} - -/* Returns 0 after writing, 1 for an unreaped child exit, or -1 on failure. */ -static int release_container_barrier(const char *path, pid_t child) -{ - unsigned int attempt; - - for (attempt = 0; attempt < CONTAINER_BARRIER_ATTEMPTS; ++attempt) { - int fd = open(path, O_WRONLY | O_CLOEXEC | O_NONBLOCK); - - if (fd >= 0) { - int result = write_container_barrier(fd); - - close(fd); - return result; - } - if (errno != ENXIO && errno != EINTR) { - return -1; - } - if (child > 0) { - siginfo_t information = {0}; - - if (waitid( - P_PID, - (id_t)child, - &information, - WEXITED | WNOHANG | WNOWAIT) != 0) { - if (errno == EINTR) { - continue; - } - return -1; - } - if (information.si_pid == child) { - return 1; - } - } - { - const struct timespec delay = {.tv_nsec = 10000000L}; - - nanosleep(&delay, NULL); - } - } - errno = ETIMEDOUT; - return -1; -} - -static int enable_pids_controller(void) -{ - int fd = open(CGROUP_ROOT "/cgroup.subtree_control", O_WRONLY | O_CLOEXEC); - int result; - int status; - - if (fd < 0) { - return -1; - } - result = write_all(fd, "+pids", 5); - status = errno; - if (close(fd) != 0 && result == 0) { - return -1; - } - errno = status; - return result; -} - -/* - * Direct-mode workloads run in the guest's own root file system, contained by - * a cgroup that every process of the workload inherits, whatever session or - * process group it moves to. Killing the cgroup ends all of them, and its pids - * controller counts each of them until PID 1 reaps it. - */ -static int prepare_exec_cgroup(void) -{ - struct stat status; - - if (stat(CGROUP_ROOT "/cgroup.procs", &status) != 0) { - if (mkdir(CGROUP_ROOT, 0755) != 0 && errno != EEXIST) { - return -1; - } - if (mount("cgroup2", CGROUP_ROOT, "cgroup2", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) != - 0) { - return -1; - } - } - if (mkdir(EXEC_CGROUP, 0755) != 0 && errno != EEXIST) { - return -1; - } - if (stat(EXEC_CGROUP "/pids.current", &status) == 0) { - return 0; - } - return errno == ENOENT ? enable_pids_controller() : -1; -} - -static int join_exec_cgroup(void) -{ - char buffer[32]; - int fd = open(EXEC_CGROUP "/cgroup.procs", O_WRONLY | O_CLOEXEC); - int length; - int result; - - if (fd < 0) { - return -1; - } - length = snprintf(buffer, sizeof(buffer), "%ld\n", (long)getpid()); - result = length > 0 && (size_t)length < sizeof(buffer) - ? write_all(fd, buffer, (size_t)length) - : -1; - close(fd); - return result; -} - -static int kill_exec_cgroup(void) -{ - int fd = open(EXEC_CGROUP "/cgroup.kill", O_WRONLY | O_CLOEXEC); - int result; - int status; - - if (fd < 0) { - goto error; - } - result = write_all(fd, "1", 1); - status = errno; - if (close(fd) != 0 && result == 0) { - goto error; - } - if (result == 0) { - return 0; - } - errno = status; -error: - status = errno; - portb_error("exec-cgroup-kill", status); - errno = status; - return -1; -} - -/* - * Returns 1 if the pids controller still charges a task to the exec cgroup, - * 0 if it charges none, or -1. A task stays charged until it is reaped, while - * cgroup.events stops counting a killed process as populated before the - * process even becomes a zombie. - */ -static int exec_cgroup_has_tasks(void) -{ - char buffer[32]; - size_t length = 0; - size_t index; - int fd = open(EXEC_CGROUP "/pids.current", O_RDONLY | O_CLOEXEC); - - if (fd < 0) { - return -1; - } - for (;;) { - ssize_t count = read(fd, buffer + length, sizeof(buffer) - length); - - if (count > 0) { - length += (size_t)count; - if (length < sizeof(buffer)) { - continue; - } - errno = EOVERFLOW; - } else if (count == 0) { - break; - } else if (errno == EINTR) { - continue; - } - { - int status = errno; - - close(fd); - errno = status; - return -1; - } - } - if (close(fd) != 0) { - return -1; - } - /* A decimal count without sign or leading zeros, then a newline. */ - if (length < 2 || buffer[length - 1] != '\n' || (buffer[0] == '0' && length != 2)) { - errno = EPROTO; - return -1; - } - for (index = 0; index + 1 < length; ++index) { - if (buffer[index] < '0' || buffer[index] > '9') { - errno = EPROTO; - return -1; - } - } - return buffer[0] != '0'; -} - -/* - * Reaps exited children without blocking. The agent runs as PID 1, so besides - * the workload's direct child it inherits every orphaned workload process. - */ -static void reap_children(pid_t child, int *child_exited, int *wait_status) -{ - for (;;) { - int status; - pid_t pid = waitpid(-1, &status, WNOHANG); - - if (pid < 0 && errno == EINTR) { - continue; - } - if (pid <= 0) { - return; - } - if (pid == child && !*child_exited) { - *child_exited = 1; - *wait_status = status; - } - } -} - -/* - * Reaps what the workload left to PID 1, then returns 0 if no task of the - * exec cgroup remains, 1 if one does, or -1 on failure. - */ -static int reap_exec_cgroup(void) -{ - int exited = 1; - int status = 0; - - reap_children(0, &exited, &status); - return exec_cgroup_has_tasks(); -} - -/* - * Kills what is left of a direct-mode workload and waits until it is gone: - * every process of the workload has exited and been reaped, so the next - * workload cannot find any of them in /proc. - */ -static int settle_exec_cgroup(void) -{ - const struct timespec delay = { - .tv_sec = 0, - .tv_nsec = 10U * 1000U * 1000U, - }; - unsigned int attempt; - - if (kill_exec_cgroup() != 0) { - return -1; - } - for (attempt = 0; attempt <= 200; ++attempt) { - int remaining = reap_exec_cgroup(); - - if (remaining < 0) { - return -1; - } - if (remaining == 0) { - return 0; - } - if (attempt == 200) { - break; - } - nanosleep(&delay, NULL); - } - errno = ETIMEDOUT; - return -1; -} - -/* Kills the workload: its process group, and in direct mode its whole cgroup. */ -static void terminate_workload(const struct agent_config *config, pid_t child) -{ - kill(-child, SIGKILL); - if (config->direct) { - (void)kill_exec_cgroup(); - } -} - -/* Parses a workload user or group ID: decimal, neither root nor the "unchanged" value -1. */ -static int parse_workload_id(const char *text, uint32_t *id) -{ - char *end = NULL; - unsigned long value; - - if (text[0] < '0' || text[0] > '9') { - errno = EINVAL; - return -1; - } - errno = 0; - value = strtoul(text, &end, 10); - if (errno != 0 || *end != '\0' || value == 0 || value >= UINT32_MAX) { - errno = EINVAL; - return -1; - } - *id = (uint32_t)value; - return 0; -} - -/* Tells the agent why a direct-mode child cannot launch its workload, then exits. */ -static void report_launch_failure(int launch_fd, int32_t stage, int error) -{ - struct launch_failure failure = {.stage = stage, .error = error}; - - (void)write_all(launch_fd, &failure, sizeof(failure)); - _exit(125); -} - -/* - * Enters the working directory with the workload's IDs, no supplementary - * groups, and no effective capabilities, as setpriv will run the workload, so - * a directory that the workload cannot search is refused before anything - * runs, instead of failing in the launch helper with an exit status that the - * workload could also return. The real and saved IDs stay root, so the child - * regains root for setpriv afterwards. setpriv and the launch helper inherit - * the directory instead of looking its path up again, so the workload starts - * in the directory that was checked even if the path is renamed, replaced, or - * retargeted in the meantime. - */ -static void enter_workload_directory( - const struct agent_config *config, - const char *directory, - int launch_fd) -{ - uint32_t uid; - uint32_t gid; - - if (parse_workload_id(config->uid, &uid) != 0 || - parse_workload_id(config->gid, &gid) != 0 || setgroups(0, NULL) != 0 || - setegid((gid_t)gid) != 0 || seteuid((uid_t)uid) != 0) { - report_launch_failure(launch_fd, LAUNCH_STAGE_SETUP, errno); - } - if (chdir(directory) != 0) { - report_launch_failure(launch_fd, LAUNCH_STAGE_CWD, errno); - } - if (seteuid(0) != 0 || setegid(0) != 0) { - report_launch_failure(launch_fd, LAUNCH_STAGE_SETUP, errno); - } -} - -/* - * Points PWD at the working directory, so a shell reports it as the caller - * named it, symbolic links included. A path with empty, ".", or ".." - * components is replaced by the directory's physical path. - */ -static int export_working_directory(const char *directory) -{ - char physical[MAX_GUEST_PATH]; - - if (strcmp(directory, "/") == 0 || safe_path(directory, 1)) { - return setenv("PWD", directory, 1); - } - if (getcwd(physical, sizeof(physical)) == NULL) { - return -1; - } - return setenv("PWD", physical, 1); -} - -static void exec_direct( - const struct agent_config *config, - const char *config_fd, - char *const workload_argv[]) -{ - char *arguments[MAX_ARGUMENTS + 20]; - size_t index = 0; - size_t workload_index = 0; - - if (join_exec_cgroup() != 0) { - _exit(125); - } - if (setenv("HOME", config->home, 1) != 0 || - setenv("USER", config->user, 1) != 0 || - setenv("LOGNAME", config->user, 1) != 0) { - dprintf( - STDERR_FILENO, - "nvx-managed-agent: cannot configure workload environment: %s\n", - strerror(errno)); - _exit(125); - } - arguments[index++] = "setpriv"; - arguments[index++] = "--reuid"; - arguments[index++] = (char *)config->uid; - arguments[index++] = "--regid"; - arguments[index++] = (char *)config->gid; - arguments[index++] = "--clear-groups"; - arguments[index++] = "--no-new-privs"; - arguments[index++] = "--bounding-set=-all"; - arguments[index++] = "--inh-caps=-all"; - arguments[index++] = "--ambient-caps=-all"; - arguments[index++] = "/sbin/nvx-managed-agent"; - arguments[index++] = "--exec-config-fd"; - arguments[index++] = (char *)config_fd; - arguments[index++] = "--"; - while (workload_argv[workload_index] != NULL && index + 1 < MAX_ARGUMENTS + 20) { - arguments[index++] = workload_argv[workload_index++]; - } - arguments[index] = NULL; - execvp("setpriv", arguments); - _exit(125); -} - -static void exec_sandbox( - const struct agent_config *config, - const char *barrier, - const char *config_fd, - char *const workload_argv[]) -{ - char *arguments[MAX_ARGUMENTS + 10]; - size_t index = 0; - size_t workload_index = 0; - - arguments[index++] = "/sbin/nvx-container-launch"; - arguments[index++] = (char *)barrier; - arguments[index++] = (char *)config->rootfs; - arguments[index++] = (char *)config->hostname; - arguments[index++] = (char *)config->uid; - arguments[index++] = (char *)config->gid; - arguments[index++] = (char *)config->user; - arguments[index++] = (char *)config->home; - if (setenv("NVX_EXEC_CONFIG_FD", config_fd, 1) != 0) { - dprintf( - STDERR_FILENO, - "nvx-managed-agent: cannot export execution configuration: %s\n", - strerror(errno)); - _exit(125); - } - while (workload_argv[workload_index] != NULL && index + 1 < MAX_ARGUMENTS + 10) { - arguments[index++] = workload_argv[workload_index++]; - } - arguments[index] = NULL; - execv(arguments[0], arguments); - _exit(125); -} - -static int decode_exec_payload( - const uint8_t *payload, - uint32_t payload_len, - uint32_t *timeout_ms, - char ***workload_argv, - struct exec_config *config) -{ - uint16_t argc; - uint16_t extension; - uint16_t flags = 0; - uint16_t environment_count = 0; - uint32_t cwd_len = 0; - uint32_t offset = 8; - char **arguments; - uint16_t index; - - memset(config, 0, sizeof(*config)); - if (payload_len < 8) { - return -1; - } - *timeout_ms = read_u32(payload); - argc = read_u16(payload + 4); - extension = read_u16(payload + 6); - if (argc == 0 || argc > MAX_ARGUMENTS) { - return -1; - } - if (extension == EXEC_EXTENDED) { - if (payload_len < 16) { - return -1; - } - flags = read_u16(payload + 8); - environment_count = read_u16(payload + 10); - cwd_len = read_u32(payload + 12); - offset = 16; - if ((flags & - ~(EXEC_CWD_PRESENT | EXEC_ENVIRONMENT_PRESENT | - EXEC_INHERIT_DEFAULT_ENV)) != 0 || - ((flags & EXEC_CWD_PRESENT) == 0 && cwd_len != 0) || - ((flags & EXEC_CWD_PRESENT) != 0 && - (cwd_len == 0 || cwd_len > MAX_ARGUMENT_LEN)) || - ((flags & EXEC_ENVIRONMENT_PRESENT) == 0 && - (environment_count != 0 || - (flags & EXEC_INHERIT_DEFAULT_ENV) != 0)) || - environment_count > MAX_ENVIRONMENT) { - return -1; - } - } else if (extension != 0) { - return -1; - } - arguments = calloc((size_t)argc + 1, sizeof(*arguments)); - if (arguments == NULL) { - return -1; - } - for (index = 0; index < argc; ++index) { - uint32_t length; - - if (offset > payload_len || payload_len - offset < 4) { - goto fail; - } - length = read_u32(payload + offset); - offset += 4; - if (length == 0 || length > MAX_ARGUMENT_LEN || length > payload_len - offset || - memchr(payload + offset, '\0', length) != NULL) { - goto fail; - } - arguments[index] = malloc((size_t)length + 1); - if (arguments[index] == NULL) { - goto fail; - } - memcpy(arguments[index], payload + offset, length); - arguments[index][length] = '\0'; - offset += length; - } - if (arguments[0][0] != '/' || - (extension == 0 && offset != payload_len)) { - goto fail; - } - if (extension == EXEC_EXTENDED) { - if (cwd_len != 0) { - if (cwd_len > payload_len - offset || - memchr(payload + offset, '\0', cwd_len) != NULL || - payload[offset] != '/') { - goto fail; - } - config->cwd = malloc((size_t)cwd_len + 1); - if (config->cwd == NULL) { - goto fail; - } - memcpy(config->cwd, payload + offset, cwd_len); - config->cwd[cwd_len] = '\0'; - offset += cwd_len; - } - if ((flags & EXEC_ENVIRONMENT_PRESENT) != 0) { - config->environment = calloc( - (size_t)environment_count + 1, sizeof(*config->environment)); - if (config->environment == NULL) { - goto fail; - } - config->environment_present = 1; - config->inherit_default_env = - (flags & EXEC_INHERIT_DEFAULT_ENV) != 0; - config->environment_count = environment_count; - for (index = 0; index < environment_count; ++index) { - uint32_t length; - const uint8_t *equals; - - if (offset > payload_len || payload_len - offset < 4) { - goto fail; - } - length = read_u32(payload + offset); - offset += 4; - if (length == 0 || length > MAX_ARGUMENT_LEN || - length > payload_len - offset || - memchr(payload + offset, '\0', length) != NULL) { - goto fail; - } - equals = memchr(payload + offset, '=', length); - if (equals == NULL || equals == payload + offset) { - goto fail; - } - for (uint16_t previous = 0; previous < index; ++previous) { - size_t name_length = (size_t)(equals - (payload + offset)); - const char *prior = config->environment[previous]; - if (strcspn(prior, "=") == name_length && - memcmp(prior, payload + offset, name_length) == 0) { - goto fail; - } - } - config->environment[index] = malloc((size_t)length + 1); - if (config->environment[index] == NULL) { - goto fail; - } - memcpy(config->environment[index], payload + offset, length); - config->environment[index][length] = '\0'; - offset += length; - } - } - if (offset != payload_len) { - goto fail; - } - } - *workload_argv = arguments; - return 0; - -fail: - for (index = 0; index < argc; ++index) { - free(arguments[index]); - } - free(arguments); - free(config->cwd); - config->cwd = NULL; - if (config->environment != NULL) { - for (index = 0; index < config->environment_count; ++index) { - free(config->environment[index]); - } - free(config->environment); - config->environment = NULL; - } - return -1; -} - -static void free_arguments(char **arguments) -{ - size_t index; - - if (arguments == NULL) { - return; - } - for (index = 0; arguments[index] != NULL; ++index) { - free(arguments[index]); - } - free(arguments); -} - -static void free_exec_config(struct exec_config *config) -{ - uint16_t index; - - free(config->cwd); - for (index = 0; index < config->environment_count; ++index) { - free(config->environment[index]); - } - free(config->environment); - memset(config, 0, sizeof(*config)); -} - -/* - * Writes the launch helper's configuration. When the caller has entered the - * working directory already, the configuration says so instead of naming it. - */ -static int write_exec_config( - int fd, - const struct exec_config *config, - int cwd_entered) -{ - uint8_t header[8]; - uint16_t flags = 0; - const char *cwd = cwd_entered ? NULL : config->cwd; - uint16_t index; - - if (cwd_entered) { - flags |= EXEC_CWD_ENTERED; - } else if (cwd != NULL) { - flags |= EXEC_CWD_PRESENT; - } - if (config->environment_present) { - flags |= EXEC_ENVIRONMENT_PRESENT; - } - if (config->inherit_default_env) { - flags |= EXEC_INHERIT_DEFAULT_ENV; - } - write_u16(header, flags); - write_u16(header + 2, config->environment_count); - write_u32(header + 4, cwd == NULL ? 0 : (uint32_t)strlen(cwd)); - if (write_all(fd, header, sizeof(header)) != 0 || - (cwd != NULL && write_all(fd, cwd, strlen(cwd)) != 0)) { - return -1; - } - for (index = 0; index < config->environment_count; ++index) { - uint32_t length = (uint32_t)strlen(config->environment[index]); - uint8_t encoded_length[4]; - - write_u32(encoded_length, length); - if (write_all(fd, encoded_length, sizeof(encoded_length)) != 0 || - write_all(fd, config->environment[index], length) != 0) { - return -1; - } - } - return 0; -} - -static int create_exec_config_fd(const struct exec_config *config, int cwd_entered) -{ - int fd = memfd_create("nvx-exec-config", MFD_CLOEXEC | MFD_ALLOW_SEALING); - - if (fd < 0) { - return -1; - } - /* A sealed, bounded anonymous file avoids depending on pipe capacity. */ - if (write_exec_config(fd, config, cwd_entered) != 0 || - lseek(fd, 0, SEEK_SET) < 0 || - fcntl(fd, F_ADD_SEALS, - F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL) != 0) { - close(fd); - return -1; - } - return fd; -} - -static int launch_workload(int argc, char **argv) -{ - char *end = NULL; - long descriptor; - uint8_t header[8]; - uint16_t flags; - uint16_t environment_count; - uint32_t cwd_len; - char *cwd = NULL; - char **environment = NULL; - uint16_t index; - - if (argc < 5 || strcmp(argv[1], "--exec-config-fd") != 0 || - strcmp(argv[3], "--") != 0 || argv[4][0] != '/') { - return 125; - } - errno = 0; - descriptor = strtol(argv[2], &end, 10); - if (errno != 0 || end == argv[2] || *end != '\0' || - descriptor < 0 || descriptor > INT32_MAX || - read_exact((int)descriptor, header, sizeof(header)) != 0) { - return 125; - } - flags = read_u16(header); - environment_count = read_u16(header + 2); - cwd_len = read_u32(header + 4); - if ((flags & - ~(EXEC_CWD_PRESENT | EXEC_ENVIRONMENT_PRESENT | - EXEC_INHERIT_DEFAULT_ENV | EXEC_CWD_ENTERED)) != 0 || - ((flags & EXEC_CWD_ENTERED) != 0 && (flags & EXEC_CWD_PRESENT) != 0) || - environment_count > MAX_ENVIRONMENT || - ((flags & EXEC_CWD_PRESENT) == 0 && cwd_len != 0) || - ((flags & EXEC_CWD_PRESENT) != 0 && - (cwd_len == 0 || cwd_len > MAX_ARGUMENT_LEN)) || - ((flags & EXEC_ENVIRONMENT_PRESENT) == 0 && - (environment_count != 0 || - (flags & EXEC_INHERIT_DEFAULT_ENV) != 0))) { - return 125; - } - if (cwd_len != 0) { - cwd = malloc((size_t)cwd_len + 1); - if (cwd == NULL || read_exact((int)descriptor, cwd, cwd_len) != 0) { - free(cwd); - return 125; - } - cwd[cwd_len] = '\0'; - } - environment = calloc( - (size_t)environment_count + 1, sizeof(*environment)); - if (environment == NULL) { - free(cwd); - return 125; - } - for (index = 0; index < environment_count; ++index) { - uint8_t encoded_length[4]; - uint32_t length; - - if (read_exact((int)descriptor, encoded_length, 4) != 0) { - goto fail; - } - length = read_u32(encoded_length); - if (length == 0 || length > MAX_ARGUMENT_LEN) { - goto fail; - } - environment[index] = malloc((size_t)length + 1); - if (environment[index] == NULL || - read_exact((int)descriptor, environment[index], length) != 0) { - goto fail; - } - if (memchr(environment[index], '\0', length) != NULL || - environment[index][0] == '=' || - memchr(environment[index], '=', length) == NULL) { - goto fail; - } - environment[index][length] = '\0'; - } - close((int)descriptor); - if (cwd != NULL && - (cwd[0] != '/' || memchr(cwd, '\0', cwd_len) != NULL)) { - dprintf(STDERR_FILENO, "nvx-managed-agent: invalid working directory\n"); - goto fail; - } - if (unsetenv("NVX_EXEC_CONFIG_FD") != 0) { - goto fail; - } - if ((flags & EXEC_CWD_ENTERED) == 0) { - const char *directory = cwd == NULL ? "/" : cwd; - - if (chdir(directory) != 0) { - dprintf( - STDERR_FILENO, - "nvx-managed-agent: cannot use working directory %s: %s\n", - directory, - strerror(errno)); - goto fail; - } - if (((flags & EXEC_ENVIRONMENT_PRESENT) == 0 || - (flags & EXEC_INHERIT_DEFAULT_ENV) != 0) && - export_working_directory(directory) != 0) { - dprintf( - STDERR_FILENO, - "nvx-managed-agent: cannot configure working directory " - "environment: %s\n", - strerror(errno)); - goto fail; - } - } - if ((flags & EXEC_ENVIRONMENT_PRESENT) != 0) { - if ((flags & EXEC_INHERIT_DEFAULT_ENV) == 0 && clearenv() != 0) { - goto fail; - } - for (index = 0; index < environment_count; ++index) { - if (putenv(environment[index]) != 0) { - goto fail; - } - environment[index] = NULL; - } - } - execv(argv[4], &argv[4]); - dprintf( - STDERR_FILENO, - "nvx-managed-agent: cannot execute workload: %s\n", - strerror(errno)); - -fail: - free(cwd); - for (index = 0; index < environment_count; ++index) { - free(environment[index]); - } - free(environment); - return 125; -} - -static int stream_output( - struct control_session *session, - uint64_t request_id, - int fd, - uint8_t kind, - size_t *output_bytes) -{ - uint8_t buffer[OUTPUT_CHUNK_BYTES]; - ssize_t count; - - for (;;) { - count = read(fd, buffer, sizeof(buffer)); - if (count > 0) { - if (*output_bytes > MAX_OUTPUT_BYTES - (size_t)count) { - return -2; - } - *output_bytes += (size_t)count; - if (send_app_frame( - session, - kind, - request_id, - 0, - buffer, - (uint32_t)count) != 0) { - return -1; - } - } else if (count == 0) { - return 1; - } else if (errno == EINTR) { - continue; - } else if (errno == EAGAIN || errno == EWOULDBLOCK) { - return 0; - } else { - return -1; - } - } -} - -/* Outcome of control traffic observed while a workload runs. */ -enum exec_control { - EXEC_CONTROL_NONE, - EXEC_CONTROL_CANCEL, - EXEC_CONTROL_SESSION_LOST, - EXEC_CONTROL_FAILED, -}; - -/* - * Handles one control record that arrives while the workload with request ID - * `exec_id` runs. CANCEL for that workload asks for its termination. A RESET - * means the host that could observe the workload is gone: the broker has - * started a new epoch and drops anything sent for the old one. Other requests - * are refused, because executions are sequential. - */ -static enum exec_control handle_exec_control( - struct control_session *session, - uint64_t exec_id) -{ - struct outer_record record; - struct app_request request; - enum exec_control result = EXEC_CONTROL_NONE; - - if (read_outer_record(session->fd, &record) != 0) { - portb_error("exec-outer-read", errno); - return EXEC_CONTROL_FAILED; - } - if (record.type == OUTER_RESET) { - result = acknowledge_reset(session, &record) == 0 - ? EXEC_CONTROL_SESSION_LOST - : EXEC_CONTROL_FAILED; - free_outer_record(&record); - return result; - } - if (record.type != OUTER_DATA || record.sequence != session->host_sequence || - record.epoch != session->epoch || - memcmp(record.instance_id, session->instance_id, 16) != 0 || - parse_app_request(record.payload, record.payload_len, &request) != 0) { - portb_error("exec-record", record.type); - free_outer_record(&record); - return EXEC_CONTROL_FAILED; - } - ++session->host_sequence; - if (send_credit(session, record.payload_len) != 0) { - free_outer_record(&record); - return EXEC_CONTROL_FAILED; - } - if (request.kind == APP_CANCEL) { - if (request.payload_len == 0 && request.request_id == exec_id) { - result = EXEC_CONTROL_CANCEL; - } - } else if (send_app_error(session, request.request_id, 16, "busy") != 0) { - result = EXEC_CONTROL_FAILED; - } - free_outer_record(&record); - return result; -} - -/* Discards output of a workload whose host is gone; returns 1 at end-of-file. */ -static int drain_output(int fd) -{ - uint8_t buffer[OUTPUT_CHUNK_BYTES]; - ssize_t count; - - for (;;) { - count = read(fd, buffer, sizeof(buffer)); - if (count > 0) { - continue; - } - if (count == 0) { - return 1; - } - if (errno == EINTR) { - continue; - } - return errno == EAGAIN || errno == EWOULDBLOCK ? 0 : -1; - } -} - -/* - * Waits until a direct-mode child either executes setpriv, which closes the - * close-on-exec launch pipe, or reports why it cannot launch the workload. - * Returns 0 once setpriv runs, 1 with `failure` filled in, or -1 on error. - */ -static int read_launch_failure(int fd, struct launch_failure *failure) -{ - uint8_t buffer[sizeof(*failure)]; - size_t length = 0; - - while (length < sizeof(buffer)) { - ssize_t count = read(fd, buffer + length, sizeof(buffer) - length); - - if (count > 0) { - length += (size_t)count; - } else if (count == 0) { - break; - } else if (errno != EINTR) { - return -1; - } - } - if (length == 0) { - return 0; - } - if (length != sizeof(buffer)) { - errno = EPROTO; - return -1; - } - memcpy(failure, buffer, sizeof(*failure)); - return 1; -} - -/* - * Refuses a workload whose working directory it cannot enter. Nothing of the - * workload ran; a diagnostic on its standard error names the directory, and - * the refusal carries the error number. - */ -static int refuse_working_directory( - struct control_session *session, - uint64_t request_id, - const char *directory, - int error) -{ - char message[MAX_GUEST_PATH + 128]; - int length = snprintf( - message, - sizeof(message), - "nvx-managed-agent: cannot enter working directory %s: %s\n", - directory, - strerror(error)); - - if (length > 0 && (size_t)length < sizeof(message) && - send_app_frame( - session, APP_STDERR, request_id, 0, message, (uint32_t)length) != 0) { - return -1; - } - return send_app_error(session, request_id, error, "cwd-failed"); -} - -static void close_pipe(int pipe_fds[2]) -{ - unsigned int index; - - for (index = 0; index < 2; ++index) { - if (pipe_fds[index] >= 0) { - close(pipe_fds[index]); - pipe_fds[index] = -1; - } - } -} - -static int run_exec( - struct control_session *session, - const struct agent_config *config, - uint64_t request_id, - uint32_t timeout_ms, - char **workload_argv, - const struct exec_config *exec_config) -{ - const char *barrier = "/run/nvx/managed-container-start"; - int stdout_pipe[2] = {-1, -1}; - int stderr_pipe[2] = {-1, -1}; - int exec_config_fd = -1; - int launch_pipe[2] = {-1, -1}; - struct launch_failure launch_failure = {0}; - int launch_report = 0; - pid_t child; - uint64_t started; - size_t output_bytes = 0; - int stdout_open = 1; - int stderr_open = 1; - int timed_out = 0; - int output_limited = 0; - int cancelled = 0; - int session_lost = 0; - int stragglers_killed = 0; - int wait_status = 0; - int child_exited = 0; - int launch_failed = 0; - - if (config->direct && prepare_exec_cgroup() != 0) { - portb_error("exec-cgroup", errno); - return send_app_error(session, request_id, 125, "launch-failed"); - } - if (config->direct) { - /* A workload whose settlement timed out may have exited since. */ - int remaining = reap_exec_cgroup(); - - if (remaining != 0) { - portb_error("exec-cgroup-verify", remaining < 0 ? errno : EBUSY); - return send_app_error(session, request_id, 125, "containment-failed"); - } - } - if (!config->direct) { - unlink(barrier); - if (mkfifo(barrier, 0600) != 0) { - return send_app_error(session, request_id, 125, "launch-failed"); - } - } - if (pipe2(stdout_pipe, O_CLOEXEC) != 0 || - pipe2(stderr_pipe, O_CLOEXEC) != 0 || - (exec_config_fd = create_exec_config_fd(exec_config, config->direct)) < 0 || - (config->direct && pipe2(launch_pipe, O_CLOEXEC) != 0)) { - unlink(barrier); - if (stdout_pipe[0] >= 0) { - close(stdout_pipe[0]); - close(stdout_pipe[1]); - } - if (stderr_pipe[0] >= 0) { - close(stderr_pipe[0]); - close(stderr_pipe[1]); - } - if (exec_config_fd >= 0) { - close(exec_config_fd); - } - return send_app_error(session, request_id, 125, "launch-failed"); - } - - child = fork(); - if (child < 0) { - close(stdout_pipe[0]); - close(stdout_pipe[1]); - close(stderr_pipe[0]); - close(stderr_pipe[1]); - close(exec_config_fd); - close_pipe(launch_pipe); - unlink(barrier); - return send_app_error(session, request_id, 125, "launch-failed"); - } - if (child == 0) { - int null_fd; - char config_fd[32]; - - setpgid(0, 0); - if (fcntl(exec_config_fd, F_SETFD, 0) != 0 || - snprintf( - config_fd, sizeof(config_fd), "%d", exec_config_fd) <= 0) { - _exit(125); - } - close(stdout_pipe[0]); - close(stderr_pipe[0]); - null_fd = open("/dev/null", O_RDONLY); - if (null_fd >= 0) { - dup2(null_fd, STDIN_FILENO); - close(null_fd); - } - dup2(stdout_pipe[1], STDOUT_FILENO); - dup2(stderr_pipe[1], STDERR_FILENO); - close(stdout_pipe[1]); - close(stderr_pipe[1]); - if (config->direct) { - const char *directory = - exec_config->cwd == NULL ? "/" : exec_config->cwd; - - close(launch_pipe[0]); - enter_workload_directory(config, directory, launch_pipe[1]); - /* - * A request that replaces the environment controls PWD itself; - * one that layers entries over the default keeps this PWD unless - * an entry names it. - */ - if ((!exec_config->environment_present || - exec_config->inherit_default_env) && - export_working_directory(directory) != 0) { - report_launch_failure(launch_pipe[1], LAUNCH_STAGE_SETUP, errno); - } - exec_direct(config, config_fd, workload_argv); - } - exec_sandbox(config, barrier, config_fd, workload_argv); - } - - setpgid(child, child); - close(exec_config_fd); - close(stdout_pipe[1]); - close(stderr_pipe[1]); - /* - * Only the child writes to the launch pipe, and it closes its end when it - * executes setpriv. Its report is read once it has exited, so the agent - * keeps serving control traffic while the child enters the directory. - */ - if (config->direct) { - close(launch_pipe[1]); - launch_pipe[1] = -1; - } - launch_failed = make_nonblocking(stdout_pipe[0]) != 0 || - make_nonblocking(stderr_pipe[0]) != 0; - if (!launch_failed && !config->direct) { - int barrier_result; - - if (write_pid_to_cgroup(child) != 0) { - launch_failed = 1; - } else { - barrier_result = release_container_barrier(barrier, child); - launch_failed = barrier_result < 0; - } - } - if (launch_failed) { - terminate_workload(config, child); - waitpid(child, NULL, 0); - close(stdout_pipe[0]); - close(stderr_pipe[0]); - close_pipe(launch_pipe); - unlink(barrier); - return send_app_error(session, request_id, 125, "launch-failed"); - } - unlink(barrier); - started = monotonic_milliseconds(); - - while (!child_exited || stdout_open || stderr_open) { - struct pollfd descriptors[3]; - int poll_result; - int stdout_result = 0; - int stderr_result = 0; - - descriptors[0].fd = stdout_open ? stdout_pipe[0] : -1; - descriptors[0].events = POLLIN | POLLHUP; - descriptors[0].revents = 0; - descriptors[1].fd = stderr_open ? stderr_pipe[0] : -1; - descriptors[1].events = POLLIN | POLLHUP; - descriptors[1].revents = 0; - /* After a reset the old session is gone; later records belong to the main loop. */ - descriptors[2].fd = session_lost ? -1 : session->fd; - descriptors[2].events = POLLIN; - descriptors[2].revents = 0; - poll_result = poll(descriptors, 3, 25); - if (poll_result < 0 && errno != EINTR) { - terminate_workload(config, child); - } - if (!session_lost && descriptors[2].revents != 0) { - switch (handle_exec_control(session, request_id)) { - case EXEC_CONTROL_NONE: - break; - case EXEC_CONTROL_CANCEL: - if (!child_exited && !cancelled) { - cancelled = 1; - terminate_workload(config, child); - } - break; - case EXEC_CONTROL_SESSION_LOST: - session_lost = 1; - terminate_workload(config, child); - break; - case EXEC_CONTROL_FAILED: - terminate_workload(config, child); - waitpid(child, NULL, 0); - if (stdout_open) { - close(stdout_pipe[0]); - } - if (stderr_open) { - close(stderr_pipe[0]); - } - close_pipe(launch_pipe); - return -1; - } - } - if (stdout_open && descriptors[0].revents != 0) { - stdout_result = session_lost - ? drain_output(stdout_pipe[0]) - : stream_output( - session, - request_id, - stdout_pipe[0], - APP_STDOUT, - &output_bytes); - } - if (stderr_open && descriptors[1].revents != 0) { - stderr_result = session_lost - ? drain_output(stderr_pipe[0]) - : stream_output( - session, - request_id, - stderr_pipe[0], - APP_STDERR, - &output_bytes); - } - if (stdout_result == 1) { - close(stdout_pipe[0]); - stdout_open = 0; - } - if (stderr_result == 1) { - close(stderr_pipe[0]); - stderr_open = 0; - } - if (stdout_result < 0 || stderr_result < 0) { - output_limited = stdout_result == -2 || stderr_result == -2; - terminate_workload(config, child); - } - reap_children(child, &child_exited, &wait_status); - /* Processes the workload left behind must not hold its output open. */ - if (child_exited && config->direct && !stragglers_killed && - (stdout_open || stderr_open)) { - stragglers_killed = 1; - (void)kill_exec_cgroup(); - } - if (!child_exited && timeout_ms != 0 && - monotonic_milliseconds() - started >= timeout_ms) { - timed_out = 1; - terminate_workload(config, child); - } - if ((timed_out || output_limited || cancelled || session_lost) && !child_exited) { - if (waitpid(child, &wait_status, 0) == child) { - child_exited = 1; - } - } - } - - /* The child has exited, so its launch report, if any, is complete. */ - if (config->direct) { - launch_report = read_launch_failure(launch_pipe[0], &launch_failure); - if (launch_report < 0) { - portb_error("exec-launch-report", errno); - } - close_pipe(launch_pipe); - } - if (config->direct && settle_exec_cgroup() != 0) { - portb_error("exec-cgroup-settle", errno); - return session_lost - ? 0 - : send_app_error(session, request_id, 125, "containment-failed"); - } - if (session_lost) { - /* Nothing may be sent for the abandoned exec once the new epoch is acknowledged. */ - return 0; - } - /* A child that reported a launch failure exited without running anything. */ - if (launch_report > 0 && launch_failure.stage == LAUNCH_STAGE_CWD) { - return refuse_working_directory( - session, - request_id, - exec_config->cwd == NULL ? "/" : exec_config->cwd, - launch_failure.error); - } - if (launch_report > 0) { - portb_error("exec-launch", launch_failure.error); - return send_app_error(session, request_id, 125, "launch-failed"); - } - if (cancelled) { - return send_app_frame( - session, APP_EXIT, request_id, 128 + SIGKILL, "cancelled", 9); - } - if (timed_out) { - return send_app_frame( - session, APP_EXIT, request_id, 124, "timeout", 7); - } - if (output_limited) { - return send_app_frame( - session, APP_EXIT, request_id, 125, "output-limit", 12); - } - if (WIFEXITED(wait_status)) { - return send_app_frame( - session, - APP_EXIT, - request_id, - WEXITSTATUS(wait_status), - "exit", - 4); - } - if (WIFSIGNALED(wait_status)) { - int status = 128 + WTERMSIG(wait_status); - return send_app_frame( - session, APP_EXIT, request_id, status, "signal", 6); - } - return send_app_frame( - session, APP_EXIT, request_id, 125, "failed", 6); -} - -/* - * Direct mode alone sets up host mappings, gives each workload a cgroup, and - * refuses a working directory that the workload cannot enter as cwd-failed. - */ -static uint32_t agent_features(const struct agent_config *config) -{ - uint32_t features = - FEATURE_CANCEL | FEATURE_WORKLOAD_ACCOUNT | FEATURE_EXEC_ENVIRONMENT; - - if (config->direct) { - features |= FEATURE_HOST_MAPPING_TABLE | FEATURE_EXEC_CGROUP | FEATURE_EXEC_CWD; - } - return features; -} - -/* - * Mounts the entries FIRST to FIRST + COUNT - 1 of the host mapping table that - * an APP_MAPS request carries after its 32-bit FIRST and COUNT. Entries arrive - * in order and only up to the number that nvx_maps= announced, before any - * workload runs. The whole request is checked before anything is mounted, and - * a mapping that fails to mount fails every later workload. - */ -static int handle_host_mappings( - struct control_session *session, - struct host_mappings *mappings, - const struct app_request *request) -{ - char source[MAX_GUEST_PATH]; - char target[MAX_GUEST_PATH]; - uint32_t first; - uint32_t count; - uint32_t index; - uint32_t offset = MAPS_HEADER_LEN; - int read_only; - - if (mappings->failed || request->payload_len < MAPS_HEADER_LEN) { - return send_app_error(session, request->request_id, 22, "invalid-request"); - } - first = read_u32(request->payload); - count = read_u32(request->payload + 4); - if (first != mappings->applied || count == 0 || - count > mappings->expected - mappings->applied) { - return send_app_error(session, request->request_id, 22, "invalid-request"); - } - for (index = 0; index < count; ++index) { - if (decode_host_mapping( - request->payload, request->payload_len, &offset, source, target, - &read_only) != 0) { - return send_app_error(session, request->request_id, 22, "invalid-request"); - } - } - if (offset != request->payload_len) { - return send_app_error(session, request->request_id, 22, "invalid-request"); - } - offset = MAPS_HEADER_LEN; - for (index = 0; index < count; ++index) { - decode_host_mapping( - request->payload, request->payload_len, &offset, source, target, &read_only); - if (apply_host_mapping(source, target, read_only) != 0) { - int status = errno; - - mappings->failed = 1; - portb_error("host-mapping", status); - dprintf( - STDERR_FILENO, - "NVX-MANAGED-ERROR: stage=host-mapping status=%d entry=%u\n", - status, - (unsigned)(first + index)); - return send_app_error(session, request->request_id, status, "mapping-failed"); - } - ++mappings->applied; - } - return send_app_frame(session, APP_READY, request->request_id, 0, NULL, 0); -} - -/* Serves one application request of the authenticated host. */ -static int handle_app_request( - struct control_session *session, - const struct agent_config *config, - struct host_mappings *mappings, - const struct app_request *request) -{ - char **workload_argv = NULL; - uint8_t features[4]; - struct exec_config exec_config = {0}; - uint32_t timeout_ms = 0; - int result; - - switch (request->kind) { - case APP_PING: - if (request->payload_len != 0) { - portb_error("ping-payload", (int)request->payload_len); - return send_app_error( - session, request->request_id, 22, "invalid-request"); - } - result = send_app_frame( - session, APP_READY, request->request_id, 0, NULL, 0); - if (result != 0) { - portb_error("ping-ready", errno); - } - return result; - case APP_EXEC: - /* Workloads must not run without the host paths that they expect. */ - if (mappings->applied != mappings->expected) { - return send_app_error( - session, request->request_id, EAGAIN, "mappings-incomplete"); - } - if (decode_exec_payload( - request->payload, - request->payload_len, - &timeout_ms, - &workload_argv, - &exec_config) != 0) { - return send_app_error( - session, request->request_id, 22, "invalid-request"); - } - result = run_exec( - session, - config, - request->request_id, - timeout_ms, - workload_argv, - &exec_config); - free_arguments(workload_argv); - free_exec_config(&exec_config); - return result; - case APP_CANCEL: - /* The targeted workload already finished and reported its outcome. */ - return 0; - case APP_FEATURES: - if (request->payload_len != 0) { - return send_app_error( - session, request->request_id, 22, "invalid-request"); - } - write_u32(features, agent_features(config)); - return send_app_frame( - session, - APP_READY, - request->request_id, - 0, - features, - sizeof(features)); - case APP_MAPS: - return handle_host_mappings(session, mappings, request); - case APP_STOP: - if (request->payload_len != 0 || - send_app_frame( - session, APP_STOPPED, request->request_id, 0, NULL, 0) != 0) { - return -1; - } - tcdrain(session->fd); - return AGENT_STOPPED; - default: - return send_app_error( - session, request->request_id, 95, "unsupported-operation"); - } -} - -static int handle_data_record( - struct control_session *session, - const struct agent_config *config, - struct host_mappings *mappings, - const struct outer_record *record) -{ - struct app_request request; - - if (record->sequence != session->host_sequence) { - portb_error("data-sequence", (int)record->sequence); - return -1; - } - if (record->epoch != session->epoch || - memcmp(record->instance_id, session->instance_id, 16) != 0) { - portb_error("data-identity", (int)record->epoch); - return -1; - } - if (parse_app_request(record->payload, record->payload_len, &request) != 0) { - portb_error("data-application", (int)record->payload_len); - return -1; - } - ++session->host_sequence; - if (send_credit(session, record->payload_len) != 0) { - portb_error("data-credit", (int)record->payload_len); - return -1; - } - return handle_app_request(session, config, mappings, &request); -} - -static int run_agent( - struct control_session *session, - const struct agent_config *config, - struct host_mappings *mappings) -{ - struct outer_record record; - - if (send_guest_attach(session->fd) != 0) { - return -1; - } - for (;;) { - if (read_outer_record(session->fd, &record) != 0) { - portb_error("outer-read", errno); - return -1; - } - if (record.type == OUTER_RESET) { - if (acknowledge_reset(session, &record) != 0) { - portb_error("reset-ack", errno); - free_outer_record(&record); - return -1; - } - } else if (record.type == OUTER_DATA) { - int result = handle_data_record(session, config, mappings, &record); - - if (result == AGENT_STOPPED) { - free_outer_record(&record); - return AGENT_STOPPED; - } - if (result != 0) { - portb_error("data", errno); - free_outer_record(&record); - return -1; - } - } else { - portb_error("outer-type", record.type); - free_outer_record(&record); - return -1; - } - free_outer_record(&record); - } -} - -/* - * Direct mode runs as PID 1 and powers the VM off itself. In sandbox mode the - * init agent supervises this process and unmounts the live share, overlay, - * layers, and scratch before it powers the VM off. - */ -static int finish_agent(const struct agent_config *config, int status) -{ - char code[12]; - - if (config->direct) { - snprintf(code, sizeof(code), "%d", status); - execl("/sbin/nvx-exit", "nvx-exit", code, (char *)NULL); - } - return status; -} - -int main(int argc, char **argv) -{ - struct control_session session = {0}; - struct host_mappings mappings = {0}; - struct agent_config config; - int result; - - if (argc >= 2 && strcmp(argv[1], "--exec-config-fd") == 0) { - return launch_workload(argc, argv); - } - if (argc != 8) { - return 125; - } - config.rootfs = argv[2]; - config.hostname = argv[3]; - config.uid = argv[4]; - config.gid = argv[5]; - config.user = argv[6]; - config.home = argv[7]; - config.direct = strcmp(config.rootfs, "-") == 0; - session.fd = open_control_tty(argv[1]); - if (session.fd < 0) { - int status = errno; - portb_error("control-open", status); - dprintf( - STDERR_FILENO, - "NVX-MANAGED-ERROR: stage=control-open status=%d\n", - status); - return finish_agent(&config, 125); - } - if (config.direct && prepare_host_mappings(&mappings) != 0) { - int status = errno; - portb_error("host-mappings", status); - dprintf( - STDERR_FILENO, - "NVX-MANAGED-ERROR: stage=host-mappings status=%d\n", - status); - close(session.fd); - return finish_agent(&config, 125); - } - if (configure_control_tty(session.fd) != 0) { - int status = errno; - portb_error("control-tty", status); - dprintf( - STDERR_FILENO, - "NVX-MANAGED-ERROR: stage=control-tty status=%d\n", - status); - close(session.fd); - return finish_agent(&config, 125); - } - result = run_agent(&session, &config, &mappings); - if (result == AGENT_STOPPED) { - close(session.fd); - return finish_agent(&config, 0); - } - if (result != 0) { - int status = errno; - portb_error("control-session", status); - dprintf( - STDERR_FILENO, - "NVX-MANAGED-ERROR: stage=control-session status=%d\n", - status); - if (session.fd >= 0) { - close(session.fd); - } - return finish_agent(&config, 125); - } - return 0; -} diff --git a/guest/common/nvx-mmio-write.c b/guest/common/nvx-mmio-write.c deleted file mode 100644 index e1749729c..000000000 --- a/guest/common/nvx-mmio-write.c +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -static int parse_u64(const char *text, uint64_t *value) -{ - char *end; - - errno = 0; - *value = strtoull(text, &end, 0); - return errno == 0 && end != text && *end == '\0'; -} - -int main(int argc, char **argv) -{ - uint64_t address; - uint64_t value; - long page_size; - uint64_t page_base; - size_t page_offset; - void *mapping; - int memory; - - if (argc != 3 || !parse_u64(argv[1], &address) || - !parse_u64(argv[2], &value) || value > UINT32_MAX) { - fprintf(stderr, "usage: nvx-mmio-write ADDRESS VALUE\n"); - return 2; - } - - page_size = sysconf(_SC_PAGESIZE); - if (page_size <= 0 || (page_size & (page_size - 1)) != 0) { - fprintf(stderr, "invalid page size\n"); - return 1; - } - page_base = address & ~((uint64_t)page_size - 1); - page_offset = (size_t)(address - page_base); - if (page_offset > (size_t)page_size - sizeof(uint32_t)) { - fprintf(stderr, "MMIO write crosses a page boundary\n"); - return 1; - } - - memory = open("/dev/mem", O_RDWR | O_SYNC); - if (memory < 0) { - perror("open /dev/mem"); - return 1; - } - mapping = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE, MAP_SHARED, - memory, (off_t)page_base); - if (mapping == MAP_FAILED) { - perror("mmap /dev/mem"); - close(memory); - return 1; - } - - *(volatile uint32_t *)((unsigned char *)mapping + page_offset) = - (uint32_t)value; - - if (munmap(mapping, (size_t)page_size) != 0) { - perror("munmap /dev/mem"); - close(memory); - return 1; - } - if (close(memory) != 0) { - perror("close /dev/mem"); - return 1; - } - return 0; -} diff --git a/guest/common/nvx-reseed.c b/guest/common/nvx-reseed.c deleted file mode 100644 index 3d1142aab..000000000 --- a/guest/common/nvx-reseed.c +++ /dev/null @@ -1,178 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED_BYTES 64 -#define GENERATION_ID_BYTES 16 -#define RANDOM_SAMPLE_BYTES 32 - -struct seed_payload { - int entropy_count; - int buf_size; - unsigned char bytes[SEED_BYTES]; -}; - -static int read_seed(const char *path, unsigned char *bytes) -{ - int fd = open(path, O_RDONLY | O_CLOEXEC); - if (fd < 0) { - fprintf(stderr, "nvx-reseed: open %s: %s\n", path, strerror(errno)); - return -1; - } - - size_t offset = 0; - while (offset < SEED_BYTES) { - ssize_t count = read(fd, bytes + offset, SEED_BYTES - offset); - if (count <= 0) { - fprintf(stderr, "nvx-reseed: read %s: %s\n", path, - count == 0 ? "unexpected end of file" : strerror(errno)); - close(fd); - return -1; - } - offset += (size_t)count; - } - close(fd); - return 0; -} - -static int print_hex(const unsigned char *bytes, size_t size) -{ - for (size_t index = 0; index < size; ++index) { - if (printf("%02x", bytes[index]) < 0) { - fprintf(stderr, "nvx-reseed: write output failed\n"); - return -1; - } - } - if (putchar('\n') == EOF) { - fprintf(stderr, "nvx-reseed: write output failed\n"); - return -1; - } - return 0; -} - -static int hex_value(char digit) -{ - if (digit >= '0' && digit <= '9') { - return digit - '0'; - } - if (digit >= 'a' && digit <= 'f') { - return digit - 'a' + 10; - } - if (digit >= 'A' && digit <= 'F') { - return digit - 'A' + 10; - } - return -1; -} - -static int validate_generation_change(const char *previous, - const unsigned char *generation_id) -{ - if (strlen(previous) != GENERATION_ID_BYTES * 2) { - fprintf(stderr, - "nvx-reseed: previous VM generation ID is not 32 hex digits\n"); - return -1; - } - unsigned char previous_bytes[GENERATION_ID_BYTES]; - for (size_t index = 0; index < sizeof(previous_bytes); ++index) { - int high = hex_value(previous[index * 2]); - int low = hex_value(previous[index * 2 + 1]); - if (high < 0 || low < 0) { - fprintf(stderr, - "nvx-reseed: previous VM generation ID is not hexadecimal\n"); - return -1; - } - previous_bytes[index] = (unsigned char)((high << 4) | low); - } - if (memcmp(previous_bytes, generation_id, sizeof(previous_bytes)) == 0) { - fprintf(stderr, "nvx-reseed: VM generation ID did not change\n"); - return -1; - } - return 0; -} - -static int print_random_sample(void) -{ - unsigned char sample[RANDOM_SAMPLE_BYTES]; - size_t offset = 0; - while (offset < sizeof(sample)) { - ssize_t count = - getrandom(sample + offset, sizeof(sample) - offset, 0); - if (count > 0) { - offset += (size_t)count; - continue; - } - if (count < 0 && errno == EINTR) { - continue; - } - fprintf(stderr, "nvx-reseed: getrandom: %s\n", - count == 0 ? "no bytes returned" : strerror(errno)); - return -1; - } - return print_hex(sample, sizeof(sample)); -} - -int main(int argc, char **argv) -{ - if (argc == 2 && strcmp(argv[1], "--sample") == 0) { - return print_random_sample() == 0 ? 0 : 1; - } - - int generation_only = 0; - const char *seed_path; - const char *previous_generation_id = NULL; - if (argc == 2 || argc == 3) { - seed_path = argv[1]; - if (argc == 3) { - previous_generation_id = argv[2]; - } - } else if (argc == 4 && strcmp(argv[1], "--generation-only") == 0) { - generation_only = 1; - seed_path = argv[2]; - previous_generation_id = argv[3]; - } else { - fprintf(stderr, - "usage: nvx-reseed --sample | [--generation-only] SEED_FILE " - "[PREVIOUS_GENERATION_ID]\n"); - return 2; - } - - struct seed_payload seed = { - .entropy_count = SEED_BYTES * 8, - .buf_size = SEED_BYTES, - }; - if (read_seed(seed_path, seed.bytes) != 0) { - return 1; - } - if (previous_generation_id != NULL && - validate_generation_change(previous_generation_id, seed.bytes) != 0) { - return 1; - } - if (generation_only) { - return print_hex(seed.bytes, GENERATION_ID_BYTES) == 0 ? 0 : 1; - } - - int random = open("/dev/random", O_WRONLY | O_CLOEXEC); - if (random < 0) { - fprintf(stderr, "nvx-reseed: open /dev/random: %s\n", strerror(errno)); - return 1; - } - if (ioctl(random, RNDADDENTROPY, &seed) != 0 || - ioctl(random, RNDRESEEDCRNG, 0) != 0) { - fprintf(stderr, "nvx-reseed: random ioctl: %s\n", strerror(errno)); - close(random); - return 1; - } - close(random); - if (previous_generation_id != NULL && - print_hex(seed.bytes, GENERATION_ID_BYTES) != 0) { - return 1; - } - return 0; -} diff --git a/guest/common/nvx-time-probe.c b/guest/common/nvx-time-probe.c deleted file mode 100644 index f77f9b254..000000000 --- a/guest/common/nvx-time-probe.c +++ /dev/null @@ -1,1763 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// nvx-time-probe: guest-side time diagnostics for the NVX time ABI. -// -// latency ns/op of TSC reads and clock_gettime(), p50/p99. -// warp pairwise cross-CPU TSC warp and offset checks. -// clock clocks, clocksources, TSC flags, hypervisor identity, and -// per-CPU clock event devices. -// -// Build (static, x86-64): cc -O2 -static -pthread -o nvx-time-probe -// nvx-time-probe.c - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PROBE_VERSION "1.1" -#define EXIT_FAIL 1 -#define EXIT_USAGE 2 -#define EXIT_ERROR 3 -#define MAX_CPUS 1024 -#define KMSG_RECORD_MAX 8192 -#define SYSLOG_ACTION_READ_ALL 3 -#define SYSLOG_ACTION_SIZE_BUFFER 10 -#define CLOCKSOURCE_DIR "/sys/devices/system/clocksource/clocksource0" - -struct tsc_rate { - double hz; - const char *source; -}; - -static struct tsc_rate g_tsc; -static volatile uint64_t g_sink; - -// --------------------------------------------------------------------------- -// CPU primitives -// --------------------------------------------------------------------------- - -static inline uint64_t rdtsc_plain(void) -{ - uint32_t lo, hi; - - __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); - return ((uint64_t)hi << 32) | lo; -} - -static inline uint64_t rdtscp_plain(void) -{ - uint32_t lo, hi, aux; - - __asm__ __volatile__("rdtscp" : "=a"(lo), "=d"(hi), "=c"(aux)); - return ((uint64_t)hi << 32) | lo; -} - -// Linux rdtsc_ordered(): the read cannot pass earlier loads. -static inline uint64_t rdtsc_lfence(void) -{ - uint32_t lo, hi; - - __asm__ __volatile__("lfence\n\trdtsc" : "=a"(lo), "=d"(hi)::"memory"); - return ((uint64_t)hi << 32) | lo; -} - -// Fully fenced read for timing brackets: later work cannot start before it. -static inline uint64_t rdtsc_fenced(void) -{ - uint32_t lo, hi; - - __asm__ __volatile__("lfence\n\trdtsc\n\tlfence" - : "=a"(lo), "=d"(hi)::"memory"); - return ((uint64_t)hi << 32) | lo; -} - -static inline void cpu_pause(void) -{ - __asm__ __volatile__("pause" ::: "memory"); -} - -static void cpuid(uint32_t leaf, uint32_t subleaf, uint32_t regs[4]) -{ - __asm__ __volatile__("cpuid" - : "=a"(regs[0]), "=b"(regs[1]), "=c"(regs[2]), - "=d"(regs[3]) - : "a"(leaf), "c"(subleaf)); -} - -static bool cpu_has_rdtscp(void) -{ - uint32_t regs[4]; - - cpuid(0x80000000u, 0, regs); - if (regs[0] < 0x80000001u) - return false; - cpuid(0x80000001u, 0, regs); - return (regs[3] >> 27) & 1; -} - -static int pin_self(int cpu) -{ - cpu_set_t set; - - CPU_ZERO(&set); - CPU_SET(cpu, &set); - if (sched_setaffinity(0, sizeof(set), &set) != 0) - return -1; - if (sched_getcpu() != cpu) { - errno = EINVAL; - return -1; - } - return 0; -} - -// --------------------------------------------------------------------------- -// Small helpers -// --------------------------------------------------------------------------- - -static double cycles_to_ns(double cycles) -{ - return g_tsc.hz > 0 ? cycles * 1e9 / g_tsc.hz : 0; -} - -static long long round_ll(double value) -{ - return (long long)(value >= 0 ? value + 0.5 : value - 0.5); -} - -static int read_first_line(const char *path, char *buf, size_t size) -{ - FILE *file = fopen(path, "r"); - - if (!file) - return -1; - if (!fgets(buf, (int)size, file)) { - fclose(file); - return -1; - } - fclose(file); - buf[strcspn(buf, "\n")] = '\0'; - return 0; -} - -static int cmp_double(const void *a, const void *b) -{ - double x = *(const double *)a; - double y = *(const double *)b; - - return (x > y) - (x < y); -} - -static int cmp_i64(const void *a, const void *b) -{ - int64_t x = *(const int64_t *)a; - int64_t y = *(const int64_t *)b; - - return (x > y) - (x < y); -} - -// Nearest-rank percentile over a sorted array, as the NVX harness reports. -static double nearest_rank(const double *sorted, size_t count, double pct) -{ - double exact = pct * (double)count; - size_t rank = (size_t)exact; - - if ((double)rank < exact) - rank++; - if (rank < 1) - rank = 1; - if (rank > count) - rank = count; - return sorted[rank - 1]; -} - -static int parse_u64_arg(const char *text, uint64_t *value) -{ - char *end; - - errno = 0; - *value = strtoull(text, &end, 0); - return errno == 0 && end != text && *end == '\0' ? 0 : -1; -} - -static int parse_cpu_list(const char *text, int *cpus, int max) -{ - int count = 0; - const char *p = text; - - while (*p) { - char *end; - long first = strtol(p, &end, 10); - long last = first; - - if (end == p || first < 0 || first >= MAX_CPUS) - return -1; - p = end; - if (*p == '-') { - last = strtol(p + 1, &end, 10); - if (end == p + 1 || last < first || last >= MAX_CPUS) - return -1; - p = end; - } - for (long cpu = first; cpu <= last; cpu++) { - for (int i = 0; i < count; i++) - if (cpus[i] == cpu) - return -1; - if (count >= max) - return -1; - cpus[count++] = (int)cpu; - } - if (*p == ',') - p++; - else if (*p) - return -1; - } - return count; -} - -static int affinity_cpus(int *cpus, int max) -{ - cpu_set_t set; - int count = 0; - - if (sched_getaffinity(0, sizeof(set), &set) != 0) - return -1; - for (int cpu = 0; cpu < CPU_SETSIZE && count < max; cpu++) - if (CPU_ISSET(cpu, &set)) - cpus[count++] = cpu; - return count; -} - -// Rejects CPUs outside the affinity mask (offline or excluded) before any -// measurement starts. -static int check_cpus_allowed(const char *command, const int *cpus, int count) -{ - cpu_set_t allowed; - - if (sched_getaffinity(0, sizeof(allowed), &allowed) != 0) { - fprintf(stderr, "nvx-time-probe %s: sched_getaffinity: %s\n", command, - strerror(errno)); - return -1; - } - for (int i = 0; i < count; i++) { - if (cpus[i] >= CPU_SETSIZE || !CPU_ISSET(cpus[i], &allowed)) { - fprintf(stderr, - "nvx-time-probe %s: cpu %d is not in the affinity mask" - " (offline or excluded)\n", - command, cpus[i]); - return -1; - } - } - return 0; -} - -static void format_cpu_list(const int *cpus, int count, char *buf, size_t size) -{ - size_t used = 0; - - buf[0] = '\0'; - for (int i = 0; i < count && used < size; i++) { - int first = cpus[i]; - int last = first; - - while (i + 1 < count && cpus[i + 1] == last + 1) - last = cpus[++i]; - used += (size_t)snprintf(buf + used, size - used, "%s%d", used ? "," : "", - first); - if (last != first && used < size) - used += (size_t)snprintf(buf + used, size - used, "-%d", last); - } -} - -// --------------------------------------------------------------------------- -// Kernel log access (/dev/kmsg, falling back to syslog(2)) -// --------------------------------------------------------------------------- - -typedef void (*kmsg_fn)(uint64_t usec, const char *text, void *ctx); - -static int kmsg_from_devkmsg(kmsg_fn fn, void *ctx) -{ - char record[KMSG_RECORD_MAX]; - int count = 0; - int fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - - if (fd < 0) - return -1; - for (;;) { - ssize_t n = read(fd, record, sizeof(record) - 1); - char *semi; - char *p; - int field = 0; - - if (n < 0 && (errno == EINTR || errno == EPIPE)) - continue; - if (n <= 0) - break; - record[n] = '\0'; - // Record format: "prio,seq,usec,flags[,...];message\n[ KEY=VALUE\n]". - semi = strchr(record, ';'); - if (!semi) - continue; - for (p = record; p < semi && field < 2; p++) - if (*p == ',') - field++; - record[strcspn(record, "\n")] = '\0'; - fn(strtoull(p, NULL, 10), semi + 1, ctx); - count++; - } - close(fd); - return count > 0 ? 0 : -1; -} - -static int kmsg_from_syslog(kmsg_fn fn, void *ctx) -{ - int size = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0); - char *buf; - char *line; - int n; - - if (size <= 0) - size = 1 << 20; - buf = malloc((size_t)size + 1); - if (!buf) - return -1; - n = klogctl(SYSLOG_ACTION_READ_ALL, buf, size); - if (n < 0) { - free(buf); - return -1; - } - buf[n] = '\0'; - for (line = buf; line && *line;) { - char *next = strchr(line, '\n'); - char *p = line; - uint64_t usec = 0; - - if (next) - *next++ = '\0'; - if (*p == '<') { - char *close_angle = strchr(p, '>'); - - if (close_angle) - p = close_angle + 1; - } - if (*p == '[') { - char *close_bracket = strchr(p, ']'); - - if (close_bracket) { - usec = (uint64_t)(strtod(p + 1, NULL) * 1e6 + 0.5); - p = close_bracket + 1; - if (*p == ' ') - p++; - } - } - fn(usec, p, ctx); - line = next; - } - free(buf); - return 0; -} - -static int kmsg_foreach(kmsg_fn fn, void *ctx) -{ - if (kmsg_from_devkmsg(fn, ctx) == 0) - return 0; - return kmsg_from_syslog(fn, ctx); -} - -// --------------------------------------------------------------------------- -// /proc/cpuinfo and TSC rate discovery -// --------------------------------------------------------------------------- - -static bool cpuinfo_has_flag(const char *flag) -{ - static char flags[8192]; - static bool loaded; - size_t len = strlen(flag); - const char *p; - - if (!loaded) { - char line[8192]; - FILE *file = fopen("/proc/cpuinfo", "r"); - - loaded = true; - while (file && fgets(line, sizeof(line), file)) { - if (strncmp(line, "flags", 5) == 0 && strchr(line, ':')) { - snprintf(flags, sizeof(flags), " %s", strchr(line, ':') + 1); - flags[strcspn(flags, "\n")] = '\0'; - strncat(flags, " ", sizeof(flags) - strlen(flags) - 1); - break; - } - } - if (file) - fclose(file); - } - for (p = flags; (p = strstr(p, flag)) != NULL; p += len) - if (p[-1] == ' ' && p[len] == ' ') - return true; - return false; -} - -static double cpuinfo_mhz(void) -{ - char line[512]; - double mhz = 0; - FILE *file = fopen("/proc/cpuinfo", "r"); - - if (!file) - return 0; - while (fgets(line, sizeof(line), file)) { - if (strncmp(line, "cpu MHz", 7) == 0 && strchr(line, ':')) { - mhz = strtod(strchr(line, ':') + 1, NULL); - break; - } - } - fclose(file); - return mhz; -} - -struct kmsg_tsc { - double refined_mhz; - double detected_tsc_mhz; - double detected_cpu_mhz; -}; - -static double mhz_after(const char *text, const char *prefix, const char *suffix) -{ - const char *p = strstr(text, prefix); - char *end; - double mhz; - - if (!p) - return 0; - p += strlen(prefix); - mhz = strtod(p, &end); - if (end == p || strncmp(end, suffix, strlen(suffix)) != 0) - return 0; - return mhz; -} - -static void kmsg_tsc_cb(uint64_t usec, const char *text, void *ctx) -{ - struct kmsg_tsc *k = ctx; - double mhz; - - (void)usec; - if ((mhz = mhz_after(text, "Refined TSC clocksource calibration: ", " MHz")) > 0) - k->refined_mhz = mhz; - else if ((mhz = mhz_after(text, "tsc: Detected ", " MHz TSC")) > 0) - k->detected_tsc_mhz = mhz; - else if ((mhz = mhz_after(text, "tsc: Detected ", " MHz processor")) > 0) - k->detected_cpu_mhz = mhz; -} - -// The kernel prints kHz precision as "NNNN.NNN MHz". -static double mhz_to_hz(double mhz) -{ - return (double)(uint64_t)(mhz * 1000.0 + 0.5) * 1000.0; -} - -static double measure_tsc_hz(unsigned ms) -{ - struct timespec a, b; - struct timespec pause = {ms / 1000, (long)(ms % 1000) * 1000000L}; - uint64_t t0, t1; - double ns; - - clock_gettime(CLOCK_MONOTONIC_RAW, &a); - t0 = rdtsc_fenced(); - nanosleep(&pause, NULL); - clock_gettime(CLOCK_MONOTONIC_RAW, &b); - t1 = rdtsc_fenced(); - ns = (double)(b.tv_sec - a.tv_sec) * 1e9 + (double)(b.tv_nsec - a.tv_nsec); - return ns > 0 ? (double)(t1 - t0) * 1e9 / ns : 0; -} - -// Precedence: option, sysfs tsc_freq_khz, kernel log (refined, then detected -// TSC, then detected processor), "cpu MHz" without APERF/MPERF, measurement. -static void detect_tsc_rate(double override_hz) -{ - struct kmsg_tsc k = {0}; - char line[64]; - - if (override_hz > 0) { - g_tsc = (struct tsc_rate){override_hz, "option"}; - return; - } - if (read_first_line("/sys/devices/system/cpu/cpu0/tsc_freq_khz", line, - sizeof(line)) == 0 && - strtoull(line, NULL, 10) > 0) { - g_tsc = (struct tsc_rate){(double)strtoull(line, NULL, 10) * 1000.0, - "sysfs-tsc_freq_khz"}; - return; - } - kmsg_foreach(kmsg_tsc_cb, &k); - if (k.refined_mhz > 0) { - g_tsc = (struct tsc_rate){mhz_to_hz(k.refined_mhz), "kmsg-refined"}; - return; - } - if (k.detected_tsc_mhz > 0) { - g_tsc = (struct tsc_rate){mhz_to_hz(k.detected_tsc_mhz), - "kmsg-detected-tsc"}; - return; - } - if (k.detected_cpu_mhz > 0) { - g_tsc = (struct tsc_rate){mhz_to_hz(k.detected_cpu_mhz), - "kmsg-detected-processor"}; - return; - } - // Without APERF/MPERF, "cpu MHz" reports cpu_khz, which equals tsc_khz. - if (!cpuinfo_has_flag("aperfmperf") && cpuinfo_mhz() > 0) { - g_tsc = (struct tsc_rate){mhz_to_hz(cpuinfo_mhz()), "cpuinfo-cpu-mhz"}; - return; - } - g_tsc = (struct tsc_rate){measure_tsc_hz(200), "measured-monotonic-raw"}; -} - -static void current_clocksource(char *buf, size_t size) -{ - if (read_first_line(CLOCKSOURCE_DIR "/current_clocksource", buf, size) != 0) - snprintf(buf, size, "unknown"); -} - -// --------------------------------------------------------------------------- -// Option parsing -// --------------------------------------------------------------------------- - -// Matches "--name value" or "--name=value" at argv[*index]. -static const char *option_value(int argc, char **argv, int *index, const char *name) -{ - const char *arg = argv[*index]; - size_t len = strlen(name); - - if (strncmp(arg, name, len) != 0) - return NULL; - if (arg[len] == '=') - return arg + len + 1; - if (arg[len] == '\0' && *index + 1 < argc) - return argv[++*index]; - return NULL; -} - -static int parse_hz_arg(const char *text, double *hz) -{ - char *end; - - errno = 0; - *hz = strtod(text, &end); - return errno == 0 && end != text && *end == '\0' && *hz > 0 ? 0 : -1; -} - -static int bad_option(const char *command, const char *arg) -{ - fprintf(stderr, "nvx-time-probe %s: invalid option or value: %s\n", command, arg); - return EXIT_USAGE; -} - -// --------------------------------------------------------------------------- -// latency -// --------------------------------------------------------------------------- - -enum method { - M_RDTSC, - M_RDTSCP, - M_LFENCE_RDTSC, - M_CLOCK_MONOTONIC, - M_CLOCK_REALTIME, - M_SYSCALL_MONOTONIC, - M_SYSCALL_GETPID, - M_SCHED_YIELD, - M_COUNT, -}; - -static const char *const method_names[M_COUNT] = { - "rdtsc", - "rdtscp", - "lfence_rdtsc", - "clock_gettime_monotonic", - "clock_gettime_realtime", - "syscall_clock_gettime_monotonic", - "syscall_getpid", - "sched_yield", -}; - -struct latency_result { - double p50; - double p99; - double min; - double mean; - size_t samples; -}; - -static uint64_t run_batch(enum method method, unsigned batch) -{ - struct timespec ts; - uint64_t sink = 0; - unsigned i; - - switch (method) { - case M_RDTSC: - for (i = 0; i < batch; i++) - sink += rdtsc_plain(); - break; - case M_RDTSCP: - for (i = 0; i < batch; i++) - sink += rdtscp_plain(); - break; - case M_LFENCE_RDTSC: - for (i = 0; i < batch; i++) - sink += rdtsc_lfence(); - break; - case M_CLOCK_MONOTONIC: - for (i = 0; i < batch; i++) { - clock_gettime(CLOCK_MONOTONIC, &ts); - sink += (uint64_t)ts.tv_nsec; - } - break; - case M_CLOCK_REALTIME: - for (i = 0; i < batch; i++) { - clock_gettime(CLOCK_REALTIME, &ts); - sink += (uint64_t)ts.tv_nsec; - } - break; - case M_SYSCALL_MONOTONIC: - for (i = 0; i < batch; i++) { - syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &ts); - sink += (uint64_t)ts.tv_nsec; - } - break; - case M_SYSCALL_GETPID: - for (i = 0; i < batch; i++) - sink += (uint64_t)syscall(SYS_getpid); - break; - case M_SCHED_YIELD: - for (i = 0; i < batch; i++) - sink += (uint64_t)sched_yield(); - break; - default: - break; - } - return sink; -} - -// Median cost of an empty fenced timing bracket, subtracted from samples. -static double bracket_overhead_cycles(size_t iterations) -{ - double *samples = malloc(iterations * sizeof(*samples)); - double median; - - if (!samples) - return 0; - for (size_t i = 0; i < iterations; i++) { - uint64_t t0 = rdtsc_fenced(); - uint64_t t1 = rdtsc_fenced(); - - samples[i] = (double)(t1 - t0); - } - qsort(samples, iterations, sizeof(*samples), cmp_double); - median = nearest_rank(samples, iterations, 0.50); - free(samples); - return median; -} - -static int measure_method(enum method method, size_t iterations, unsigned batch, - double overhead_cycles, struct latency_result *out) -{ - double *per_op = malloc(iterations * sizeof(*per_op)); - double sum = 0; - - if (!per_op) - return -1; - g_sink += run_batch(method, batch); - for (size_t i = 0; i < iterations; i++) { - uint64_t t0 = rdtsc_fenced(); - uint64_t sink = run_batch(method, batch); - uint64_t t1 = rdtsc_fenced(); - double cycles = (double)(t1 - t0) - overhead_cycles; - - g_sink += sink; - if (cycles < 0) - cycles = 0; - per_op[i] = cycles_to_ns(cycles) / batch; - sum += per_op[i]; - } - qsort(per_op, iterations, sizeof(*per_op), cmp_double); - out->p50 = nearest_rank(per_op, iterations, 0.50); - out->p99 = nearest_rank(per_op, iterations, 0.99); - out->min = per_op[0]; - out->mean = sum / (double)iterations; - out->samples = iterations; - free(per_op); - return 0; -} - -static int cmd_latency(int argc, char **argv) -{ - uint64_t iterations = 2000; - uint64_t batch = 50; - uint64_t cpu_arg = UINT64_MAX; - double tsc_hz = 0; - struct latency_result results[M_COUNT] = {0}; - char clocksource[64]; - bool has_rdtscp = cpu_has_rdtscp(); - bool has_vdso = getauxval(AT_SYSINFO_EHDR) != 0; - double overhead; - int cpu; - - for (int i = 2; i < argc; i++) { - const char *value; - - if ((value = option_value(argc, argv, &i, "--iterations"))) { - if (parse_u64_arg(value, &iterations) || iterations == 0 || - iterations > 10000000) - return bad_option("latency", argv[i]); - } else if ((value = option_value(argc, argv, &i, "--batch"))) { - if (parse_u64_arg(value, &batch) || batch == 0 || batch > 1000000) - return bad_option("latency", argv[i]); - } else if ((value = option_value(argc, argv, &i, "--cpu"))) { - if (parse_u64_arg(value, &cpu_arg) || cpu_arg >= MAX_CPUS) - return bad_option("latency", argv[i]); - } else if ((value = option_value(argc, argv, &i, "--tsc-hz"))) { - if (parse_hz_arg(value, &tsc_hz)) - return bad_option("latency", argv[i]); - } else { - return bad_option("latency", argv[i]); - } - } - - cpu = cpu_arg == UINT64_MAX ? sched_getcpu() : (int)cpu_arg; - if (cpu >= 0 && check_cpus_allowed("latency", &cpu, 1) != 0) - return EXIT_USAGE; - if (cpu < 0 || pin_self(cpu) != 0) { - fprintf(stderr, "nvx-time-probe latency: cannot pin to cpu %d: %s\n", cpu, - strerror(errno)); - return EXIT_ERROR; - } - detect_tsc_rate(tsc_hz); - if (g_tsc.hz <= 0) { - fprintf(stderr, "nvx-time-probe latency: unknown TSC rate; pass --tsc-hz\n"); - return EXIT_ERROR; - } - current_clocksource(clocksource, sizeof(clocksource)); - overhead = bracket_overhead_cycles((size_t)iterations); - - printf("nvx-time-probe %s latency: cpu=%d iterations=%" PRIu64 " batch=%" PRIu64 - " tsc_hz=%.0f tsc_hz_source=%s clocksource=%s vdso=%d" - " bracket_overhead_ns=%.1f\n", - PROBE_VERSION, cpu, iterations, batch, g_tsc.hz, g_tsc.source, - clocksource, has_vdso, cycles_to_ns(overhead)); - printf("%-32s %10s %10s %10s %10s\n", "method", "p50_ns", "p99_ns", "min_ns", - "mean_ns"); - for (int m = 0; m < M_COUNT; m++) { - if (m == M_RDTSCP && !has_rdtscp) { - printf("%-32s unsupported (CPUID.80000001H:EDX[27] clear)\n", - method_names[m]); - continue; - } - if (measure_method((enum method)m, (size_t)iterations, (unsigned)batch, - overhead, &results[m]) != 0) { - fprintf(stderr, "nvx-time-probe latency: out of memory\n"); - return EXIT_ERROR; - } - printf("%-32s %10.1f %10.1f %10.1f %10.1f\n", method_names[m], - results[m].p50, results[m].p99, results[m].min, results[m].mean); - fflush(stdout); - } - for (int m = 0; m < M_COUNT; m++) { - if (!results[m].samples) { - printf("NVX-TIME-PROBE latency method=%s unsupported=1\n", method_names[m]); - continue; - } - printf("NVX-TIME-PROBE latency method=%s p50_ns=%.1f p99_ns=%.1f min_ns=%.1f" - " mean_ns=%.1f samples=%zu batch=%" PRIu64 " cpu=%d clocksource=%s\n", - method_names[m], results[m].p50, results[m].p99, results[m].min, - results[m].mean, results[m].samples, batch, cpu, clocksource); - } - return 0; -} - -// --------------------------------------------------------------------------- -// warp -// --------------------------------------------------------------------------- - -#define PP_STOP UINT64_MAX -#define PP_MAX_SAMPLES (1u << 18) -#define STALL_SECONDS 2.0 -#define SPIN_CHECK_MASK 0xfffffu - -// (a) Linux check_tsc_warp(): both CPUs take one spinlock, read the TSC, and -// compare it with the last value either CPU stored. A backward step proves a -// skew larger than the lock handoff latency, so it is a lower bound. -static struct { - _Alignas(64) atomic_int lock; - uint64_t last_tsc; - uint64_t max_warp; - uint64_t warps; - _Alignas(64) atomic_int ready; - atomic_int abort; - atomic_int stop; -} g_warp; - -// (b) Ping-pong: cpu_a reads t1 and signals, cpu_b reads t2 and answers, and -// cpu_a reads t3. The offset of cpu_b relative to cpu_a lies in [t2-t3, t2-t1]; -// the point estimate is t2-(t1+t3)/2 with uncertainty (t3-t1)/2. -static struct { - _Alignas(64) _Atomic uint64_t seq; - _Atomic uint64_t t2; - _Alignas(64) atomic_int ready; - atomic_int abort; -} g_pp; - -struct pair_result { - int cpu_a; - int cpu_b; - uint64_t warp_iterations; - uint64_t warps; - uint64_t max_warp_cycles; - uint64_t rounds; - uint64_t min_rtt_cycles; - int64_t min_rtt_offset_x2; - int64_t lo_cycles; - int64_t hi_cycles; - double median_offset_cycles; - double offset_cycles; - double uncertainty_cycles; - bool consistent; - bool stalled; -}; - -struct thread_arg { - int cpu; - bool leader; - uint64_t budget_cycles; - uint64_t stall_cycles; - uint64_t iterations; - int pin_errno; - struct pair_result *result; - int64_t *offsets_x2; -}; - -static bool pair_barrier(atomic_int *ready, atomic_int *abort_flag, bool failed) -{ - if (failed) - atomic_store(abort_flag, 1); - atomic_fetch_add(ready, 1); - while (atomic_load(ready) < 2) - cpu_pause(); - return !atomic_load(abort_flag); -} - -static uint64_t coarse_ns(void) -{ - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); - return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; -} - -static void *warp_thread(void *p) -{ - struct thread_arg *arg = p; - bool pinned = pin_self(arg->cpu) == 0; - uint64_t end; - uint64_t deadline_ns; - uint64_t i; - - if (!pinned) - arg->pin_errno = errno; - if (!pair_barrier(&g_warp.ready, &g_warp.abort, !pinned)) - return NULL; - end = rdtsc_lfence() + arg->budget_cycles; - // Guard against a TSC that never reaches the deadline. - deadline_ns = coarse_ns() + - (uint64_t)(cycles_to_ns((double)arg->budget_cycles) + STALL_SECONDS * 1e9); - for (i = 0;; i++) { - uint64_t prev, now; - - while (atomic_exchange_explicit(&g_warp.lock, 1, memory_order_acquire)) - while (atomic_load_explicit(&g_warp.lock, memory_order_relaxed)) - cpu_pause(); - prev = g_warp.last_tsc; - now = rdtsc_lfence(); - g_warp.last_tsc = now; - if (prev > now) { - if (prev - now > g_warp.max_warp) - g_warp.max_warp = prev - now; - g_warp.warps++; - } - atomic_store_explicit(&g_warp.lock, 0, memory_order_release); - if (!(i & 7)) { - if (arg->leader && - (rdtsc_plain() > end || (!(i & 4095) && coarse_ns() > deadline_ns))) - atomic_store_explicit(&g_warp.stop, 1, memory_order_relaxed); - if (atomic_load_explicit(&g_warp.stop, memory_order_relaxed)) - break; - } - } - arg->iterations = i + 1; - return NULL; -} - -static void *pp_leader(void *p) -{ - struct thread_arg *arg = p; - struct pair_result *r = arg->result; - bool pinned = pin_self(arg->cpu) == 0; - uint64_t stored = 0; - uint64_t end; - uint64_t k; - - if (!pinned) - arg->pin_errno = errno; - if (!pair_barrier(&g_pp.ready, &g_pp.abort, !pinned)) - return NULL; - end = rdtsc_lfence() + arg->budget_cycles; - for (k = 1;; k++) { - uint64_t spins = 0; - uint64_t t1, t2, t3, rtt; - int64_t lo, hi; - - t1 = rdtsc_fenced(); - atomic_store_explicit(&g_pp.seq, 2 * k - 1, memory_order_release); - while (atomic_load_explicit(&g_pp.seq, memory_order_acquire) != 2 * k) { - if ((++spins & SPIN_CHECK_MASK) == 0 && - rdtsc_plain() - t1 > arg->stall_cycles) { - r->stalled = true; - goto done; - } - } - t3 = rdtsc_lfence(); - t2 = atomic_load_explicit(&g_pp.t2, memory_order_relaxed); - rtt = t3 - t1; - lo = (int64_t)(t2 - t3); - hi = (int64_t)(t2 - t1); - if (rtt < r->min_rtt_cycles) { - r->min_rtt_cycles = rtt; - r->min_rtt_offset_x2 = lo + hi; - } - if (lo > r->lo_cycles) - r->lo_cycles = lo; - if (hi < r->hi_cycles) - r->hi_cycles = hi; - if (stored < PP_MAX_SAMPLES) - arg->offsets_x2[stored++] = lo + hi; - r->rounds = k; - if ((k & 63) == 0 && rdtsc_plain() > end) - break; - } -done: - atomic_store_explicit(&g_pp.seq, PP_STOP, memory_order_release); - arg->iterations = stored; - return NULL; -} - -static void *pp_follower(void *p) -{ - struct thread_arg *arg = p; - bool pinned = pin_self(arg->cpu) == 0; - - if (!pinned) - arg->pin_errno = errno; - if (!pair_barrier(&g_pp.ready, &g_pp.abort, !pinned)) - return NULL; - for (uint64_t k = 1;; k++) { - uint64_t spins = 0; - uint64_t wait_start = 0; - uint64_t value; - - while ((value = atomic_load_explicit(&g_pp.seq, memory_order_acquire)) != - 2 * k - 1) { - if (value == PP_STOP) - return NULL; - if ((++spins & SPIN_CHECK_MASK) == 0) { - uint64_t now = rdtsc_plain(); - - if (!wait_start) - wait_start = now; - else if (now - wait_start > arg->stall_cycles) - return NULL; - } - } - atomic_store_explicit(&g_pp.t2, rdtsc_fenced(), memory_order_relaxed); - atomic_store_explicit(&g_pp.seq, 2 * k, memory_order_release); - } -} - -static int run_threads(void *(*fn_a)(void *), void *(*fn_b)(void *), - struct thread_arg *a, struct thread_arg *b) -{ - pthread_t ta, tb; - int err; - - if ((err = pthread_create(&ta, NULL, fn_a, a)) != 0) { - errno = err; - return -1; - } - if ((err = pthread_create(&tb, NULL, fn_b, b)) != 0) { - // Release the first thread from its barrier before joining it. - atomic_store(&g_warp.abort, 1); - atomic_store(&g_pp.abort, 1); - atomic_fetch_add(&g_warp.ready, 1); - atomic_fetch_add(&g_pp.ready, 1); - pthread_join(ta, NULL); - errno = err; - return -1; - } - pthread_join(ta, NULL); - pthread_join(tb, NULL); - if (a->pin_errno || b->pin_errno) { - errno = a->pin_errno ? a->pin_errno : b->pin_errno; - return -1; - } - return 0; -} - -static int run_pair(int cpu_a, int cpu_b, uint64_t budget_cycles, - uint64_t stall_cycles, int64_t *offsets_x2, - struct pair_result *r) -{ - struct thread_arg a = {cpu_a, true, budget_cycles, stall_cycles, 0, 0, r, offsets_x2}; - struct thread_arg b = {cpu_b, false, budget_cycles, stall_cycles, 0, 0, r, NULL}; - - memset(r, 0, sizeof(*r)); - r->cpu_a = cpu_a; - r->cpu_b = cpu_b; - - atomic_store(&g_warp.lock, 0); - atomic_store(&g_warp.ready, 0); - atomic_store(&g_warp.abort, 0); - atomic_store(&g_warp.stop, 0); - g_warp.last_tsc = 0; - g_warp.max_warp = 0; - g_warp.warps = 0; - if (run_threads(warp_thread, warp_thread, &a, &b) != 0) - return -1; - r->warp_iterations = a.iterations + b.iterations; - r->warps = g_warp.warps; - r->max_warp_cycles = g_warp.max_warp; - - atomic_store(&g_pp.seq, 0); - atomic_store(&g_pp.t2, 0); - atomic_store(&g_pp.ready, 0); - atomic_store(&g_pp.abort, 0); - r->min_rtt_cycles = UINT64_MAX; - r->lo_cycles = INT64_MIN; - r->hi_cycles = INT64_MAX; - a.iterations = 0; - if (run_threads(pp_leader, pp_follower, &a, &b) != 0) - return -1; - if (a.iterations == 0) { - r->stalled = true; - return 0; - } - qsort(offsets_x2, (size_t)a.iterations, sizeof(*offsets_x2), cmp_i64); - r->median_offset_cycles = (double)offsets_x2[(a.iterations - 1) / 2] / 2.0; - r->consistent = r->lo_cycles <= r->hi_cycles; - if (r->consistent) { - r->offset_cycles = ((double)r->lo_cycles + (double)r->hi_cycles) / 2.0; - r->uncertainty_cycles = ((double)r->hi_cycles - (double)r->lo_cycles) / 2.0; - } else { - r->offset_cycles = (double)r->min_rtt_offset_x2 / 2.0; - r->uncertainty_cycles = (double)r->min_rtt_cycles / 2.0; - } - return 0; -} - -static double abs_double(double value) -{ - return value < 0 ? -value : value; -} - -static int cmd_warp(int argc, char **argv) -{ - static int cpus[MAX_CPUS]; - uint64_t duration_ms = 100; - uint64_t bound_ns = 1000; - double tsc_hz = 0; - int ncpus = -1; - int pairs = 0; - int inconsistent = 0; - int stalled = 0; - uint64_t total_warps = 0; - uint64_t max_backward = 0; - double max_abs_offset = 0; - double max_uncertainty = 0; - double max_skew_bound = 0; - uint64_t budget_cycles; - uint64_t stall_cycles; - int64_t *offsets_x2; - char cpu_text[256]; - bool pass; - bool conclusive; - - for (int i = 2; i < argc; i++) { - const char *value; - - if ((value = option_value(argc, argv, &i, "--duration-ms"))) { - if (parse_u64_arg(value, &duration_ms) || duration_ms == 0 || - duration_ms > 600000) - return bad_option("warp", argv[i]); - } else if ((value = option_value(argc, argv, &i, "--cpus"))) { - if ((ncpus = parse_cpu_list(value, cpus, MAX_CPUS)) <= 0) - return bad_option("warp", argv[i]); - } else if ((value = option_value(argc, argv, &i, "--bound-ns"))) { - if (parse_u64_arg(value, &bound_ns)) - return bad_option("warp", argv[i]); - } else if ((value = option_value(argc, argv, &i, "--tsc-hz"))) { - if (parse_hz_arg(value, &tsc_hz)) - return bad_option("warp", argv[i]); - } else { - return bad_option("warp", argv[i]); - } - } - if (ncpus < 0 && (ncpus = affinity_cpus(cpus, MAX_CPUS)) <= 0) { - fprintf(stderr, "nvx-time-probe warp: sched_getaffinity: %s\n", strerror(errno)); - return EXIT_ERROR; - } - if (check_cpus_allowed("warp", cpus, ncpus) != 0) - return EXIT_USAGE; - detect_tsc_rate(tsc_hz); - if (g_tsc.hz <= 0) { - fprintf(stderr, "nvx-time-probe warp: unknown TSC rate; pass --tsc-hz\n"); - return EXIT_ERROR; - } - budget_cycles = (uint64_t)((double)duration_ms * g_tsc.hz / 1000.0); - stall_cycles = (uint64_t)(STALL_SECONDS * g_tsc.hz) + budget_cycles; - offsets_x2 = malloc(PP_MAX_SAMPLES * sizeof(*offsets_x2)); - if (!offsets_x2) { - fprintf(stderr, "nvx-time-probe warp: out of memory\n"); - return EXIT_ERROR; - } - format_cpu_list(cpus, ncpus, cpu_text, sizeof(cpu_text)); - printf("nvx-time-probe %s warp: cpus=%s pairs=%d duration_ms=%" PRIu64 - " (per test per pair) tsc_hz=%.0f tsc_hz_source=%s bound_ns=%" PRIu64 "\n", - PROBE_VERSION, cpu_text, ncpus * (ncpus - 1) / 2, duration_ms, g_tsc.hz, - g_tsc.source, bound_ns); - fflush(stdout); - - for (int i = 0; i < ncpus; i++) { - for (int j = i + 1; j < ncpus; j++) { - struct pair_result r; - double skew_bound; - - if (run_pair(cpus[i], cpus[j], budget_cycles, stall_cycles, offsets_x2, - &r) != 0) { - fprintf(stderr, "nvx-time-probe warp: pair %d-%d: %s\n", cpus[i], - cpus[j], strerror(errno)); - free(offsets_x2); - return EXIT_ERROR; - } - pairs++; - total_warps += r.warps; - if (r.max_warp_cycles > max_backward) - max_backward = r.max_warp_cycles; - if (r.stalled) { - stalled++; - printf("pair %d-%d: warp_iterations=%" PRIu64 " warps=%" PRIu64 - " max_backward_cycles=%" PRIu64 " ping_pong=stalled\n", - r.cpu_a, r.cpu_b, r.warp_iterations, r.warps, r.max_warp_cycles); - continue; - } - if (!r.consistent) - inconsistent++; - skew_bound = r.consistent - ? (abs_double((double)r.lo_cycles) > abs_double((double)r.hi_cycles) - ? abs_double((double)r.lo_cycles) - : abs_double((double)r.hi_cycles)) - : abs_double(r.offset_cycles) + r.uncertainty_cycles; - if (abs_double(r.offset_cycles) > max_abs_offset) - max_abs_offset = abs_double(r.offset_cycles); - if (r.uncertainty_cycles > max_uncertainty) - max_uncertainty = r.uncertainty_cycles; - if (skew_bound > max_skew_bound) - max_skew_bound = skew_bound; - printf("pair %d-%d: warp_iterations=%" PRIu64 " warps=%" PRIu64 - " max_backward_cycles=%" PRIu64 " max_backward_ns=%lld rounds=%" PRIu64 - " min_rtt_ns=%lld offset_cycles=%.1f offset_ns=%lld uncertainty_ns=%lld" - " bounds_ns=[%lld,%lld] median_offset_ns=%lld consistent=%d\n", - r.cpu_a, r.cpu_b, r.warp_iterations, r.warps, r.max_warp_cycles, - round_ll(cycles_to_ns((double)r.max_warp_cycles)), r.rounds, - round_ll(cycles_to_ns((double)r.min_rtt_cycles)), r.offset_cycles, - round_ll(cycles_to_ns(r.offset_cycles)), - round_ll(cycles_to_ns(r.uncertainty_cycles)), - round_ll(cycles_to_ns((double)r.lo_cycles)), - round_ll(cycles_to_ns((double)r.hi_cycles)), - round_ll(cycles_to_ns(r.median_offset_cycles)), r.consistent); - fflush(stdout); - } - } - free(offsets_x2); - if (pairs == 0) - printf("single CPU: no pairs to compare\n"); - - pass = stalled == 0 && cycles_to_ns((double)max_backward) <= (double)bound_ns && - cycles_to_ns(max_abs_offset) <= (double)bound_ns; - // The offset estimate decides nothing when its uncertainty reaches the bound - // (for example, under trapped RDTSC, where every read exits to the VMM). - conclusive = pairs == 0 || (stalled == 0 && cycles_to_ns(max_uncertainty) < (double)bound_ns); - if (!conclusive) - printf("warning: offset uncertainty (+/-%lld ns) is not below the bound (%" PRIu64 - " ns); the verdict is not conclusive (trapped RDTSC or a descheduled vCPU?)\n", - round_ll(cycles_to_ns(max_uncertainty)), bound_ns); - printf("NVX-TIME-PROBE warp max_backward_cycles=%" PRIu64 " max_backward_ns=%lld" - " max_abs_offset_ns=%lld pairs=%d verdict=%s bound_ns=%" PRIu64 "\n", - max_backward, round_ll(cycles_to_ns((double)max_backward)), - round_ll(cycles_to_ns(max_abs_offset)), pairs, pass ? "PASS" : "FAIL", - bound_ns); - printf("NVX-TIME-PROBE warp-detail max_abs_offset_cycles=%.1f max_uncertainty_ns=%lld" - " max_skew_bound_ns=%lld total_warps=%" PRIu64 " inconsistent_pairs=%d" - " stalled_pairs=%d cpus=%s duration_ms=%" PRIu64 " tsc_hz=%.0f" - " tsc_hz_source=%s conclusive=%d\n", - max_abs_offset, round_ll(cycles_to_ns(max_uncertainty)), - round_ll(cycles_to_ns(max_skew_bound)), total_warps, inconsistent, stalled, - cpu_text, duration_ms, g_tsc.hz, g_tsc.source, conclusive); - return pass ? 0 : EXIT_FAIL; -} - -// --------------------------------------------------------------------------- -// clock -// --------------------------------------------------------------------------- - -static const char *const tsc_flag_names[] = { - "tsc", "rdtscp", "constant_tsc", "nonstop_tsc", "nonstop_tsc_s3", - "tsc_known_freq", "tsc_reliable", "tsc_deadline_timer", "tsc_adjust", "art", - "hypervisor", "arat", "aperfmperf", "hwp", "x2apic", - NULL, -}; - -static const char *const hypervisor_patterns[] = { - "Hypervisor detected", "Hyper-V", "Booting paravirtualized kernel", "kvm-guest", - NULL, -}; - -static const char *const time_patterns[] = { - "tsc", "TSC", "clocksource", "clockevent", "kvm-clock", - "APIC timer", "LAPIC", "lapic", "calibrat", "Calibrat", - "unchecked MSR", "hpet", "HPET", "timekeeping", "sched_clock", - "detected stall", "watchdog", "lockup", "hung_task", "jiffies", - "rtc", "RTC", NULL, -}; - -static const char *const param_files[] = { - "/sys/module/rcupdate/parameters/rcu_cpu_stall_suppress", - "/sys/module/rcupdate/parameters/rcu_cpu_stall_timeout", - "/proc/sys/kernel/watchdog", - "/proc/sys/kernel/soft_watchdog", - "/proc/sys/kernel/nmi_watchdog", - "/proc/sys/kernel/watchdog_thresh", - "/proc/sys/kernel/hung_task_timeout_secs", - NULL, -}; - -struct kmsg_filter { - const char *const *patterns; - int matches; - int tsc_unstable; - int unchecked_msr; -}; - -struct tick_info { - bool seen; - int tick_mode; - int state; - int hres_active; - int nohz; - char device[64]; - char handler[64]; -}; - -static void add_unique(char *list, size_t size, const char *item) -{ - size_t len = strlen(item); - - for (const char *p = list; (p = strstr(p, item)) != NULL; p += len) - if ((p == list || p[-1] == ',') && (p[len] == ',' || p[len] == '\0')) - return; - if (list[0]) - strncat(list, ",", size - strlen(list) - 1); - strncat(list, item, size - strlen(list) - 1); -} - -static void sanitize_token(char *text) -{ - for (char *p = text; *p; p++) - if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || - (*p >= '0' && *p <= '9') || *p == '#' || *p == '-' || *p == '.')) - *p = '_'; -} - -static void print_clock(const char *name, clockid_t id, struct timespec *out) -{ - struct timespec ts; - - if (clock_gettime(id, &ts) != 0) { - printf("%-20s unavailable (%s)\n", name, strerror(errno)); - return; - } - if (out) - *out = ts; - printf("%-20s %lld.%09ld", name, (long long)ts.tv_sec, ts.tv_nsec); - if (id == CLOCK_REALTIME) { - struct tm tm; - char iso[32]; - time_t seconds = ts.tv_sec; - - gmtime_r(&seconds, &tm); - strftime(iso, sizeof(iso), "%Y-%m-%dT%H:%M:%S", &tm); - printf(" (%s.%09ldZ)", iso, ts.tv_nsec); - } - printf("\n"); -} - -static void kmsg_filter_cb(uint64_t usec, const char *text, void *ctx) -{ - struct kmsg_filter *filter = ctx; - - if (strstr(text, "Marking TSC unstable") || strstr(text, "TSC unstable")) - filter->tsc_unstable++; - if (strstr(text, "unchecked MSR access")) - filter->unchecked_msr++; - for (const char *const *pattern = filter->patterns; *pattern; pattern++) { - if (strstr(text, *pattern)) { - printf("[%5llu.%06llu] %s\n", (unsigned long long)(usec / 1000000), - (unsigned long long)(usec % 1000000), text); - filter->matches++; - return; - } - } -} - -static const char *tick_mode_name(int mode) -{ - return mode == 0 ? "periodic" : mode == 1 ? "oneshot" : "unknown"; -} - -static const char *clockevent_state_name(int state) -{ - static const char *const names[] = {"detached", "shutdown", "periodic", "oneshot", - "oneshot_stopped"}; - - return state >= 0 && state <= 4 ? names[state] : "unknown"; -} - -static void print_timer_list(char *summary, size_t size) -{ - static struct tick_info cpus[MAX_CPUS]; - struct tick_info broadcast = {false, -1, -1, -1, -1, "", ""}; - struct tick_info *cur = NULL; - char devices[256] = ""; - char modes[64] = ""; - char states[128] = ""; - char line[512]; - int hr_cpu = -1; - int tick_mode = -1; - int count = 0; - FILE *file = fopen("/proc/timer_list", "r"); - - if (!file) { - printf("timer_list: unavailable (%s)\n", strerror(errno)); - snprintf(summary, size, "clockevents=unavailable"); - return; - } - for (int i = 0; i < MAX_CPUS; i++) - cpus[i] = (struct tick_info){false, -1, -1, -1, -1, "", ""}; - while (fgets(line, sizeof(line), file)) { - char name[64]; - int value; - - line[strcspn(line, "\n")] = '\0'; - if (strncmp(line, "cpu: ", 5) == 0) { - hr_cpu = atoi(line + 5); - cur = NULL; - } else if (strncmp(line, "Tick Device: mode:", 18) == 0) { - tick_mode = atoi(line + 18); - hr_cpu = -1; - cur = NULL; - } else if (strncmp(line, "Broadcast device", 16) == 0) { - cur = &broadcast; - cur->seen = true; - cur->tick_mode = tick_mode; - } else if (sscanf(line, "Per CPU device: %d", &value) == 1) { - cur = value >= 0 && value < MAX_CPUS ? &cpus[value] : NULL; - if (cur) { - cur->seen = true; - cur->tick_mode = tick_mode; - } - } else if (cur && sscanf(line, "Clock Event Device: %63s", name) == 1) { - snprintf(cur->device, sizeof(cur->device), "%s", name); - } else if (cur && sscanf(line, " mode: %d", &value) == 1) { - cur->state = value; - } else if (cur && sscanf(line, " event_handler: %63s", name) == 1) { - snprintf(cur->handler, sizeof(cur->handler), "%s", name); - } else if (hr_cpu >= 0 && hr_cpu < MAX_CPUS) { - if (sscanf(line, " .hres_active : %d", &value) == 1) - cpus[hr_cpu].hres_active = value; - else if (sscanf(line, " .nohz_mode : %d", &value) == 1 || - sscanf(line, " .nohz : %d", &value) == 1) - cpus[hr_cpu].nohz = value; - } - } - fclose(file); - - if (broadcast.seen) - printf("broadcast: device=%s state=%s tick_mode=%s handler=%s\n", - broadcast.device[0] ? broadcast.device : "none", - clockevent_state_name(broadcast.state), tick_mode_name(broadcast.tick_mode), - broadcast.handler[0] ? broadcast.handler : "none"); - for (int i = 0; i < MAX_CPUS; i++) { - if (!cpus[i].seen) - continue; - count++; - printf("cpu%d: device=%s state=%s tick_mode=%s handler=%s hres_active=%d nohz=%d\n", - i, cpus[i].device[0] ? cpus[i].device : "none", - clockevent_state_name(cpus[i].state), tick_mode_name(cpus[i].tick_mode), - cpus[i].handler[0] ? cpus[i].handler : "none", cpus[i].hres_active, - cpus[i].nohz); - add_unique(devices, sizeof(devices), cpus[i].device[0] ? cpus[i].device : "none"); - add_unique(modes, sizeof(modes), tick_mode_name(cpus[i].tick_mode)); - add_unique(states, sizeof(states), clockevent_state_name(cpus[i].state)); - } - if (!broadcast.seen && count == 0) - printf("timer_list: no tick devices found (file empty or masked)\n"); - snprintf(summary, size, - "clockevent_cpus=%d clockevent_devices=%s tick_modes=%s clockevent_states=%s" - " broadcast=%s:%s", - count, devices[0] ? devices : "none", modes[0] ? modes : "none", - states[0] ? states : "none", - broadcast.seen && broadcast.device[0] ? broadcast.device : "none", - broadcast.seen ? clockevent_state_name(broadcast.state) : "none"); -} - -struct cpuid_summary { - char vendor[16]; - uint32_t max_leaf; - uint32_t features_eax; - uint32_t features_edx; - bool hypervisor; -}; - -static void print_cpuid(struct cpuid_summary *s) -{ - uint32_t r[4]; - uint32_t max_basic, max_ext, family, model; - - memset(s, 0, sizeof(*s)); - cpuid(0, 0, r); - max_basic = r[0]; - cpuid(0x80000000u, 0, r); - max_ext = r[0]; - cpuid(1, 0, r); - family = (r[0] >> 8) & 0xf; - model = (r[0] >> 4) & 0xf; - if (family == 0x6 || family == 0xf) - model |= (r[0] >> 12) & 0xf0; - if (family == 0xf) - family += (r[0] >> 20) & 0xff; - s->hypervisor = (r[2] >> 31) & 1; - printf("cpuid.1: family=%u model=%u stepping=%u edx.tsc=%u ecx.tsc_deadline=%u" - " ecx.hypervisor=%u\n", - family, model, r[0] & 0xf, (r[3] >> 4) & 1, (r[2] >> 24) & 1, s->hypervisor); - if (max_basic >= 6) { - cpuid(6, 0, r); - printf("cpuid.6: eax.arat=%u eax.hwp=%u ecx.aperfmperf=%u\n", (r[0] >> 2) & 1, - (r[0] >> 7) & 1, r[2] & 1); - } - if (max_basic >= 7) { - cpuid(7, 0, r); - printf("cpuid.7.0: ebx.tsc_adjust=%u\n", (r[1] >> 1) & 1); - } - if (max_basic >= 0xa) { - cpuid(0xa, 0, r); - printf("cpuid.0xa: eax=%#010x (pmu_version=%u)\n", r[0], r[0] & 0xff); - } - if (max_basic >= 0x15) { - cpuid(0x15, 0, r); - printf("cpuid.0x15: eax.denominator=%u ebx.numerator=%u ecx.crystal_hz=%u", r[0], - r[1], r[2]); - if (r[0] && r[1] && r[2]) - printf(" (tsc_hz=%.0f)", (double)r[2] * r[1] / r[0]); - printf("\n"); - } - if (max_basic >= 0x16) { - cpuid(0x16, 0, r); - printf("cpuid.0x16: eax.base_mhz=%u ebx.max_mhz=%u ecx.bus_mhz=%u\n", - r[0] & 0xffff, r[1] & 0xffff, r[2] & 0xffff); - } - if (max_ext >= 0x80000001u) { - cpuid(0x80000001u, 0, r); - printf("cpuid.0x80000001: edx.rdtscp=%u\n", (r[3] >> 27) & 1); - } - if (max_ext >= 0x80000007u) { - cpuid(0x80000007u, 0, r); - printf("cpuid.0x80000007: edx.invariant_tsc=%u\n", (r[3] >> 8) & 1); - } - if (!s->hypervisor) { - printf("cpuid.0x40000000: not queried (hypervisor bit clear)\n"); - snprintf(s->vendor, sizeof(s->vendor), "none"); - return; - } - cpuid(0x40000000u, 0, r); - s->max_leaf = r[0]; - memcpy(s->vendor, &r[1], 4); - memcpy(s->vendor + 4, &r[2], 4); - memcpy(s->vendor + 8, &r[3], 4); - s->vendor[12] = '\0'; - for (int i = 0; i < 12 && s->vendor[i]; i++) - if (s->vendor[i] < 0x20 || s->vendor[i] > 0x7e) - s->vendor[i] = '.'; - printf("cpuid.0x40000000: eax.max_leaf=%#x vendor=\"%s\"\n", s->max_leaf, s->vendor); - for (uint32_t leaf = 0x40000001u; leaf <= s->max_leaf && leaf <= 0x40000010u; leaf++) { - cpuid(leaf, 0, r); - printf("cpuid.%#x: eax=%#010x ebx=%#010x ecx=%#010x edx=%#010x\n", leaf, r[0], - r[1], r[2], r[3]); - if (leaf == 0x40000003u) { - s->features_eax = r[0]; - s->features_edx = r[3]; - } - } - if (strcmp(s->vendor, "Microsoft Hv") == 0 && s->max_leaf >= 0x40000003u) - printf("hyperv.features: hypercall=%u vp_index=%u access_frequency_msrs=%u" - " access_tsc_invariant=%u frequency_msrs_available=%u\n", - (s->features_eax >> 5) & 1, (s->features_eax >> 6) & 1, - (s->features_eax >> 11) & 1, (s->features_eax >> 15) & 1, - (s->features_edx >> 8) & 1); -} - -// Opt-in: reads go through the msr driver's rdmsr_safe(), so a #GP returns -// EIO instead of faulting in the guest. -static void print_msrs(void) -{ - static const struct { - uint32_t index; - const char *name; - } msrs[] = { - {0x10, "IA32_TSC"}, - {0x3b, "IA32_TSC_ADJUST"}, - {0x40000002u, "HV_X64_MSR_VP_INDEX"}, - {0x40000022u, "HV_X64_MSR_TSC_FREQUENCY"}, - {0x40000023u, "HV_X64_MSR_APIC_FREQUENCY"}, - {0x40000118u, "HV_X64_MSR_TSC_INVARIANT_CONTROL"}, - {0x4b564d01u, "MSR_KVM_SYSTEM_TIME_NEW"}, - }; - int fd = open("/dev/cpu/0/msr", O_RDONLY | O_CLOEXEC); - - if (fd < 0) { - printf("msr: /dev/cpu/0/msr unavailable (%s)\n", strerror(errno)); - return; - } - for (size_t i = 0; i < sizeof(msrs) / sizeof(msrs[0]); i++) { - uint64_t value; - - if (pread(fd, &value, sizeof(value), (off_t)msrs[i].index) == sizeof(value)) - printf("msr %#010x %-34s = %#" PRIx64 " (%" PRIu64 ")\n", msrs[i].index, - msrs[i].name, value, value); - else - printf("msr %#010x %-34s : %s\n", msrs[i].index, msrs[i].name, - errno == EIO ? "read faulted (#GP)" : strerror(errno)); - } - close(fd); -} - -static int cmd_clock(int argc, char **argv) -{ - uint64_t measure_ms = 200; - double tsc_hz = 0; - bool read_msrs = false; - struct timespec mono = {0}; - struct kmsg_filter hv_filter = {hypervisor_patterns, 0, 0, 0}; - struct kmsg_filter time_filter = {time_patterns, 0, 0, 0}; - struct cpuid_summary ids; - char clocksource[64]; - char available[256] = ""; - char present[256] = ""; - char absent[256] = ""; - char timer_summary[512]; - char line[4096]; - uint64_t tsc; - int cpu; - - for (int i = 2; i < argc; i++) { - const char *value; - - if ((value = option_value(argc, argv, &i, "--measure-ms"))) { - if (parse_u64_arg(value, &measure_ms) || measure_ms > 60000) - return bad_option("clock", argv[i]); - } else if ((value = option_value(argc, argv, &i, "--tsc-hz"))) { - if (parse_hz_arg(value, &tsc_hz)) - return bad_option("clock", argv[i]); - } else if (strcmp(argv[i], "--msr") == 0) { - read_msrs = true; - } else { - return bad_option("clock", argv[i]); - } - } - detect_tsc_rate(tsc_hz); - - printf("nvx-time-probe %s clock\n== clocks\n", PROBE_VERSION); - cpu = sched_getcpu(); - tsc = rdtsc_fenced(); - print_clock("CLOCK_REALTIME", CLOCK_REALTIME, NULL); - print_clock("CLOCK_MONOTONIC", CLOCK_MONOTONIC, &mono); - print_clock("CLOCK_MONOTONIC_RAW", CLOCK_MONOTONIC_RAW, NULL); - print_clock("CLOCK_BOOTTIME", CLOCK_BOOTTIME, NULL); - printf("%-20s %" PRIu64 " on cpu %d", "TSC", tsc, cpu); - if (g_tsc.hz > 0) - printf(" (%.9f s; tsc_minus_monotonic_ns=%lld)", (double)tsc / g_tsc.hz, - round_ll((double)tsc * 1e9 / g_tsc.hz - - ((double)mono.tv_sec * 1e9 + (double)mono.tv_nsec))); - printf("\ntsc_hz=%.0f tsc_hz_source=%s\n", g_tsc.hz, g_tsc.source); - if (measure_ms > 0) - printf("tsc_hz_measured=%.0f (over %" PRIu64 " ms against CLOCK_MONOTONIC_RAW)\n", - measure_tsc_hz((unsigned)measure_ms), measure_ms); - - printf("== clocksource\n"); - current_clocksource(clocksource, sizeof(clocksource)); - if (read_first_line(CLOCKSOURCE_DIR "/available_clocksource", line, sizeof(line)) == 0) - for (char *token = strtok(line, " "); token; token = strtok(NULL, " ")) - add_unique(available, sizeof(available), token); - printf("current: %s\navailable: %s\n", clocksource, available[0] ? available : "unknown"); - - printf("== cpuinfo\n"); - { - FILE *file = fopen("/proc/cpuinfo", "r"); - int processors = 0; - bool model_printed = false; - char mhz[512] = ""; - - while (file && fgets(line, sizeof(line), file)) { - char *colon = strchr(line, ':'); - - line[strcspn(line, "\n")] = '\0'; - if (strncmp(line, "processor", 9) == 0) - processors++; - else if (!model_printed && strncmp(line, "model name", 10) == 0 && colon) { - printf("model name:%s\n", colon + 1); - model_printed = true; - } else if (strncmp(line, "cpu MHz", 7) == 0 && colon) - add_unique(mhz, sizeof(mhz), colon + 2); - } - if (file) - fclose(file); - printf("processors: %d\ncpu MHz: %s\n", processors, mhz[0] ? mhz : "unknown"); - } - for (const char *const *flag = tsc_flag_names; *flag; flag++) - add_unique(cpuinfo_has_flag(*flag) ? present : absent, - cpuinfo_has_flag(*flag) ? sizeof(present) : sizeof(absent), *flag); - printf("time flags present: %s\ntime flags absent: %s\n", present[0] ? present : "none", - absent[0] ? absent : "none"); - - printf("== cpuid\n"); - print_cpuid(&ids); - if (read_msrs) { - printf("== msr (cpu 0)\n"); - print_msrs(); - } - - printf("== kernel\n"); - if (read_first_line("/proc/cmdline", line, sizeof(line)) == 0) - printf("cmdline: %s\n", line); - if (read_first_line("/sys/devices/system/cpu/online", line, sizeof(line)) == 0) - printf("online cpus: %s\n", line); - for (const char *const *path = param_files; *path; path++) { - if (read_first_line(*path, line, sizeof(line)) == 0) - printf("%s = %s\n", *path, line); - else - printf("%s: absent\n", *path); - } - - printf("== kmsg: hypervisor\n"); - if (kmsg_foreach(kmsg_filter_cb, &hv_filter) != 0) - printf("(kernel log unavailable: %s)\n", strerror(errno)); - else if (!hv_filter.matches) - printf("(none)\n"); - printf("== kmsg: time\n"); - if (kmsg_foreach(kmsg_filter_cb, &time_filter) != 0) - printf("(kernel log unavailable: %s)\n", strerror(errno)); - else if (!time_filter.matches) - printf("(none)\n"); - - printf("== timer_list\n"); - print_timer_list(timer_summary, sizeof(timer_summary)); - - sanitize_token(ids.vendor); - printf("NVX-TIME-PROBE clock clocksource=%s available=%s tsc_hz=%.0f tsc_hz_source=%s" - " hv_vendor=%s hv_max_leaf=%#x hv_features_eax=%#x hv_features_edx=%#x" - " flags=%s missing_flags=%s tsc_unstable_msgs=%d unchecked_msr_msgs=%d %s\n", - clocksource, available[0] ? available : "unknown", g_tsc.hz, g_tsc.source, - ids.vendor, ids.max_leaf, ids.features_eax, ids.features_edx, - present[0] ? present : "none", absent[0] ? absent : "none", - time_filter.tsc_unstable, time_filter.unchecked_msr, timer_summary); - return 0; -} - -// --------------------------------------------------------------------------- -// main -// --------------------------------------------------------------------------- - -static void usage(FILE *out) -{ - fprintf(out, - "usage: nvx-time-probe COMMAND [OPTIONS]\n" - "\n" - "Commands:\n" - " latency [--iterations N] [--batch B] [--cpu C] [--tsc-hz HZ]\n" - " Per-operation cost (p50/p99/min/mean ns) of rdtsc, rdtscp,\n" - " lfence;rdtsc, clock_gettime(MONOTONIC/REALTIME) through the vDSO,\n" - " and syscall controls. Default: 2000 timed batches of 50 operations\n" - " on the current CPU.\n" - " warp [--duration-ms N] [--cpus LIST] [--bound-ns N] [--tsc-hz HZ]\n" - " Pairwise cross-CPU TSC checks: a check_tsc_warp-style spinlock test\n" - " and a ping-pong offset estimate, each running N ms per pair\n" - " (default 100). LIST looks like 0-3,6 (default: every CPU in the\n" - " affinity mask). The bound defaults to 1000 ns.\n" - " clock [--measure-ms N] [--tsc-hz HZ] [--msr]\n" - " Clocks, clocksources, TSC flags, CPUID time leaves, hypervisor and\n" - " time kernel messages, and the /proc/timer_list clock event summary.\n" - " --msr also reads the TSC, Hyper-V time, and kvmclock MSRs on CPU 0\n" - " through /dev/cpu/0/msr (a #GP is reported, not raised).\n" - " version\n" - "\n" - "The TSC rate comes from --tsc-hz, sysfs tsc_freq_khz, the kernel log, or\n" - "/proc/cpuinfo, in that order, and is measured as a last resort.\n" - "Exit status: 0 success, 1 warp FAIL, 2 usage error, 3 runtime error.\n"); -} - -int main(int argc, char **argv) -{ - setvbuf(stdout, NULL, _IOLBF, 0); - if (argc < 2) { - usage(stderr); - return EXIT_USAGE; - } - if (strcmp(argv[1], "latency") == 0) - return cmd_latency(argc, argv); - if (strcmp(argv[1], "warp") == 0) - return cmd_warp(argc, argv); - if (strcmp(argv[1], "clock") == 0) - return cmd_clock(argc, argv); - if (strcmp(argv[1], "version") == 0 || strcmp(argv[1], "--version") == 0) { - printf("nvx-time-probe %s\n", PROBE_VERSION); - return 0; - } - if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 || - strcmp(argv[1], "-h") == 0) { - usage(stdout); - return 0; - } - usage(stderr); - return EXIT_USAGE; -} diff --git a/guest/common/nvx-time.c b/guest/common/nvx-time.c deleted file mode 100644 index ce75ee163..000000000 --- a/guest/common/nvx-time.c +++ /dev/null @@ -1,5076 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// nvx-time: the guest time component of NVX time ABI v1. -// -// doc/design/3.2-clock-and-time.md ("Guest Obligations") is the contract this -// helper enforces. It runs the conformance checks, the violation watcher and -// the wall-clock discipline (one daemon), and the snapshot agent's time steps: -// the guest preparation steps of doc/design/5.3-snapshot-protocol.md and the -// guest repair steps of doc/design/5.5-restore-protocol.md, which the comments -// below cite by number. It prints nothing on success: checks record their -// result in the state file, and only violation events reach the console. This -// file defines the check IDs, the guest codes, and the formats of its output -// lines and the state file. -// -// boot initial clock step (C12), then the other boot -// checks asynchronously, which start the daemon -// check --phase PHASE [--new-cpus LIST] -// capture or restore checks, for diagnosis -// pre-capture capture checks and watchdog suppression -// capture REQUEST GENERATION_ID ENTROPY_FILE [--finish] -// capture request, then restore repair -// cancel-capture undo pre-capture after a rejected capture -// restore-finish [--ack] [--new-cpus LIST] -// acknowledgement, then the daemon's restore -// checks and release -// status print every recorded check and the runtime -// state, after waiting for pending checks -// sample print one host time sample -// generation-id print the VM generation ID -// exhaustive the CI-only exhaustive check -// test NAME ARGUMENTS... pure decoders and checks, for unit tests -// -// --report-only, or nvx_time_abi=report-only on the kernel command line, -// reports failures without powering the VM off and prints NVX-TIME-REPORT -// instead of NVX-TIME-ABI, so that its output never passes for a conformant -// run. Every portb transfer uses four-byte reads: a restore packet without -// memory ranges costs 24 data-port exits. - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef SYS_pidfd_open -#define SYS_pidfd_open 434 -#endif - -#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) - -#define NSEC_PER_SEC INT64_C(1000000000) -#define NSEC_PER_MSEC INT64_C(1000000) -#define NSEC_PER_USEC INT64_C(1000) -#define MAX_CPUS 64 - -#define STATUS_CONFORMANCE 193 -#define STATUS_VIOLATION 194 -#define STATUS_REPAIR 195 - -#define PORTB_DATA 0xe9 -#define PORTB_STATUS 0xea -#define PORTB_WINDOW 0xeb -#define PORT_SHUTDOWN 0x604 -#define PORT_SNAPSHOT 0x605 -#define SELECT_PACKET 0xa5 -#define SELECT_GENERATION_ID 0xa6 -#define SELECT_TIME_SAMPLE 0xa7 -#define PORTB_PACKET_AVAILABLE 0x02 -#define PORTB_GENERATION_ID_AVAILABLE 0x20 -#define PORTB_TIME_WINDOW 0x40 -#define SNAPSHOT_ACKNOWLEDGE 2 - -#define PACKET_HEADER_SIZE 32 -#define PACKET_RANGE_SIZE 16 -#define PACKET_ENTROPY_SIZE 64 -#define PACKET_MAX_RANGES 255 -#define PACKET_MAX_SIZE \ - (PACKET_HEADER_SIZE + PACKET_MAX_RANGES * PACKET_RANGE_SIZE + \ - PACKET_ENTROPY_SIZE) -#define PACKET_DOWNTIME_UTC 0x01 -#define PACKET_MEMORY_TARGET 0x02 -#define PACKET_ACK_REQUIRED 0x04 -#define PACKET_TEST_HOOKS 0x08 -#define PACKET_KNOWN_FLAGS 0x0f -#define SAMPLE_SIZE 16 -#define SAMPLE_TEST_HOOKS 0x08 -#define GENERATION_ID_SIZE 16 - -#define RATE_DEVIATION_LIMIT INT64_C(16384000) -#define FREQUENCY_LIMIT (INT64_C(500) << 16) -#define DOWNTIME_LIMIT_NS (INT64_C(2592000) * NSEC_PER_SEC) -#define REPAIR_BOUND_NS NSEC_PER_MSEC -#define BOOT_BOUND_NS NSEC_PER_MSEC -#define POLL_BOUND_NS (50 * NSEC_PER_USEC) -#define SAMPLE_ATTEMPTS 3 -#define STEP_THRESHOLD_NS (128 * NSEC_PER_MSEC) -#define SYNCHRONIZED_AGE_NS (128 * NSEC_PER_SEC) -#define FAST_PERIOD_S 16 -#define SLOW_PERIOD_S 64 -#define FAST_POLLS 4 -#define FAST_TIME_CONSTANT 4 -#define SLOW_TIME_CONSTANT 6 -#define TIMER_LIST_WAIT_NS (200 * NSEC_PER_MSEC) -#define DEFAULT_STALL_TIMEOUT_S 21 -// The asynchronous boot checks start 150 ms after the time ABI boot step, -// which init runs shortly before shell-ready, and repair step 7 150 ms -// after the acknowledgement: earlier, they contend with the readiness path -// while the VMM still demand-faults guest memory, and with the workload's own -// first activity after readiness. Both start at SCHED_IDLE and continue at -// normal priority 100 ms after they start, which bounds the fail-fast window -// under a CPU-bound workload. -#define DEFERRED_START_NS (150 * NSEC_PER_MSEC) -#define IDLE_BOUND_NS (100 * NSEC_PER_MSEC) -// nvx-time status and pre-capture wait at most 30 s for pending checks. -#define STATUS_WAIT_NS (30 * NSEC_PER_SEC) -#define STATUS_POLL_NS (10 * NSEC_PER_MSEC) -#define EVENT_MAX 512 -#define DETAIL_MAX 384 -#define TEXT_MAX 65536 - -#define RUN_DIR "/run/nvx" -#define TIME_DIR RUN_DIR "/time" -#define STATE_PATH TIME_DIR "/state" -#define STATE_TEMP_PATH TIME_DIR "/.state" -#define STATE_LOCK_PATH TIME_DIR "/state.lock" -#define DAEMON_PID_PATH TIME_DIR "/daemon.pid" -#define SUPPRESSION_PATH TIME_DIR "/suppression" -#define SUPPRESSION_LOCK_PATH TIME_DIR "/suppression.lock" -#define RESTORE_PATH TIME_DIR "/restore" -#define PORTB_LOCK_PATH RUN_DIR "/portb.lock" -#define RCU_SYNC_PATH RUN_DIR "/rcu-sync" - -#define RCU_STALL_SUPPRESS "/sys/module/rcupdate/parameters/rcu_cpu_stall_suppress" -#define RCU_STALL_TIMEOUT "/sys/module/rcupdate/parameters/rcu_cpu_stall_timeout" -#define RCU_NORMAL "/sys/kernel/rcu_normal" -#define RCU_STALL_COUNT "/sys/kernel/rcu_stall_count" -#define SOFT_WATCHDOG "/proc/sys/kernel/soft_watchdog" -#define HUNG_TASK_TIMEOUT "/proc/sys/kernel/hung_task_timeout_secs" -#define CLOCKSOURCE_DIR "/sys/devices/system/clocksource/clocksource0" - -enum phase { PHASE_BOOT, PHASE_CAPTURE, PHASE_RESTORE, PHASE_RUNTIME }; - -static const char *const k_phase_names[] = {"boot", "capture", "restore", - "runtime"}; - -static bool g_report_only; -static bool g_test; -static uint32_t g_generation; -static bool g_ports; -// Set in the daemon. Its lines can arrive while a shell or workload is in the -// middle of a console line, so each one starts with a newline. -static bool g_async_output; - -// --------------------------------------------------------------------------- -// Small utilities -// --------------------------------------------------------------------------- - -static int64_t clock_ns(clockid_t clock) -{ - struct timespec now; - - clock_gettime(clock, &now); - return (int64_t)now.tv_sec * NSEC_PER_SEC + now.tv_nsec; -} - -// The CPU time of this process, every thread's, exited threads' included: -// what a check's cpu_us reports. -static int64_t cpu_time_ns(void) -{ - return clock_ns(CLOCK_PROCESS_CPUTIME_ID); -} - -static int write_all(int fd, const void *data, size_t size) -{ - const char *bytes = data; - - while (size > 0) { - ssize_t count = write(fd, bytes, size); - - if (count < 0 && errno == EINTR) - continue; - if (count <= 0) - return -1; - bytes += count; - size -= (size_t)count; - } - return 0; -} - -static ssize_t read_bytes(const char *path, void *buffer, size_t size) -{ - int fd = open(path, O_RDONLY | O_CLOEXEC); - size_t total = 0; - - if (fd < 0) - return -1; - while (total < size) { - ssize_t count = read(fd, (char *)buffer + total, size - total); - - if (count < 0 && errno == EINTR) - continue; - if (count < 0) { - int error = errno; - - close(fd); - errno = error; - return -1; - } - if (count == 0) - break; - total += (size_t)count; - } - close(fd); - return (ssize_t)total; -} - -static ssize_t read_text(const char *path, char *buffer, size_t size) -{ - ssize_t count = read_bytes(path, buffer, size - 1); - - if (count < 0) - return -1; - buffer[count] = '\0'; - return count; -} - -typedef void (*line_consumer)(void *context, char *line); - -// Passes each line of TEXT to CONSUME, splitting TEXT in place. -static void for_each_text_line(char *text, line_consumer consume, - void *context) -{ - for (char *line = text; line != NULL && *line != '\0';) { - char *next = strchr(line, '\n'); - - if (next != NULL) - *next++ = '\0'; - consume(context, line); - line = next; - } -} - -// Streams the file at PATH into CONSUME one line at a time, so files whose -// size depends on the workload (/proc/timer_list) or on the CPU count -// (/proc/cpuinfo) are never truncated. Longer lines than the line buffer are -// cut. Returns -1 with errno set on failure. -static int for_each_file_line(const char *path, line_consumer consume, - void *context) -{ - char chunk[16384]; - char line[8192]; - size_t length = 0; - int fd = open(path, O_RDONLY | O_CLOEXEC); - - if (fd < 0) - return -1; - for (;;) { - ssize_t count = read(fd, chunk, sizeof(chunk)); - - if (count < 0 && errno == EINTR) - continue; - if (count < 0) { - int error = errno; - - close(fd); - errno = error; - return -1; - } - if (count == 0) - break; - for (ssize_t index = 0; index < count; index++) { - if (chunk[index] == '\n') { - line[length] = '\0'; - consume(context, line); - length = 0; - } else if (length < sizeof(line) - 1) { - line[length++] = chunk[index]; - } - } - } - close(fd); - if (length > 0) { - line[length] = '\0'; - consume(context, line); - } - return 0; -} - -static int parse_int64(const char *text, int64_t *value) -{ - char *end; - - errno = 0; - *value = strtoll(text, &end, 10); - if (errno != 0 || end == text) - return -1; - while (*end == '\n' || *end == ' ') - end++; - return *end == '\0' ? 0 : -1; -} - -static int read_int64(const char *path, int64_t *value) -{ - char text[64]; - - if (read_text(path, text, sizeof(text)) < 0) - return -1; - return parse_int64(text, value); -} - -static int write_text(const char *path, const char *text) -{ - int fd = open(path, O_WRONLY | O_CLOEXEC); - int result; - - if (fd < 0) - return -1; - result = write_all(fd, text, strlen(text)); - if (close(fd) != 0) - result = -1; - return result; -} - -static int write_int64(const char *path, int64_t value) -{ - char text[32]; - - snprintf(text, sizeof(text), "%" PRId64 "\n", value); - return write_text(path, text); -} - -// Atomically replaces PATH with TEXT through a temporary file in its directory. -static int replace_file(const char *path, const char *temporary, - const char *text) -{ - int fd = open(temporary, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); - int result; - - if (fd < 0) - return -1; - result = write_all(fd, text, strlen(text)); - if (close(fd) != 0) - result = -1; - if (result == 0 && rename(temporary, path) != 0) - result = -1; - if (result != 0) - unlink(temporary); - return result; -} - -static void make_runtime_directories(void) -{ - (void)mkdir(RUN_DIR, 0755); - (void)mkdir(TIME_DIR, 0755); -} - -static bool ends_with(const char *text, const char *suffix) -{ - size_t length = strlen(text); - size_t suffix_length = strlen(suffix); - - return length >= suffix_length && - strcmp(text + length - suffix_length, suffix) == 0; -} - -static uint32_t read_le32(const uint8_t *bytes) -{ - return (uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 | - (uint32_t)bytes[2] << 16 | (uint32_t)bytes[3] << 24; -} - -static uint64_t read_le64(const uint8_t *bytes) -{ - return (uint64_t)read_le32(bytes) | (uint64_t)read_le32(bytes + 4) << 32; -} - -static void format_hex(const uint8_t *bytes, size_t size, char *text) -{ - static const char digits[] = "0123456789abcdef"; - - for (size_t index = 0; index < size; index++) { - text[index * 2] = digits[bytes[index] >> 4]; - text[index * 2 + 1] = digits[bytes[index] & 0x0f]; - } - text[size * 2] = '\0'; -} - -static int parse_hex(const char *text, uint8_t *bytes, size_t size) -{ - if (strlen(text) != size * 2) - return -1; - for (size_t index = 0; index < size * 2; index++) { - char c = text[index]; - int value; - - if (c >= '0' && c <= '9') - value = c - '0'; - else if (c >= 'a' && c <= 'f') - value = c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - value = c - 'A' + 10; - else - return -1; - if (index % 2 == 0) - bytes[index / 2] = (uint8_t)(value << 4); - else - bytes[index / 2] |= (uint8_t)value; - } - return 0; -} - -// Parses a CPU list such as "0-3,5". Returns the number of CPUs in it. -static int parse_cpu_list(const char *text, bool *cpus) -{ - const char *cursor = text; - int count = 0; - - memset(cpus, 0, sizeof(bool) * MAX_CPUS); - while (*cursor != '\0' && *cursor != '\n') { - char *end; - long first = strtol(cursor, &end, 10); - long last = first; - - if (end == cursor || first < 0) - return -1; - cursor = end; - if (*cursor == '-') { - last = strtol(cursor + 1, &end, 10); - if (end == cursor + 1 || last < first) - return -1; - cursor = end; - } - if (last >= MAX_CPUS) - return -1; - for (long cpu = first; cpu <= last; cpu++) { - if (!cpus[cpu]) { - cpus[cpu] = true; - count++; - } - } - if (*cursor == ',') - cursor++; - else if (*cursor != '\0' && *cursor != '\n') - return -1; - } - return count; -} - -// --------------------------------------------------------------------------- -// Console output, events, and power-off -// --------------------------------------------------------------------------- - -static const char *abi_prefix(void) -{ - return g_report_only ? "NVX-TIME-REPORT" : "NVX-TIME-ABI"; -} - -// Escapes TEXT into OUT, which holds LIMIT characters plus a terminator. An -// escape sequence that would not fit is dropped whole, never split. -static void escape_text(const char *text, char *out, size_t limit) -{ - size_t length = 0; - - for (const unsigned char *c = (const unsigned char *)text; *c != '\0'; - c++) { - char piece[5]; - size_t size; - - if (*c == '"' || *c == '\\') { - piece[0] = '\\'; - piece[1] = (char)*c; - size = 2; - } else if (*c < 0x20 || *c > 0x7e) { - snprintf(piece, sizeof(piece), "\\x%02x", *c); - size = 4; - } else { - piece[0] = (char)*c; - size = 1; - } - if (length + size > limit) - break; - memcpy(out + length, piece, size); - length += size; - } - out[length] = '\0'; -} - -static void console_write(const char *line) -{ - char buffer[EVENT_MAX + 128]; - int fd = g_test ? -1 : open("/dev/console", O_WRONLY | O_NOCTTY | O_CLOEXEC); - - if (g_async_output) { - snprintf(buffer, sizeof(buffer), "\n%s", line); - line = buffer; - } - if (fd < 0) { - (void)write_all(g_test ? STDOUT_FILENO : STDERR_FILENO, line, - strlen(line)); - return; - } - (void)write_all(fd, line, strlen(line)); - close(fd); -} - -static int enable_ports(void) -{ - if (g_ports) - return 0; - if (g_test) { - errno = EPERM; - return -1; - } - // A four-byte read at port P needs permission for P to P + 3, so the - // reads at 0xe9 and 0xeb cover 0xe9 to 0xee. - if (ioperm(PORTB_DATA, PORTB_WINDOW + 4 - PORTB_DATA, 1) != 0 || - ioperm(PORT_SHUTDOWN, 2, 1) != 0) - return -1; - g_ports = true; - return 0; -} - -// Reads SIZE bytes from PORT four bytes per exit; the device zero-fills -// past the end of the selected record. A string read (rep insl) would take one -// exit, but OpenVMM's MSHV and WHP emulation raises #GP for it in user mode. -static void portb_read(uint16_t port, uint8_t *buffer, size_t size) -{ - for (size_t offset = 0; offset < size; offset += 4) { - uint32_t value = inl(port); - size_t count = size - offset < 4 ? size - offset : 4; - - memcpy(buffer + offset, &value, count); - } -} - -static int lock_file(const char *path) -{ - int fd; - - make_runtime_directories(); - fd = open(path, O_RDWR | O_CREAT | O_CLOEXEC, 0600); - if (fd < 0) - return -1; - while (flock(fd, LOCK_EX) != 0) { - if (errno != EINTR) { - close(fd); - return -1; - } - } - return fd; -} - -static void power_off(int status) -{ - fflush(NULL); - if (enable_ports() == 0) { - outb((unsigned char)status, PORT_SHUTDOWN); - } else { - int fd = open("/dev/port", O_WRONLY | O_CLOEXEC); - unsigned char byte = (unsigned char)status; - - if (fd >= 0) { - ssize_t written = pwrite(fd, &byte, 1, PORT_SHUTDOWN); - - (void)written; - close(fd); - } - } - for (;;) - pause(); -} - -static void format_event(char *line, const char *code, const char *source, - enum phase phase, uint32_t generation, - int64_t boottime_ns, const char *detail) -{ - int prefix = snprintf(line, EVENT_MAX + 1, - "%s-VIOLATION: v=1 code=%s source=%s phase=%s " - "generation=%" PRIu32 " boottime_ns=%" PRId64 - " detail=\"", - abi_prefix(), code, source, k_phase_names[phase], - generation, boottime_ns); - - escape_text(detail, line + prefix, EVENT_MAX - (size_t)prefix - 2); - strcat(line, "\"\n"); -} - -static void count_violation(void); - -// Writes a violation event to the kernel log, the portb data port, and the -// console. Consumers treat identical lines as one event. -static void emit_event(const char *code, const char *source, enum phase phase, - const char *detail) -{ - char line[EVENT_MAX + 1]; - char record[EVENT_MAX + 8]; - int fd; - - format_event(line, code, source, phase, g_generation, - clock_ns(CLOCK_BOOTTIME), detail); - if (g_test) { - console_write(line); - return; - } - fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); - if (fd >= 0) { - snprintf(record, sizeof(record), "<2>%s", line); - (void)write_all(fd, record, strlen(record)); - close(fd); - } - if (enable_ports() == 0) { - if (g_async_output) - outb('\n', PORTB_DATA); - for (const char *c = line; *c != '\0'; c++) - outb((unsigned char)*c, PORTB_DATA); - } - console_write(line); - count_violation(); -} - -// Reports a fatal time-ABI failure. In report-only mode it returns, and the -// caller abandons the failed step. -static void fail_fatal(int status, const char *code, const char *source, - enum phase phase, const char *format, ...) - __attribute__((format(printf, 5, 6))); - -static void fail_fatal(int status, const char *code, const char *source, - enum phase phase, const char *format, ...) -{ - char detail[DETAIL_MAX]; - va_list arguments; - - va_start(arguments, format); - vsnprintf(detail, sizeof(detail), format, arguments); - va_end(arguments); - emit_event(code, source, phase, detail); - if (!g_report_only) - power_off(status); -} - -// --------------------------------------------------------------------------- -// Published state (/run/nvx/time/state) and private records -// --------------------------------------------------------------------------- - -// A conformance check's record: STATUS is empty until the phase first runs, -// then "pending" while its check runs, then "ok" (a failed check powers the -// guest off; report-only mode records "fail" instead). ELAPSED_US is the -// check's wall time and CPU_US its CPU time over every process that ran it. -// GENERATION is g when the check ran; the boot check's is 0. FAILURES is the -// check's failure count, kept in report-only mode only, and -1 otherwise. -struct check_state { - char status[16]; - uint64_t cpus; - int64_t elapsed_us; - int64_t cpu_us; - uint64_t generation; - int64_t failures; -}; - -// The phases with a check record, indexed by enum phase. -#define CHECK_PHASES 3 - -struct time_state { - uint64_t generation; - struct check_state checks[CHECK_PHASES]; - uint64_t tsc_hz; - uint64_t lapic_hz; - uint64_t discontinuities; - char last_discontinuity[16]; - int64_t last_step_ns; - int64_t last_step_realtime_ns; - uint64_t last_downtime_ns; - char last_downtime_source[16]; - uint64_t synchronized; - int64_t offset_ns; - int64_t uncertainty_ns; - int64_t frequency_ppb; - uint64_t samples; - uint64_t rejected_samples; - char last_sample_error[24]; - uint64_t violations; -}; - -// A fresh state is published at the start of the boot checks, which are then -// pending; a state without a boot record also counts as pending. -static void state_init(struct time_state *state) -{ - memset(state, 0, sizeof(*state)); - for (int phase = 0; phase < CHECK_PHASES; phase++) - state->checks[phase].failures = -1; - snprintf(state->checks[PHASE_BOOT].status, - sizeof(state->checks[PHASE_BOOT].status), "pending"); - snprintf(state->last_discontinuity, sizeof(state->last_discontinuity), - "none"); - snprintf(state->last_downtime_source, sizeof(state->last_downtime_source), - "none"); - snprintf(state->last_sample_error, sizeof(state->last_sample_error), - "none"); -} - -static void copy_word(char *destination, size_t size, const char *value) -{ - snprintf(destination, size, "%.*s", (int)strcspn(value, "\n"), value); -} - -// Parses key=value lines. Unknown keys are ignored; a malformed number in a -// known numeric key fails the parse. -static int state_parse(char *text, struct time_state *state) -{ - struct check_state *boot = &state->checks[PHASE_BOOT]; - struct check_state *capture = &state->checks[PHASE_CAPTURE]; - struct check_state *restore = &state->checks[PHASE_RESTORE]; - struct { - const char *key; - uint64_t *unsigned_value; - int64_t *signed_value; - } const numbers[] = { - {"generation", &state->generation, NULL}, - {"boot_cpus", &boot->cpus, NULL}, - {"capture_cpus", &capture->cpus, NULL}, - {"restore_cpus", &restore->cpus, NULL}, - {"boot_elapsed_us", NULL, &boot->elapsed_us}, - {"capture_elapsed_us", NULL, &capture->elapsed_us}, - {"restore_elapsed_us", NULL, &restore->elapsed_us}, - {"boot_cpu_us", NULL, &boot->cpu_us}, - {"capture_cpu_us", NULL, &capture->cpu_us}, - {"restore_cpu_us", NULL, &restore->cpu_us}, - {"capture_generation", &capture->generation, NULL}, - {"restore_generation", &restore->generation, NULL}, - {"boot_failures", NULL, &boot->failures}, - {"capture_failures", NULL, &capture->failures}, - {"restore_failures", NULL, &restore->failures}, - {"tsc_hz", &state->tsc_hz, NULL}, - {"lapic_hz", &state->lapic_hz, NULL}, - {"discontinuities", &state->discontinuities, NULL}, - {"last_step_ns", NULL, &state->last_step_ns}, - {"last_step_realtime_ns", NULL, &state->last_step_realtime_ns}, - {"last_downtime_ns", &state->last_downtime_ns, NULL}, - {"synchronized", &state->synchronized, NULL}, - {"offset_ns", NULL, &state->offset_ns}, - {"uncertainty_ns", NULL, &state->uncertainty_ns}, - {"frequency_ppb", NULL, &state->frequency_ppb}, - {"samples", &state->samples, NULL}, - {"rejected_samples", &state->rejected_samples, NULL}, - {"violations", &state->violations, NULL}, - }; - struct { - const char *key; - char *value; - size_t size; - } const words[] = { - {"boot_status", boot->status, sizeof(boot->status)}, - {"capture_status", capture->status, sizeof(capture->status)}, - {"restore_status", restore->status, sizeof(restore->status)}, - {"last_discontinuity", state->last_discontinuity, - sizeof(state->last_discontinuity)}, - {"last_downtime_source", state->last_downtime_source, - sizeof(state->last_downtime_source)}, - {"last_sample_error", state->last_sample_error, - sizeof(state->last_sample_error)}, - }; - - state_init(state); - for (char *line = text; line != NULL && *line != '\0';) { - char *next = strchr(line, '\n'); - char *value = strchr(line, '='); - - if (next != NULL) - *next++ = '\0'; - if (value != NULL) { - *value++ = '\0'; - for (size_t index = 0; index < ARRAY_SIZE(words); index++) { - if (strcmp(line, words[index].key) == 0) - copy_word(words[index].value, words[index].size, value); - } - for (size_t index = 0; index < ARRAY_SIZE(numbers); index++) { - int64_t number; - - if (strcmp(line, numbers[index].key) != 0) - continue; - if (parse_int64(value, &number) != 0) - return -1; - if (numbers[index].unsigned_value != NULL) - *numbers[index].unsigned_value = (uint64_t)number; - else - *numbers[index].signed_value = number; - } - } - line = next; - } - return 0; -} - -// Appends a formatted line to TEXT at *LENGTH, if it fits. -static void append_text(char *text, size_t size, size_t *length, - const char *format, ...) - __attribute__((format(printf, 4, 5))); - -static void append_text(char *text, size_t size, size_t *length, - const char *format, ...) -{ - va_list arguments; - int written; - - if (*length >= size) - return; - va_start(arguments, format); - written = vsnprintf(text + *length, size - *length, format, arguments); - va_end(arguments); - if (written > 0) - *length += (size_t)written; -} - -// Formats the state file's key=value lines in their fixed order; the keys of a -// phase whose check never ran are absent. -static void state_format(const struct time_state *state, char *text, - size_t size) -{ - size_t length = 0; - - append_text(text, size, &length, "version=1\ngeneration=%" PRIu64 "\n", - state->generation); - for (int phase = 0; phase < CHECK_PHASES; phase++) { - if (state->checks[phase].status[0] != '\0') - append_text(text, size, &length, "%s_status=%s\n", - k_phase_names[phase], state->checks[phase].status); - } - for (int phase = 0; phase < CHECK_PHASES; phase++) { - if (state->checks[phase].status[0] != '\0') - append_text(text, size, &length, "%s_cpus=%" PRIu64 "\n", - k_phase_names[phase], state->checks[phase].cpus); - } - for (int phase = 0; phase < CHECK_PHASES; phase++) { - if (state->checks[phase].status[0] != '\0') - append_text(text, size, &length, "%s_elapsed_us=%" PRId64 "\n", - k_phase_names[phase], state->checks[phase].elapsed_us); - } - for (int phase = 0; phase < CHECK_PHASES; phase++) { - if (state->checks[phase].status[0] != '\0') - append_text(text, size, &length, "%s_cpu_us=%" PRId64 "\n", - k_phase_names[phase], state->checks[phase].cpu_us); - } - for (int phase = PHASE_CAPTURE; phase < CHECK_PHASES; phase++) { - if (state->checks[phase].status[0] != '\0') - append_text(text, size, &length, "%s_generation=%" PRIu64 "\n", - k_phase_names[phase], state->checks[phase].generation); - } - for (int phase = 0; phase < CHECK_PHASES; phase++) { - if (state->checks[phase].status[0] != '\0' && - state->checks[phase].failures >= 0) - append_text(text, size, &length, "%s_failures=%" PRId64 "\n", - k_phase_names[phase], state->checks[phase].failures); - } - append_text(text, size, &length, - "tsc_hz=%" PRIu64 "\n" - "lapic_hz=%" PRIu64 "\n" - "discontinuities=%" PRIu64 "\n" - "last_discontinuity=%s\n" - "last_step_ns=%" PRId64 "\n" - "last_step_realtime_ns=%" PRId64 "\n" - "last_downtime_ns=%" PRIu64 "\n" - "last_downtime_source=%s\n" - "synchronized=%" PRIu64 "\n" - "offset_ns=%" PRId64 "\n" - "uncertainty_ns=%" PRId64 "\n" - "frequency_ppb=%" PRId64 "\n" - "samples=%" PRIu64 "\n" - "rejected_samples=%" PRIu64 "\n" - "last_sample_error=%s\n" - "violations=%" PRIu64 "\n", - state->tsc_hz, state->lapic_hz, state->discontinuities, - state->last_discontinuity, state->last_step_ns, - state->last_step_realtime_ns, state->last_downtime_ns, - state->last_downtime_source, state->synchronized, - state->offset_ns, state->uncertainty_ns, state->frequency_ppb, - state->samples, state->rejected_samples, - state->last_sample_error, state->violations); -} - -static int state_load(struct time_state *state) -{ - char text[2048]; - - if (read_text(STATE_PATH, text, sizeof(text)) < 0) - return -1; - return state_parse(text, state); -} - -static int state_store(const struct time_state *state) -{ - char text[2048]; - - state_format(state, text, sizeof(text)); - return replace_file(STATE_PATH, STATE_TEMP_PATH, text); -} - -typedef void (*state_update)(struct time_state *state, const void *context); - -// Applies UPDATE to the published state under its lock. The helpers and the -// daemon all write the state this way, so updates never interleave. -static int state_apply(state_update update, const void *context) -{ - struct time_state state; - int lock = lock_file(STATE_LOCK_PATH); - int result; - - if (lock < 0) - return -1; - if (state_load(&state) != 0) - state_init(&state); - update(&state, context); - result = state_store(&state); - close(lock); - return result; -} - -static void add_violation(struct time_state *state, const void *context) -{ - (void)context; - state->violations++; -} - -static void count_violation(void) -{ - if (!g_test && access(STATE_PATH, F_OK) == 0) - (void)state_apply(add_violation, NULL); -} - -static int64_t frequency_to_ppb(int64_t frequency) -{ - int64_t scaled = frequency * 1000; - - return (scaled + (scaled >= 0 ? 32768 : -32768)) / 65536; -} - -// F and L are published in the state file by the boot check. -static int rates_load(uint64_t *tsc_hz, uint64_t *lapic_hz) -{ - struct time_state state; - - if (state_load(&state) != 0 || state.tsc_hz == 0 || state.lapic_hz == 0) - return -1; - *tsc_hz = state.tsc_hz; - *lapic_hz = state.lapic_hz; - return 0; -} - -struct check_record { - enum phase phase; - const char *status; - int cpus; - int64_t elapsed_us; - int64_t cpu_us; - uint64_t tsc_hz; - uint64_t lapic_hz; - int failures; -}; - -static void apply_check(struct time_state *state, const void *context) -{ - const struct check_record *record = context; - struct check_state *check = &state->checks[record->phase]; - - snprintf(check->status, sizeof(check->status), "%s", record->status); - check->cpus = (uint64_t)record->cpus; - check->elapsed_us = record->elapsed_us; - check->cpu_us = record->cpu_us; - check->generation = record->phase == PHASE_BOOT ? 0 : state->generation; - check->failures = -1; - // Report-only mode keeps the failures in the state file, in keys that only - // it writes. - if (g_report_only) { - check->failures = record->failures; - if (record->failures > 0 && strcmp(record->status, "ok") == 0) - snprintf(check->status, sizeof(check->status), "fail"); - } - if (record->tsc_hz != 0) - state->tsc_hz = record->tsc_hz; - if (record->lapic_hz != 0) - state->lapic_hz = record->lapic_hz; -} - -// Records a phase's conformance check in the state file: "pending" while it -// runs, then "ok" (a failed check powers the guest off instead), with the -// generation it ran in and its wall and CPU times. Rates of 0 keep the -// published ones. Report-only mode records "fail" and the failure count of a -// check that failed. -static int record_check(enum phase phase, const char *status, int cpus, - int64_t elapsed_us, int64_t cpu_us, uint64_t tsc_hz, - uint64_t lapic_hz, int failures) -{ - struct check_record record = {phase, status, cpus, elapsed_us, - cpu_us, tsc_hz, lapic_hz, failures}; - - return state_apply(apply_check, &record); -} - -// The watchdog settings saved before a capture, in the order they are -// written. Missing debug-kernel sysctls are skipped. -static const char *const k_suppressed[] = {RCU_STALL_SUPPRESS, SOFT_WATCHDOG, - HUNG_TASK_TIMEOUT}; -static const int64_t k_suppressed_values[] = {1, 0, 0}; - -static int suppression_save(void) -{ - char text[512] = ""; - size_t length = 0; - int fd; - - for (size_t index = 0; index < ARRAY_SIZE(k_suppressed); index++) { - int64_t value; - - if (access(k_suppressed[index], F_OK) != 0) { - if (index == 0) - return -1; - continue; - } - if (read_int64(k_suppressed[index], &value) != 0) - return -1; - length += (size_t)snprintf(text + length, sizeof(text) - length, - "%s=%" PRId64 "\n", k_suppressed[index], - value); - } - fd = open(SUPPRESSION_PATH, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600); - if (fd < 0) - return -1; - if (write_all(fd, text, length) != 0) { - close(fd); - unlink(SUPPRESSION_PATH); - return -1; - } - close(fd); - for (size_t index = 0; index < ARRAY_SIZE(k_suppressed); index++) { - if (access(k_suppressed[index], F_OK) == 0 && - write_int64(k_suppressed[index], k_suppressed_values[index]) != 0) - return -1; - } - return 0; -} - -// Writes back every value saved by suppression_save, then forgets them. A -// lock serializes the daemon's release at the stall timeout with the restore -// worker's; with MISSING_OK, values already written back count as restored. -static int suppression_restore(char *detail, size_t size, bool missing_ok) -{ - char text[512]; - char *line = text; - int lock = lock_file(SUPPRESSION_LOCK_PATH); - int result = -1; - - if (lock < 0) { - snprintf(detail, size, "lock %s: %s", SUPPRESSION_LOCK_PATH, - strerror(errno)); - return -1; - } - if (read_text(SUPPRESSION_PATH, text, sizeof(text)) < 0) { - if (missing_ok && errno == ENOENT) - result = 0; - else - snprintf(detail, size, "read %s: %s", SUPPRESSION_PATH, - strerror(errno)); - close(lock); - return result; - } - while (*line != '\0') { - char *next = strchr(line, '\n'); - char *value = strchr(line, '='); - - if (next != NULL) - *next++ = '\0'; - if (value == NULL) - break; - *value++ = '\0'; - if (write_text(line, value) != 0) { - snprintf(detail, size, "restore %.200s: %s", line, - strerror(errno)); - close(lock); - return -1; - } - line = next != NULL ? next : line + strlen(line); - } - if (unlink(SUPPRESSION_PATH) != 0) - snprintf(detail, size, "remove %s: %s", SUPPRESSION_PATH, - strerror(errno)); - else - result = 0; - close(lock); - return result; -} - -// --------------------------------------------------------------------------- -// Decoders and decisions (pure; exercised by `nvx-time test`) -// --------------------------------------------------------------------------- - -struct restore_packet { - uint8_t flags; - uint8_t online_vp_count; - uint8_t range_count; - uint32_t generation; - int32_t rate_deviation; - uint64_t downtime_ns; - uint64_t utc_ns; - uint64_t ranges[PACKET_MAX_RANGES][2]; - uint8_t entropy[PACKET_ENTROPY_SIZE]; -}; - -static int parse_packet_header(const uint8_t *bytes, - struct restore_packet *packet, char *detail, - size_t size) -{ - uint8_t online; - - memset(packet, 0, sizeof(*packet)); - if (memcmp(bytes, "OVR", 3) != 0) { - snprintf(detail, size, "restore packet magic is %02x%02x%02x", - bytes[0], bytes[1], bytes[2]); - return -1; - } - if (bytes[3] != 4) { - snprintf(detail, size, "restore packet version is %u", bytes[3]); - return -1; - } - packet->flags = bytes[4]; - online = bytes[5]; - packet->online_vp_count = online; - packet->range_count = bytes[6]; - packet->generation = read_le32(bytes + 8); - packet->rate_deviation = (int32_t)read_le32(bytes + 12); - packet->downtime_ns = read_le64(bytes + 16); - packet->utc_ns = read_le64(bytes + 24); - if ((packet->flags & ~PACKET_KNOWN_FLAGS) != 0) { - snprintf(detail, size, "restore packet flags 0x%02x set reserved bits", - packet->flags); - return -1; - } - if (online != 0 && online != 1 && online != 2 && online != 4 && - online != 8) { - snprintf(detail, size, "restore packet online VP count is %u", online); - return -1; - } - if ((packet->flags & PACKET_MEMORY_TARGET) == 0 && packet->range_count != 0) { - snprintf(detail, size, - "restore packet has %u memory ranges without a memory target", - packet->range_count); - return -1; - } - if (bytes[7] != 0) { - snprintf(detail, size, "restore packet reserved byte is %u", bytes[7]); - return -1; - } - if (packet->generation == 0) { - snprintf(detail, size, "restore packet generation is 0"); - return -1; - } - if (packet->rate_deviation > RATE_DEVIATION_LIMIT || - packet->rate_deviation < -RATE_DEVIATION_LIMIT) { - snprintf(detail, size, - "restore packet rate deviation %" PRId32 - " exceeds the 250 ppm tolerance", - packet->rate_deviation); - return -1; - } - if (packet->downtime_ns > (uint64_t)DOWNTIME_LIMIT_NS) { - snprintf(detail, size, - "restore packet downtime %" PRIu64 " ns exceeds 30 days", - packet->downtime_ns); - return -1; - } - if (packet->utc_ns == 0 || packet->utc_ns > (uint64_t)INT64_MAX) { - snprintf(detail, size, "restore packet UTC %" PRIu64 " is invalid", - packet->utc_ns); - return -1; - } - return 0; -} - -static size_t packet_body_size(const struct restore_packet *packet) -{ - return (size_t)packet->range_count * PACKET_RANGE_SIZE + PACKET_ENTROPY_SIZE; -} - -static int parse_packet_body(const uint8_t *bytes, - struct restore_packet *packet, char *detail, - size_t size) -{ - for (unsigned index = 0; index < packet->range_count; index++) { - uint64_t start = read_le64(bytes + index * PACKET_RANGE_SIZE); - uint64_t length = read_le64(bytes + index * PACKET_RANGE_SIZE + 8); - - if (length == 0 || start + length < start) { - snprintf(detail, size, "restore memory range %u is invalid", index); - return -1; - } - packet->ranges[index][0] = start; - packet->ranges[index][1] = length; - } - memcpy(packet->entropy, bytes + packet->range_count * PACKET_RANGE_SIZE, - PACKET_ENTROPY_SIZE); - return 0; -} - -struct time_sample { - uint8_t flags; - uint32_t generation; - uint64_t utc_ns; - int64_t t0; - int64_t t1; -}; - -static int parse_sample(const uint8_t *bytes, struct time_sample *sample, - char *detail, size_t size) -{ - uint16_t reserved = (uint16_t)(bytes[2] | bytes[3] << 8); - - sample->flags = bytes[1]; - sample->generation = read_le32(bytes + 4); - sample->utc_ns = read_le64(bytes + 8); - if (bytes[0] != 1) { - snprintf(detail, size, "time sample version is %u", bytes[0]); - return -1; - } - if ((sample->flags & ~SAMPLE_TEST_HOOKS) != 0 || reserved != 0) { - snprintf(detail, size, "time sample flags 0x%02x or reserved 0x%04x", - sample->flags, reserved); - return -1; - } - if (sample->utc_ns == 0 || sample->utc_ns > (uint64_t)INT64_MAX) { - snprintf(detail, size, "time sample UTC %" PRIu64 " is invalid", - sample->utc_ns); - return -1; - } - return 0; -} - -// The host latched UTC between the guest's CLOCK_REALTIME reads T0 and T1. -static void pair_clocks(int64_t t0, int64_t t1, uint64_t utc_ns, - int64_t *theta, int64_t *epsilon) -{ - *epsilon = (t1 - t0) / 2; - *theta = (int64_t)utc_ns - (t0 + (t1 - t0) / 2); -} - -static void offset_to_timeval(int64_t offset_ns, struct timeval *time) -{ - time->tv_sec = (time_t)(offset_ns / NSEC_PER_SEC); - time->tv_usec = (suseconds_t)(offset_ns % NSEC_PER_SEC); - if (time->tv_usec < 0) { - time->tv_sec -= 1; - time->tv_usec += NSEC_PER_SEC; - } -} - -static int64_t restore_frequency(int32_t rate_deviation) -{ - int64_t frequency = -(int64_t)rate_deviation; - - if (frequency > FREQUENCY_LIMIT) - return FREQUENCY_LIMIT; - if (frequency < -FREQUENCY_LIMIT) - return -FREQUENCY_LIMIT; - return frequency; -} - -static int64_t ceil_us(int64_t nanoseconds) -{ - return (nanoseconds + NSEC_PER_USEC - 1) / NSEC_PER_USEC; -} - -// Builds the discipline's correction for an accepted sample: a step at or -// beyond 128 ms, otherwise a PLL slew. Returns true for a step. -static bool plan_correction(int64_t theta, int64_t epsilon, bool fast, - struct timex *tx) -{ - int64_t magnitude = theta < 0 ? -theta : theta; - - memset(tx, 0, sizeof(*tx)); - if (magnitude >= STEP_THRESHOLD_NS) { - tx->modes = ADJ_SETOFFSET | ADJ_NANO; - offset_to_timeval(theta, &tx->time); - return true; - } - tx->modes = ADJ_OFFSET | ADJ_STATUS | ADJ_NANO | ADJ_TIMECONST | - ADJ_MAXERROR | ADJ_ESTERROR; - tx->offset = (long)theta; - tx->status = STA_PLL | STA_NANO; - tx->constant = fast ? FAST_TIME_CONSTANT : SLOW_TIME_CONSTANT; - tx->maxerror = (long)ceil_us(magnitude + epsilon); - tx->esterror = (long)ceil_us(epsilon); - return false; -} - -struct watch_rule { - const char *code; - const char *all[2]; - const char *any[4]; -}; - -static const struct watch_rule k_watch_rules[] = { - {"G_TSC_UNSTABLE", {"Marking TSC unstable due to ", NULL}, {NULL}}, - {"G_CLOCKSOURCE_UNSTABLE", - {"timekeeping watchdog on CPU", "as unstable because the skew is too large"}, - {NULL}}, - {"G_CLOCKSOURCE_SWITCH", {"clocksource: Switched to clocksource ", NULL}, - {NULL}}, - {"G_CLOCKSOURCE_SKEW", - {"clocksource: ", NULL}, - {" ahead of CPU ", " behind CPU ", NULL}}, - {"G_TSC_WARP", - {NULL, NULL}, - {"TSC synchronization [CPU#", "cycles TSC warp between CPUs", - "TSC warped randomly between CPUs", NULL}}, - {"G_TSC_ADJUST", {"TSC ADJUST", NULL}, {NULL}}, - {"G_RCU_STALL", - {"rcu: INFO: ", NULL}, - {" detected stalls on CPUs/tasks", " self-detected stall on CPU", - " detected expedited stalls", NULL}}, - {"G_RCU_STARVED", - {"rcu: ", NULL}, - {" kthread starved for ", " kthread timer wakeup didn't happen for ", - NULL}}, - {"G_SOFT_LOCKUP", {"watchdog: BUG: soft lockup - CPU#", NULL}, {NULL}}, - {"G_HARD_LOCKUP", {"Watchdog detected hard LOCKUP", NULL}, {NULL}}, - {"G_HUNG_TASK", {"INFO: task ", " blocked for more than "}, {NULL}}, - {"G_UNCHECKED_MSR", {"unchecked MSR access error", NULL}, {NULL}}, - {"G_APIC_ID_MISMATCH", {"APIC ID mismatch", NULL}, {NULL}}, -}; - -// Returns the code of the first rule whose substrings all appear in MESSAGE. -// The guest's own NVX-TIME lines quote kernel text and are never matched. -static const char *match_watch_rule(const char *message) -{ - if (strncmp(message, "NVX-TIME-", 9) == 0) - return NULL; - for (size_t index = 0; index < ARRAY_SIZE(k_watch_rules); index++) { - const struct watch_rule *rule = &k_watch_rules[index]; - bool all = true; - bool any = rule->any[0] == NULL; - - for (size_t part = 0; part < ARRAY_SIZE(rule->all) && rule->all[part]; - part++) - all = all && strstr(message, rule->all[part]) != NULL; - for (size_t part = 0; part < ARRAY_SIZE(rule->any) && rule->any[part]; - part++) - any = any || strstr(message, rule->any[part]) != NULL; - if (all && any) - return rule->code; - } - return NULL; -} - -struct boot_log { - bool hypervisor; - bool privileges; - bool lapic; - char last_switch[48]; - char problem[DETAIL_MAX]; - char wx[DETAIL_MAX]; -}; - -// CONFIG_DEBUG_WX audits the kernel page tables before init runs and logs -// this for a mapping that is both writable and executable. -static const char k_wx_record[] = "Found insecure W+X mapping"; - -static void boot_log_add(struct boot_log *log, const char *message) -{ - static const char *const forbidden[] = { - "Fast TSC calibration", "Refined TSC clocksource calibration", - "kvm-clock", "APIC timer: using supplied frequency"}; - static const char switched[] = "clocksource: Switched to clocksource "; - const char *name = strstr(message, switched); - const char *code; - - if (log->wx[0] == '\0' && strncmp(message, "NVX-TIME-", 9) != 0 && - strstr(message, k_wx_record) != NULL) - snprintf(log->wx, sizeof(log->wx), "%.300s", message); - if (strstr(message, "Hypervisor detected: Microsoft Hyper-V") != NULL) - log->hypervisor = true; - if (strstr(message, "Hyper-V: privilege flags low 0x8860,") != NULL) - log->privileges = true; - if (ends_with(message, "Hyper-V: LAPIC Timer Frequency: 0x989680") || - ends_with(message, "Hyper-V: LAPIC Timer Frequency: 0x1e8480")) - log->lapic = true; - if (name != NULL) { - name += sizeof(switched) - 1; - snprintf(log->last_switch, sizeof(log->last_switch), "%.*s", - (int)strcspn(name, " \n"), name); - } - if (log->problem[0] != '\0') - return; - code = match_watch_rule(message); - if (code != NULL && strcmp(code, "G_CLOCKSOURCE_SWITCH") != 0) { - snprintf(log->problem, sizeof(log->problem), "%s: %.300s", code, - message); - return; - } - for (size_t index = 0; index < ARRAY_SIZE(forbidden); index++) { - if (strncmp(message, "NVX-TIME-", 9) != 0 && - strstr(message, forbidden[index]) != NULL) { - snprintf(log->problem, sizeof(log->problem), - "forbidden record: %.300s", message); - return; - } - } -} - -static bool boot_log_verdict(const struct boot_log *log, char *detail, - size_t size) -{ - if (log->problem[0] != '\0') - snprintf(detail, size, "%s", log->problem); - else if (!log->hypervisor) - snprintf(detail, size, "no 'Hypervisor detected: Microsoft Hyper-V'"); - else if (!log->privileges) - snprintf(detail, size, "no 'Hyper-V: privilege flags low 0x8860,'"); - else if (!log->lapic) - snprintf(detail, size, "no 'Hyper-V: LAPIC Timer Frequency' of 1 GHz " - "or 200 MHz"); - else if (strcmp(log->last_switch, "tsc") != 0) - snprintf(detail, size, "last clocksource switch is to '%s', not tsc", - log->last_switch); - else - return true; - return false; -} - -// --------------------------------------------------------------------------- -// Kernel log -// --------------------------------------------------------------------------- - -typedef void (*kmsg_consumer)(void *context, const char *message); - -// Reads every available /dev/kmsg record from the non-blocking FD and passes -// each message text to CONSUME. Returns 0 when drained, -EPIPE when records -// were overwritten before they were read, or another negative errno. -static int kmsg_drain(int fd, kmsg_consumer consume, void *context) -{ - char record[8192]; - - for (;;) { - ssize_t count = read(fd, record, sizeof(record) - 1); - char *message; - - if (count < 0 && errno == EINTR) - continue; - if (count < 0) - return errno == EAGAIN ? 0 : -errno; - if (count == 0) - return 0; - record[count] = '\0'; - message = strchr(record, ';'); - if (message == NULL) - continue; - message++; - message[strcspn(message, "\n")] = '\0'; - consume(context, message); - } -} - -#define SYSLOG_ACTION_READ_ALL 3 -#define SYSLOG_ACTION_SIZE_BUFFER 10 - -// Returns the message text of one SYSLOG_ACTION_READ_ALL line, without its -// "" prefix and its printk timestamp ("[ 1.234567] "). -static const char *syslog_message(const char *line) -{ - const char *cursor = line; - const char *end = strchr(line, '>'); - const char *scan; - - if (*cursor == '<' && end != NULL) - cursor = end + 1; - if (*cursor != '[') - return cursor; - scan = cursor + 1; - while (*scan == ' ') - scan++; - while (*scan >= '0' && *scan <= '9') - scan++; - if (*scan++ != '.') - return cursor; - while (*scan >= '0' && *scan <= '9') - scan++; - if (*scan != ']') - return cursor; - return scan[1] == ' ' ? scan + 2 : scan + 1; -} - -static void boot_log_line(void *context, char *line) -{ - boot_log_add(context, syslog_message(line)); -} - -struct line_starts { - const char **starts; - size_t count; - size_t capacity; -}; - -// Records the start of every line of BUFFER that contains NEEDLE. -static bool mark_lines(const char *buffer, const char *needle, - struct line_starts *lines) -{ - for (const char *hit = strstr(buffer, needle); hit != NULL; - hit = strstr(hit + 1, needle)) { - const char *start = hit; - - while (start > buffer && start[-1] != '\n') - start--; - if (lines->count == lines->capacity) { - size_t capacity = lines->capacity * 2; - const char **grown = - realloc(lines->starts, capacity * sizeof(*lines->starts)); - - if (grown == NULL) - return false; - lines->starts = grown; - lines->capacity = capacity; - } - lines->starts[lines->count++] = start; - } - return true; -} - -static int compare_starts(const void *left, const void *right) -{ - const char *a = *(const char *const *)left; - const char *b = *(const char *const *)right; - - return a < b ? -1 : a > b; -} - -// C4 and K1 over the whole SYSLOG_ACTION_READ_ALL text. Only lines that -// contain a required record, a forbidden record, the W+X record, or the first -// substring of a watcher row can change the verdict, so a few whole-buffer -// searches find them, and boot_log_add() sees just those lines, in log order. -// Matching every rule against every line cost about a millisecond on a -// guest's boot log. -static void boot_log_scan(char *buffer, struct boot_log *log) -{ - static const char *const anchors[] = { - "Hypervisor detected: Microsoft Hyper-V", - "Hyper-V: privilege flags low 0x8860,", - "Hyper-V: LAPIC Timer Frequency: ", - "Fast TSC calibration", - "Refined TSC clocksource calibration", - "kvm-clock", - "APIC timer: using supplied frequency", - k_wx_record}; - struct line_starts lines = {malloc(256 * sizeof(const char *)), 0, 256}; - bool ok = lines.starts != NULL; - - for (size_t index = 0; ok && index < ARRAY_SIZE(anchors); index++) - ok = mark_lines(buffer, anchors[index], &lines); - for (size_t index = 0; ok && index < ARRAY_SIZE(k_watch_rules); index++) { - const struct watch_rule *rule = &k_watch_rules[index]; - - if (rule->all[0] != NULL) { - ok = mark_lines(buffer, rule->all[0], &lines); - continue; - } - for (size_t part = 0; - ok && part < ARRAY_SIZE(rule->any) && rule->any[part]; part++) - ok = mark_lines(buffer, rule->any[part], &lines); - } - if (!ok) { - free(lines.starts); - for_each_text_line(buffer, boot_log_line, log); - return; - } - qsort(lines.starts, lines.count, sizeof(*lines.starts), compare_starts); - for (size_t index = 0; index < lines.count; index++) { - char *start = (char *)lines.starts[index]; - char *end = strchr(start, '\n'); - - if (index > 0 && lines.starts[index - 1] == start) - continue; - if (end != NULL) - *end = '\0'; - boot_log_add(log, syslog_message(start)); - if (end != NULL) - *end = '\n'; - } - free(lines.starts); -} - -// Reads the whole kernel log for C4 with one SYSLOG_ACTION_READ_ALL. The -// caller has already positioned the daemon's /dev/kmsg descriptor at the end -// of the log, so records logged in between are seen twice, never missed. -// BUFFER holds SIZE + 1 bytes, the size SYSLOG_ACTION_SIZE_BUFFER reported. -static int read_boot_log_into(struct boot_log *log, char *buffer, int size) -{ - int count = klogctl(SYSLOG_ACTION_READ_ALL, buffer, size); - int error = errno; - - if (count >= 0) { - buffer[count] = '\0'; - boot_log_scan(buffer, log); - } - errno = error; - return count < 0 ? -1 : 0; -} - -// --------------------------------------------------------------------------- -// Conformance checks -// --------------------------------------------------------------------------- - -struct checks { - enum phase phase; - int failures; - int possible; - int online_count; - bool online[MAX_CPUS]; - uint64_t cpu_khz[MAX_CPUS]; - bool cpu_khz_known[MAX_CPUS]; - uint64_t tsc_hz; - uint64_t lapic_hz; -}; - -static bool check_failed_code(struct checks *checks, const char *code, - const char *format, ...) - __attribute__((format(printf, 3, 4))); - -// Serializes failure reports from the boot check's per-CPU threads, which -// share the console and the violation count. -static pthread_mutex_t g_report_lock = PTHREAD_MUTEX_INITIALIZER; - -// Emits CODE and powers off with status 193; a failed check prints no -// marker. In report-only mode it returns false and the run continues. -static bool check_failed_code(struct checks *checks, const char *code, - const char *format, ...) -{ - char detail[DETAIL_MAX]; - va_list arguments; - - va_start(arguments, format); - vsnprintf(detail, sizeof(detail), format, arguments); - va_end(arguments); - pthread_mutex_lock(&g_report_lock); - checks->failures++; - emit_event(code, "conformance", checks->phase, detail); - if (!g_report_only) - power_off(STATUS_CONFORMANCE); - pthread_mutex_unlock(&g_report_lock); - return false; -} - -static bool check_failed(struct checks *checks, const char *id, - const char *format, ...) - __attribute__((format(printf, 3, 4))); - -// check_failed_code() with the conformance code G_CONFORMANCE_. -static bool check_failed(struct checks *checks, const char *id, - const char *format, ...) -{ - char detail[DETAIL_MAX]; - char code[32]; - va_list arguments; - - va_start(arguments, format); - vsnprintf(detail, sizeof(detail), format, arguments); - va_end(arguments); - snprintf(code, sizeof(code), "G_CONFORMANCE_%s", id); - return check_failed_code(checks, code, "%s", detail); -} - -static int checks_init(struct checks *checks, enum phase phase) -{ - char text[256]; - bool possible[MAX_CPUS]; - - memset(checks, 0, sizeof(*checks)); - checks->phase = phase; - if (read_text("/sys/devices/system/cpu/possible", text, sizeof(text)) < 0) - return -1; - checks->possible = parse_cpu_list(text, possible); - if (read_text("/sys/devices/system/cpu/online", text, sizeof(text)) < 0) - return -1; - checks->online_count = parse_cpu_list(text, checks->online); - return checks->possible > 0 && checks->online_count > 0 ? 0 : -1; -} - -static void cpuid(uint32_t leaf, uint32_t subleaf, uint32_t registers[4]) -{ - __asm__ __volatile__("cpuid" - : "=a"(registers[0]), "=b"(registers[1]), - "=c"(registers[2]), "=d"(registers[3]) - : "a"(leaf), "c"(subleaf)); -} - -// Runs the calling thread on CPU only, so inline CPUID and the msr driver -// read that CPU without inter-processor calls. -static int pin_to_cpu(int cpu) -{ - cpu_set_t set; - - CPU_ZERO(&set); - CPU_SET(cpu, &set); - if (sched_setaffinity(0, sizeof(set), &set) != 0) - return -1; - return sched_getcpu() == cpu ? 0 : -1; -} - -static bool all_zero(const uint32_t value[4]) -{ - return (value[0] | value[1] | value[2] | value[3]) == 0; -} - -// C1: the identity leaves and the explicit zero leaves. The absence of a -// hypervisor signature at other bases is a static backend property that the -// CI exhaustive check covers, so the boot check skips that 255-leaf scan. -static bool check_identity(struct checks *checks, int cpu) -{ - const uint32_t capacity = (uint32_t)checks->possible; - const uint32_t expected[6][4] = { - {0x40000005, 0x7263694d, 0x666f736f, 0x76482074}, - {0x31237648, 0, 0, 0}, - {0x0058564e, 0x00010000, 0, 0}, - {0x00008860, 0, 0, 0x00000100}, - {0, 0xffffffff, 0, 0}, - {capacity, capacity, 0, 0}, - }; - static const uint32_t zero_leaves[][2] = {{0x40000006, 0x4000000f}, - {0x40000080, 0x40000082}}; - uint32_t value[4]; - - for (uint32_t index = 0; index < 6; index++) { - cpuid(0x40000000 + index, 0, value); - if (memcmp(value, expected[index], sizeof(value)) != 0) - return check_failed( - checks, "C1", - "cpu %d leaf 0x%08" PRIx32 " is %08" PRIx32 ":%08" PRIx32 - ":%08" PRIx32 ":%08" PRIx32 ", expected %08" PRIx32 - ":%08" PRIx32 ":%08" PRIx32 ":%08" PRIx32, - cpu, 0x40000000 + index, value[0], value[1], value[2], - value[3], expected[index][0], expected[index][1], - expected[index][2], expected[index][3]); - } - for (size_t range = 0; range < ARRAY_SIZE(zero_leaves); range++) { - for (uint32_t leaf = zero_leaves[range][0]; leaf <= zero_leaves[range][1]; - leaf++) { - cpuid(leaf, 0, value); - if (!all_zero(value)) - return check_failed( - checks, "C1", - "cpu %d leaf 0x%08" PRIx32 " is %08" PRIx32 ":%08" PRIx32 - ":%08" PRIx32 ":%08" PRIx32 ", expected zero", - cpu, leaf, value[0], value[1], value[2], value[3]); - } - } - return true; -} - -static bool check_bit(struct checks *checks, int cpu, const char *name, - uint32_t reg, unsigned bit, bool expected) -{ - if (((reg >> bit) & 1) == (expected ? 1u : 0u)) - return true; - return check_failed(checks, "C2", "cpu %d %s is %s", cpu, name, - expected ? "clear" : "set"); -} - -static bool check_leaf(struct checks *checks, int cpu, uint32_t leaf, - const uint32_t expected[4]) -{ - uint32_t value[4]; - - cpuid(leaf, 0, value); - if (memcmp(value, expected, sizeof(value)) == 0) - return true; - return check_failed(checks, "C2", - "cpu %d leaf 0x%08" PRIx32 " is %08" PRIx32 - ":%08" PRIx32 ":%08" PRIx32 ":%08" PRIx32 - ", expected %08" PRIx32 ":%08" PRIx32 ":%08" PRIx32 - ":%08" PRIx32, - cpu, leaf, value[0], value[1], value[2], value[3], - expected[0], expected[1], expected[2], expected[3]); -} - -// C2: the CPU time bits every profile fixes. -static bool check_time_bits(struct checks *checks, int cpu) -{ - static const uint32_t arat_only[4] = {4, 0, 0, 0}; - static const uint32_t zero[4] = {0, 0, 0, 0}; - static const uint32_t invariant_tsc[4] = {0, 0, 0, 0x100}; - uint32_t value[4]; - uint32_t max_basic; - bool ok; - - cpuid(0, 0, value); - max_basic = value[0]; - cpuid(1, 0, value); - ok = check_bit(checks, cpu, "CPUID.1:ECX[31] (hypervisor)", value[2], 31, - true) && - check_bit(checks, cpu, "CPUID.1:ECX[24] (TSC-deadline)", value[2], - 24, false) && - check_bit(checks, cpu, "CPUID.1:ECX[15] (PDCM)", value[2], 15, - false) && - check_bit(checks, cpu, "CPUID.1:EDX[4] (TSC)", value[3], 4, true); - if (!ok) - return false; - if (max_basic < 6) - return check_failed(checks, "C2", "cpu %d lacks leaf 0x6 (ARAT)", cpu); - if (!check_leaf(checks, cpu, 6, arat_only)) - return false; - if (max_basic >= 7) { - cpuid(7, 0, value); - if (!check_bit(checks, cpu, "CPUID.7.0:EBX[1] (TSC_ADJUST)", value[1], - 1, false)) - return false; - } - if (max_basic >= 0xa && !check_leaf(checks, cpu, 0xa, zero)) - return false; - if (max_basic >= 0x15 && !check_leaf(checks, cpu, 0x15, zero)) - return false; - if (max_basic >= 0x16 && !check_leaf(checks, cpu, 0x16, zero)) - return false; - cpuid(0x80000000, 0, value); - if (value[0] < 0x80000007) - return check_failed(checks, "C2", "cpu %d lacks leaf 0x80000007", cpu); - cpuid(0x80000001, 0, value); - if (!check_bit(checks, cpu, "CPUID.80000001:EDX[27] (RDTSCP)", value[3], - 27, true)) - return false; - return check_leaf(checks, cpu, 0x80000007, invariant_tsc); -} - -static int msr_read(int fd, uint32_t msr, uint64_t *value) -{ - ssize_t count = pread(fd, value, sizeof(*value), (off_t)msr); - - if (count == (ssize_t)sizeof(*value)) - return 0; - return count < 0 ? -errno : -EIO; -} - -// C3: the synthetic MSRs. At boot (FULL), the VP index on every CPU and the -// partition-wide MSRs on CPU 0 only; at restore, CPU 0's declared TSC rate. -static bool check_msrs(struct checks *checks, int cpu, bool full) -{ - static const uint32_t faulting[] = {0x40000000, 0x40000001, 0x40000020}; - char path[64]; - uint64_t value = 0; - bool ok = true; - int error = 0; - int fd; - - snprintf(path, sizeof(path), "/dev/cpu/%d/msr", cpu); - fd = open(path, O_RDONLY | O_CLOEXEC); - if (fd < 0) - return check_failed(checks, "C3", "cpu %d: open %s: %s", cpu, path, - strerror(errno)); - if (full && ((error = msr_read(fd, 0x40000002, &value)) != 0 || - value != (uint64_t)cpu)) - ok = check_failed(checks, "C3", - "cpu %d: MSR 0x40000002 (VP index) is %" PRIu64 - " (error %d)", - cpu, value, error); - if (cpu != 0) { - close(fd); - return ok; - } - if (ok && (error = msr_read(fd, 0x40000022, &value)) != 0) - ok = check_failed(checks, "C3", "cpu %d: MSR 0x40000022 read: %s", - cpu, strerror(-error)); - if (ok && checks->tsc_hz != 0 && value != checks->tsc_hz) - ok = check_failed(checks, "C3", - "cpu %d: MSR 0x40000022 is %" PRIu64 - " Hz, expected %" PRIu64 " Hz", - cpu, value, checks->tsc_hz); - if (ok && checks->tsc_hz == 0) - checks->tsc_hz = value; - if (ok && checks->cpu_khz_known[cpu] && - value / 1000 != checks->cpu_khz[cpu]) - ok = check_failed(checks, "C3", - "cpu %d: floor(F / 1000) is %" PRIu64 - " kHz but cpu MHz is %" PRIu64 " kHz", - cpu, value / 1000, checks->cpu_khz[cpu]); - if (ok && full) { - error = msr_read(fd, 0x40000023, &value); - if (error != 0 || (value != 1000000000 && value != 200000000) || - (checks->lapic_hz != 0 && value != checks->lapic_hz)) - ok = check_failed(checks, "C3", - "cpu %d: MSR 0x40000023 (LAPIC rate) is %" PRIu64 - " (error %d)", - cpu, value, error); - else - checks->lapic_hz = value; - } - if (ok && full && - ((error = msr_read(fd, 0x40000118, &value)) != 0 || value != 1)) - ok = check_failed(checks, "C3", - "cpu %d: MSR 0x40000118 is %" PRIu64 " (error %d)", - cpu, value, error); - for (size_t index = 0; ok && full && index < ARRAY_SIZE(faulting); - index++) { - error = msr_read(fd, faulting[index], &value); - if (error != -EIO) - ok = check_failed(checks, "C3", - "cpu %d: MSR 0x%08" PRIx32 - " read did not fail with EIO (error %d)", - cpu, faulting[index], error); - } - close(fd); - return ok; -} - -static bool has_word(const char *list, const char *word) -{ - size_t length = strlen(word); - - for (const char *cursor = list; (cursor = strstr(cursor, word)) != NULL; - cursor += length) { - bool starts = cursor == list || cursor[-1] == ' ' || cursor[-1] == '\t'; - char after = cursor[length]; - - if (starts && (after == '\0' || after == ' ' || after == '\n')) - return true; - } - return false; -} - -// Parses /proc/cpuinfo line by line: records each CPU's `cpu MHz` in kHz and, -// when FLAGS is set, checks C5 on the CPUs in SELECTED. The first C5 failure -// ends the check. -struct cpuinfo_parse { - struct checks *checks; - const bool *selected; - bool flags; - bool failed; - int cpu; - bool seen[MAX_CPUS]; -}; - -static void cpuinfo_init(struct cpuinfo_parse *parse, struct checks *checks, - const bool *selected, bool flags) -{ - memset(parse, 0, sizeof(*parse)); - parse->checks = checks; - parse->selected = selected; - parse->flags = flags; - parse->cpu = -1; -} - -static void cpuinfo_line(void *context, char *line) -{ - static const char *const required[] = { - "tsc", "constant_tsc", "nonstop_tsc", "tsc_known_freq", - "tsc_reliable", "rdtscp", "hypervisor", "arat"}; - static const char *const forbidden[] = {"tsc_deadline_timer", "tsc_adjust"}; - struct cpuinfo_parse *parse = context; - char *colon = strchr(line, ':'); - int cpu = parse->cpu; - - if (parse->failed || colon == NULL) - return; - if (strncmp(line, "processor", 9) == 0) { - cpu = atoi(colon + 1); - parse->cpu = cpu >= 0 && cpu < MAX_CPUS ? cpu : -1; - if (parse->cpu >= 0) - parse->seen[cpu] = true; - } else if (cpu >= 0 && strncmp(line, "cpu MHz", 7) == 0) { - char fraction[4] = "000"; - const char *dot = strchr(colon, '.'); - unsigned long long whole = strtoull(colon + 1, NULL, 10); - - for (int digit = 0; dot != NULL && digit < 3 && - dot[1 + digit] >= '0' && dot[1 + digit] <= '9'; - digit++) - fraction[digit] = dot[1 + digit]; - parse->checks->cpu_khz[cpu] = whole * 1000 + strtoull(fraction, NULL, 10); - parse->checks->cpu_khz_known[cpu] = true; - } else if (cpu >= 0 && parse->flags && parse->selected[cpu] && - strncmp(line, "flags", 5) == 0) { - for (size_t index = 0; index < ARRAY_SIZE(required); index++) { - if (!has_word(colon + 1, required[index])) { - parse->failed = true; - check_failed(parse->checks, "C5", "cpu %d lacks the %s flag", - cpu, required[index]); - return; - } - } - for (size_t index = 0; index < ARRAY_SIZE(forbidden); index++) { - if (has_word(colon + 1, forbidden[index])) { - parse->failed = true; - check_failed(parse->checks, "C5", "cpu %d has the %s flag", cpu, - forbidden[index]); - return; - } - } - } -} - -static bool cpuinfo_result(const struct cpuinfo_parse *parse) -{ - if (parse->failed) - return false; - for (int index = 0; parse->flags && index < MAX_CPUS; index++) { - if (parse->selected[index] && !parse->seen[index]) - return check_failed(parse->checks, "C5", - "cpu %d is not in /proc/cpuinfo", index); - } - return true; -} - -static bool check_cpuinfo_text(struct checks *checks, char *text, - const bool *selected, bool flags) -{ - struct cpuinfo_parse parse; - - cpuinfo_init(&parse, checks, selected, flags); - for_each_text_line(text, cpuinfo_line, &parse); - return cpuinfo_result(&parse); -} - -static bool check_cpuinfo(struct checks *checks, const bool *selected, - bool flags) -{ - struct cpuinfo_parse parse; - - cpuinfo_init(&parse, checks, selected, flags); - if (for_each_file_line("/proc/cpuinfo", cpuinfo_line, &parse) != 0) - return check_failed(checks, "C5", "read /proc/cpuinfo: %s", - strerror(errno)); - return cpuinfo_result(&parse); -} - -// --------------------------------------------------------------------------- -// Exhaustive check (CI only) -// --------------------------------------------------------------------------- - -static int msr_write(int fd, uint32_t msr, uint64_t value) -{ - ssize_t count = pwrite(fd, &value, sizeof(value), (off_t)msr); - - if (count == (ssize_t)sizeof(value)) - return 0; - return count < 0 ? -errno : -EIO; -} - -// The leaves that OpenVMM programs as explicit zeros. -static bool explicit_zero_leaf(uint32_t leaf) -{ - return (leaf >= 0x40000006 && leaf <= 0x4000000f) || - (leaf >= 0x40000080 && leaf <= 0x40000082); -} - -// X1 and X2 for one CPUID result: all zeros or, except for an explicit zero -// leaf, the Intel out-of-range result (the highest basic leaf's result for -// the same subleaf). -static bool exhaustive_leaf_ok(uint32_t leaf, const uint32_t value[4], - const uint32_t out_of_range[4], char *detail, - size_t size) -{ - char signature[13]; - - if (all_zero(value) || - (!explicit_zero_leaf(leaf) && - memcmp(value, out_of_range, 4 * sizeof(uint32_t)) == 0)) - return true; - memcpy(signature, &value[1], 4); - memcpy(signature + 4, &value[2], 4); - memcpy(signature + 8, &value[3], 4); - signature[12] = '\0'; - for (int index = 0; index < 12; index++) { - if (signature[index] < 0x20 || signature[index] > 0x7e) - signature[index] = '.'; - } - snprintf(detail, size, - "leaf 0x%08" PRIx32 " is %08" PRIx32 " %08" PRIx32 " %08" PRIx32 - " %08" PRIx32 " (signature \"%s\"), expected zero%s", - leaf, value[0], value[1], value[2], value[3], signature, - explicit_zero_leaf(leaf) ? "" : " or the highest basic leaf"); - return false; -} - -struct exhaustive { - int failures; - uint64_t tsc_hz; - uint64_t lapic_hz; - struct checks cpuinfo; -}; - -static void exhaustive_report(struct exhaustive *state, const char *id, int cpu, - bool passed, const char *detail) -{ - char escaped[DETAIL_MAX]; - - escape_text(detail, escaped, sizeof(escaped) - 1); - printf("NVX-TIME-ABI-EXHAUSTIVE: v=1 check=%s cpu=%d status=%s " - "detail=\"%s\"\n", - id, cpu, passed ? "pass" : "fail", escaped); - if (!passed) - state->failures++; -} - -static int exhaustive_summary(const struct exhaustive *state, int online) -{ - printf("NVX-TIME-ABI-EXHAUSTIVE: v=1 status=%s cpus=%d failures=%d\n", - state->failures == 0 ? "ok" : "fail", online, state->failures); - return state->failures == 0 ? 0 : 1; -} - -// X1 (every leaf 0x40000006..=0x400000ff) when STEP is 1, or X2 (every base -// 0x40000100..=0x4000ff00) when STEP is 0x100, on the current CPU. -static void exhaustive_leaves(struct exhaustive *state, const char *id, int cpu, - uint32_t first, uint32_t last, uint32_t step, - const uint32_t out_of_range[4]) -{ - char detail[DETAIL_MAX] = ""; - int failing = 0; - int count = 0; - - for (uint32_t leaf = first; leaf <= last; leaf += step, count++) { - char reason[DETAIL_MAX]; - uint32_t value[4]; - - cpuid(leaf, 0, value); - if (!exhaustive_leaf_ok(leaf, value, out_of_range, reason, - sizeof(reason)) && - failing++ == 0) - snprintf(detail, sizeof(detail), "%s", reason); - } - if (failing == 0) { - snprintf(detail, sizeof(detail), "%d %s read zero or the highest basic leaf", - count, step == 1 ? "leaves" : "bases"); - } else if (failing > 1) { - size_t length = strlen(detail); - - snprintf(detail + length, sizeof(detail) - length, "; %d of %d fail", - failing, count); - } - exhaustive_report(state, id, cpu, failing == 0, detail); -} - -// X3: every C3 property on this CPU. -static void exhaustive_x3(struct exhaustive *state, int fd, int cpu) -{ - static const uint32_t faulting[] = {0x40000000, 0x40000001, 0x40000020}; - char detail[DETAIL_MAX]; - uint64_t index = 0; - uint64_t tsc = 0; - uint64_t lapic = 0; - uint64_t control = 0; - uint64_t value; - bool ok = false; - int error; - - if ((error = msr_read(fd, 0x40000002, &index)) != 0 || - index != (uint64_t)cpu) - snprintf(detail, sizeof(detail), - "MSR 0x40000002 (VP index) is %" PRIu64 " (error %d)", index, - error); - else if ((error = msr_read(fd, 0x40000022, &tsc)) != 0 || tsc == 0 || - (state->tsc_hz != 0 && tsc != state->tsc_hz)) - snprintf(detail, sizeof(detail), - "MSR 0x40000022 (TSC rate) is %" PRIu64 - " (error %d; first CPU %" PRIu64 ")", - tsc, error, state->tsc_hz); - else if (state->cpuinfo.cpu_khz_known[cpu] && - tsc / 1000 != state->cpuinfo.cpu_khz[cpu]) - snprintf(detail, sizeof(detail), - "floor(F / 1000) is %" PRIu64 " kHz but cpu MHz is %" PRIu64 - " kHz", - tsc / 1000, state->cpuinfo.cpu_khz[cpu]); - else if ((error = msr_read(fd, 0x40000023, &lapic)) != 0 || - (lapic != 1000000000 && lapic != 200000000) || - (state->lapic_hz != 0 && lapic != state->lapic_hz)) - snprintf(detail, sizeof(detail), - "MSR 0x40000023 (LAPIC rate) is %" PRIu64 - " (error %d; first CPU %" PRIu64 ")", - lapic, error, state->lapic_hz); - else if ((error = msr_read(fd, 0x40000118, &control)) != 0 || control != 1) - snprintf(detail, sizeof(detail), - "MSR 0x40000118 is %" PRIu64 " (error %d)", control, error); - else - ok = true; - for (size_t item = 0; ok && item < ARRAY_SIZE(faulting); item++) { - error = msr_read(fd, faulting[item], &value); - if (error != -EIO) { - ok = false; - snprintf(detail, sizeof(detail), - "MSR 0x%08" PRIx32 " read did not fail with EIO (error %d)", - faulting[item], error); - } - } - if (ok) { - snprintf(detail, sizeof(detail), - "vp_index=%d tsc_hz=%" PRIu64 " lapic_hz=%" PRIu64 - " invariant_control=1", - cpu, tsc, lapic); - if (state->tsc_hz == 0) { - state->tsc_hz = tsc; - state->lapic_hz = lapic; - } - } - exhaustive_report(state, "X3", cpu, ok, detail); -} - -// X4: MSR 0x40000118 accepts 0 and 1, each read back, and rejects 2. It is -// left at 1, the value Linux wrote at boot, whatever the outcome. -static void exhaustive_x4(struct exhaustive *state, int fd, int cpu) -{ - char detail[DETAIL_MAX] = "0 and 1 accepted, 2 rejected, left at 1"; - uint64_t value = 0; - bool ok = true; - int error = 0; - - for (uint64_t written = 0; ok && written <= 1; written++) { - if ((error = msr_write(fd, 0x40000118, written)) != 0 || - (error = msr_read(fd, 0x40000118, &value)) != 0 || - value != written) { - ok = false; - snprintf(detail, sizeof(detail), - "after writing %" PRIu64 ", MSR 0x40000118 is %" PRIu64 - " (error %d)", - written, value, error); - } - } - if (ok && (error = msr_write(fd, 0x40000118, 2)) != -EIO) { - ok = false; - snprintf(detail, sizeof(detail), - "write of 2 to MSR 0x40000118 did not fail with EIO (error %d)", - error); - } - (void)msr_write(fd, 0x40000118, 1); - if (ok && (msr_read(fd, 0x40000118, &value) != 0 || value != 1)) { - ok = false; - snprintf(detail, sizeof(detail), - "MSR 0x40000118 is %" PRIu64 " after the check", value); - } - exhaustive_report(state, "X4", cpu, ok, detail); -} - -// X5: writes to the identity MSRs that are read-only or absent fail. X6: -// reads of IA32_TSC_ADJUST and IA32_TSC_DEADLINE fail. -static void exhaustive_faults(struct exhaustive *state, int fd, int cpu, - bool writes) -{ - static const uint32_t read_only[] = {0x40000000, 0x40000001, 0x40000002, - 0x40000020, 0x40000022, 0x40000023}; - static const uint32_t absent[] = {0x3b, 0x6e0}; - const uint32_t *list = writes ? read_only : absent; - size_t length = writes ? ARRAY_SIZE(read_only) : ARRAY_SIZE(absent); - char detail[DETAIL_MAX]; - bool ok = true; - - snprintf(detail, sizeof(detail), "%zu %s fail with EIO", length, - writes ? "writes" : "reads"); - for (size_t item = 0; ok && item < length; item++) { - uint64_t value = 0; - int error = writes ? msr_write(fd, list[item], 0) - : msr_read(fd, list[item], &value); - - if (error != -EIO) { - ok = false; - snprintf(detail, sizeof(detail), - "%s MSR 0x%" PRIx32 " did not fail with EIO (error %d)", - writes ? "write of 0 to" : "read of", list[item], error); - } - } - exhaustive_report(state, writes ? "X5" : "X6", cpu, ok, detail); -} - -// The CI-only exhaustive check: X1 to X6 on every online CPU. It reports and -// exits, never powers off, and production boots never run it. -static int exhaustive_run(void) -{ - static const char *const ids[] = {"X1", "X2", "X3", "X4", "X5", "X6"}; - struct cpuinfo_parse parse; - struct exhaustive state; - bool cpus[MAX_CPUS]; - cpu_set_t original; - char text[256]; - int online; - - memset(&state, 0, sizeof(state)); - if (read_text("/sys/devices/system/cpu/online", text, sizeof(text)) < 0 || - (online = parse_cpu_list(text, cpus)) <= 0) { - state.failures = 1; - return exhaustive_summary(&state, 0); - } - cpuinfo_init(&parse, &state.cpuinfo, cpus, false); - (void)for_each_file_line("/proc/cpuinfo", cpuinfo_line, &parse); - if (sched_getaffinity(0, sizeof(original), &original) != 0) - CPU_ZERO(&original); - for (int cpu = 0; cpu < MAX_CPUS; cpu++) { - char detail[DETAIL_MAX]; - uint32_t basic[4]; - uint32_t out_of_range[4]; - char path[64]; - int fd; - - if (!cpus[cpu]) - continue; - if (pin_to_cpu(cpu) != 0) { - for (size_t item = 0; item < ARRAY_SIZE(ids); item++) - exhaustive_report(&state, ids[item], cpu, false, - "cannot run on this CPU"); - continue; - } - cpuid(0, 0, basic); - cpuid(basic[0], 0, out_of_range); - exhaustive_leaves(&state, "X1", cpu, 0x40000006, 0x400000ff, 1, - out_of_range); - exhaustive_leaves(&state, "X2", cpu, 0x40000100, 0x4000ff00, 0x100, - out_of_range); - snprintf(path, sizeof(path), "/dev/cpu/%d/msr", cpu); - fd = open(path, O_RDWR | O_CLOEXEC); - if (fd < 0) { - snprintf(detail, sizeof(detail), "open %s: %s", path, - strerror(errno)); - for (size_t item = 2; item < ARRAY_SIZE(ids); item++) - exhaustive_report(&state, ids[item], cpu, false, detail); - continue; - } - exhaustive_x3(&state, fd, cpu); - exhaustive_x4(&state, fd, cpu); - exhaustive_faults(&state, fd, cpu, true); - exhaustive_faults(&state, fd, cpu, false); - close(fd); - } - if (CPU_COUNT(&original) > 0) - (void)sched_setaffinity(0, sizeof(original), &original); - return exhaustive_summary(&state, online); -} - -// C6: `tsc` is current, and only tsc, refined-jiffies, or jiffies is -// available. -static bool check_clocksource_values(struct checks *checks, const char *current, - char *available) -{ - bool tsc = false; - - if (strncmp(current, "tsc", 3) != 0 || - (current[3] != '\0' && current[3] != '\n')) - return check_failed(checks, "C6", "current clocksource is %.*s", - (int)strcspn(current, "\n"), current); - for (char *token = strtok(available, " \n"); token != NULL; - token = strtok(NULL, " \n")) { - if (strcmp(token, "tsc") == 0) - tsc = true; - else if (strcmp(token, "refined-jiffies") != 0 && - strcmp(token, "jiffies") != 0) - return check_failed(checks, "C6", "clocksource %.64s is available", - token); - } - if (!tsc) - return check_failed(checks, "C6", "tsc is not an available clocksource"); - return true; -} - -static bool check_clocksource(struct checks *checks) -{ - char current[64]; - char available[256]; - - if (read_text(CLOCKSOURCE_DIR "/current_clocksource", current, - sizeof(current)) < 0 || - read_text(CLOCKSOURCE_DIR "/available_clocksource", available, - sizeof(available)) < 0) - return check_failed(checks, "C6", "clocksource sysfs: %s", - strerror(errno)); - return check_clocksource_values(checks, current, available); -} - -struct tick_section { - bool seen; - int mode; - int state; - char device[48]; - char handler[48]; -}; - -// Line-by-line /proc/timer_list parser. The file lists every active hrtimer -// before the tick devices, so its size depends on the workload. -struct timer_list_parse { - struct tick_section cpus[MAX_CPUS]; - struct tick_section broadcast; - struct tick_section *current; - int tick_mode; -}; - -static void timer_list_init(struct timer_list_parse *parse) -{ - static const struct tick_section empty = {false, -1, -1, "", ""}; - - for (int index = 0; index < MAX_CPUS; index++) - parse->cpus[index] = empty; - parse->broadcast = empty; - parse->current = NULL; - parse->tick_mode = -1; -} - -static void timer_list_line(void *context, char *line) -{ - struct timer_list_parse *parse = context; - struct tick_section *current = parse->current; - char name[48]; - int value; - - if (sscanf(line, "Tick Device: mode: %d", &value) == 1) { - parse->tick_mode = value; - parse->current = NULL; - } else if (strncmp(line, "Broadcast device", 16) == 0) { - parse->current = &parse->broadcast; - parse->current->seen = true; - parse->current->mode = parse->tick_mode; - } else if (sscanf(line, "Per CPU device: %d", &value) == 1) { - parse->current = - value >= 0 && value < MAX_CPUS ? &parse->cpus[value] : NULL; - if (parse->current != NULL) { - parse->current->seen = true; - parse->current->mode = parse->tick_mode; - } - } else if (current != NULL && - sscanf(line, "Clock Event Device: %47s", name) == 1) { - snprintf(current->device, sizeof(current->device), "%s", name); - } else if (current != NULL && sscanf(line, " mode: %d", &value) == 1) { - current->state = value; - } else if (current != NULL && - sscanf(line, " event_handler: %47s", name) == 1) { - snprintf(current->handler, sizeof(current->handler), "%s", name); - } -} - -// C7 verdict: every CPU in SELECTED ticks with the LAPIC in one-shot mode -// through hrtimer_interrupt, and no broadcast device exists. -static bool timer_list_result(const struct timer_list_parse *parse, - const bool *selected, char *detail, size_t size) -{ - if (parse->broadcast.seen && - strcmp(parse->broadcast.device, "") != 0) { - snprintf(detail, size, "broadcast clock event device is %s", - parse->broadcast.device); - return false; - } - for (int cpu = 0; cpu < MAX_CPUS; cpu++) { - const struct tick_section *section = &parse->cpus[cpu]; - - if (!selected[cpu]) - continue; - if (!section->seen || strcmp(section->device, "lapic") != 0 || - strcmp(section->handler, "hrtimer_interrupt") != 0 || - section->mode != 1 || (section->state != 3 && section->state != 4)) { - snprintf(detail, size, - "cpu %d tick device %s handler %s tick mode %d state %d", - cpu, section->seen ? section->device : "missing", - section->handler[0] != '\0' ? section->handler : "none", - section->mode, section->state); - return false; - } - } - return true; -} - -static bool timer_list_verdict(char *text, const bool *selected, char *detail, - size_t size) -{ - static struct timer_list_parse parse; - - timer_list_init(&parse); - for_each_text_line(text, timer_list_line, &parse); - return timer_list_result(&parse, selected, detail, size); -} - -// Polls C7 for at most WAIT_NS, because a CPU switches to one-shot mode at -// its first tick. A zero wait checks once. -static bool check_timer_list(struct checks *checks, int64_t wait_ns) -{ - static struct timer_list_parse parse; - char detail[DETAIL_MAX] = ""; - int64_t deadline = clock_ns(CLOCK_MONOTONIC) + wait_ns; - - for (;;) { - timer_list_init(&parse); - if (for_each_file_line("/proc/timer_list", timer_list_line, &parse) != - 0) - return check_failed(checks, "C7", "read /proc/timer_list: %s", - strerror(errno)); - if (timer_list_result(&parse, checks->online, detail, sizeof(detail))) - return true; - if (clock_ns(CLOCK_MONOTONIC) >= deadline) - return check_failed(checks, "C7", "%s", detail); - nanosleep(&(struct timespec){0, 10 * NSEC_PER_MSEC}, NULL); - } -} - -// C8: no VMBus, no cpufreq, and the production RCU stall settings. -static bool check_platform(struct checks *checks) -{ - struct stat info; - int64_t value = -1; - - if (stat("/sys/bus/vmbus", &info) == 0) - return check_failed(checks, "C8", "/sys/bus/vmbus exists"); - if (stat("/sys/devices/system/cpu/cpufreq/policy0", &info) == 0) - return check_failed(checks, "C8", "cpufreq policy0 exists"); - if (read_int64(RCU_STALL_SUPPRESS, &value) != 0 || value != 0) - return check_failed(checks, "C8", "rcu_cpu_stall_suppress is %" PRId64, - value); - if (read_int64(RCU_STALL_TIMEOUT, &value) != 0 || value != 21) - return check_failed(checks, "C8", "rcu_cpu_stall_timeout is %" PRId64, - value); - return true; -} - -// C9: no clock workaround on the kernel command line. -static bool check_cmdline_text(struct checks *checks, char *cmdline) -{ - static const char *const prefixes[] = {"tsc_early_khz=", "lapic_timer_hz="}; - static const char *const tokens[] = {"notsc", "nolapic", "nolapic_timer", - "tsc=unstable", "hpet=force"}; - - for (char *token = strtok(cmdline, " \t\n"); token != NULL; - token = strtok(NULL, " \t\n")) { - bool bad = strncmp(token, "clocksource=", 12) == 0 && - strcmp(token + 12, "tsc") != 0; - - for (size_t index = 0; index < ARRAY_SIZE(prefixes); index++) - bad = bad || strncmp(token, prefixes[index], - strlen(prefixes[index])) == 0; - for (size_t index = 0; index < ARRAY_SIZE(tokens); index++) - bad = bad || strcmp(token, tokens[index]) == 0; - if (bad) - return check_failed(checks, "C9", "command line contains %.120s", - token); - } - return true; -} - -static bool check_cmdline(struct checks *checks) -{ - char text[4096]; - - if (read_text("/proc/cmdline", text, sizeof(text)) < 0) - return check_failed(checks, "C9", "read /proc/cmdline: %s", - strerror(errno)); - return check_cmdline_text(checks, text); -} - -static pid_t daemon_pid(void) -{ - char path[64]; - char comm[32]; - int64_t pid; - - if (read_int64(DAEMON_PID_PATH, &pid) != 0 || pid <= 1 || pid > INT_MAX || - kill((pid_t)pid, 0) != 0) - return -1; - snprintf(path, sizeof(path), "/proc/%" PRId64 "/comm", pid); - if (read_text(path, comm, sizeof(comm)) < 0 || - strcmp(comm, "nvx-time\n") != 0) - return -1; - return (pid_t)pid; -} - -// The asynchronous boot checks and repair step 7 start at idle priority, so -// that on few vCPUs they never take a CPU from the workload, and continue at -// normal priority after IDLE_BOUND_NS; the watcher and the discipline keep -// normal priority. musl does not implement sched_setscheduler(), so this uses -// the per-thread system call: PID 0 is the calling thread. -static void set_idle_priority(pid_t pid, bool idle) -{ - struct sched_param param = {0}; - - (void)syscall(SYS_sched_setscheduler, pid, idle ? SCHED_IDLE : SCHED_OTHER, - ¶m); -} - -// Raises every thread of this process to normal priority, twice, so that a -// thread an idle thread created during the first pass is raised too. -static void promote_threads(void) -{ - for (int pass = 0; pass < 2; pass++) { - DIR *tasks = opendir("/proc/self/task"); - struct dirent *entry; - - if (tasks == NULL) - return; - while ((entry = readdir(tasks)) != NULL) { - long tid = strtol(entry->d_name, NULL, 10); - - if (tid > 0) - set_idle_priority((pid_t)tid, false); - } - closedir(tasks); - } -} - -// A normal-priority thread that raises the boot checker's idle threads when -// the checks still run IDLE_BOUND_NS after they started. -struct promoter { - pthread_t thread; - pthread_mutex_t lock; - pthread_cond_t stop; - struct timespec deadline; - bool stopped; -}; - -static void *promoter_run(void *argument) -{ - struct promoter *promoter = argument; - bool stopped; - int result = 0; - - pthread_mutex_lock(&promoter->lock); - while (!promoter->stopped && result == 0) - result = pthread_cond_timedwait(&promoter->stop, &promoter->lock, - &promoter->deadline); - stopped = promoter->stopped; - pthread_mutex_unlock(&promoter->lock); - if (!stopped) - promote_threads(); - return NULL; -} - -// Starts PROMOTER, at the caller's priority, for checks that started at -// START_NS. Returns true when it runs. -static bool promoter_start(struct promoter *promoter, int64_t start_ns) -{ - int64_t deadline = start_ns + IDLE_BOUND_NS; - pthread_condattr_t attributes; - bool ready; - - promoter->stopped = false; - promoter->deadline.tv_sec = deadline / NSEC_PER_SEC; - promoter->deadline.tv_nsec = deadline % NSEC_PER_SEC; - if (pthread_condattr_init(&attributes) != 0) - return false; - ready = pthread_condattr_setclock(&attributes, CLOCK_MONOTONIC) == 0 && - pthread_cond_init(&promoter->stop, &attributes) == 0; - pthread_condattr_destroy(&attributes); - return ready && pthread_mutex_init(&promoter->lock, NULL) == 0 && - pthread_create(&promoter->thread, NULL, promoter_run, promoter) == - 0; -} - -static void promoter_stop(struct promoter *promoter) -{ - pthread_mutex_lock(&promoter->lock); - promoter->stopped = true; - pthread_cond_signal(&promoter->stop); - pthread_mutex_unlock(&promoter->lock); - pthread_join(promoter->thread, NULL); -} - -// Repair steps 6 and 7 on the readiness path: the acknowledgement when the -// host gated its input, then hand step 7 to DAEMON. The sigqueue() value is -// the readiness path's wall time since START_NS (CLOCK_MONOTONIC), in -// microseconds clamped to 32 bits, read after the acknowledgement so that the -// read does not delay it. The readiness path's CPU time is not reported: the -// restore latency gate measures that path. Returns -1, without acknowledging, -// when no daemon runs; 1 when the daemon vanished after the acknowledgement. -// The caller has enabled the ports when ACK is set. -static int signal_restore(pid_t daemon, bool ack, int64_t start_ns) -{ - union sigval value; - int64_t elapsed_us; - - if (daemon <= 0 || kill(daemon, 0) != 0) - return -1; - if (ack) - outb(SNAPSHOT_ACKNOWLEDGE, PORT_SNAPSHOT); - elapsed_us = (clock_ns(CLOCK_MONOTONIC) - start_ns) / NSEC_PER_USEC; - if (elapsed_us > UINT32_MAX) - elapsed_us = UINT32_MAX; - value.sival_ptr = (void *)(uintptr_t)(elapsed_us < 0 ? 0 : elapsed_us); - return sigqueue(daemon, SIGUSR1, value) == 0 ? 0 : 1; -} - -// C10: the daemon runs, has recorded no violation, and no stall was counted. -static bool check_daemon(struct checks *checks) -{ - struct time_state state; - int64_t stalls = -1; - - if (daemon_pid() < 0) - return check_failed(checks, "C10", "the time daemon is not running"); - if (state_load(&state) != 0) - return check_failed(checks, "C10", "time state is unreadable"); - if (state.violations != 0) - return check_failed(checks, "C10", - "the time daemon recorded %" PRIu64 " violations", - state.violations); - if (read_int64(RCU_STALL_COUNT, &stalls) != 0 || stalls != 0) - return check_failed(checks, "C10", "rcu_stall_count is %" PRId64, - stalls); - return true; -} - -// C11: on the CI debug kernel, the soft-lockup and hung-task detectors run. -static bool check_debug_watchdogs(struct checks *checks) -{ - bool soft = access(SOFT_WATCHDOG, F_OK) == 0; - bool hung = access(HUNG_TASK_TIMEOUT, F_OK) == 0; - int64_t value = -1; - - if (!soft && !hung) - return true; - if (!soft || read_int64(SOFT_WATCHDOG, &value) != 0 || value != 1) - return check_failed(checks, "C11", "soft_watchdog is %" PRId64, value); - if (!hung || read_int64(HUNG_TASK_TIMEOUT, &value) != 0 || value == 0) - return check_failed(checks, "C11", - "hung_task_timeout_secs is %" PRId64, value); - return true; -} - -// C1, C2, and C3 on every CPU in SELECTED, pinned to each in turn. The -// three checks are independent, so a report-only run shows each failure; -// the loop stops at the first CPU that fails. -static bool check_cpus(struct checks *checks, const bool *selected, - bool identity, bool msrs) -{ - cpu_set_t original; - bool ok = true; - - if (sched_getaffinity(0, sizeof(original), &original) != 0) - return check_failed(checks, "C1", "sched_getaffinity: %s", - strerror(errno)); - for (int cpu = 0; ok && cpu < MAX_CPUS; cpu++) { - if (!selected[cpu]) - continue; - if (pin_to_cpu(cpu) != 0) { - ok = check_failed(checks, "C1", "cannot run on cpu %d: %s", cpu, - strerror(errno)); - break; - } - if (identity) { - if (!check_identity(checks, cpu)) - ok = false; - if (!check_time_bits(checks, cpu)) - ok = false; - } - if (msrs && !check_msrs(checks, cpu, true)) - ok = false; - } - (void)sched_setaffinity(0, sizeof(original), &original); - return ok; -} - -struct cpu_job { - pthread_t thread; - struct checks checks; - bool identity; - bool msrs; - bool running; - int cpu; -}; - -static void *cpu_job_run(void *argument) -{ - struct cpu_job *job = argument; - - if (pin_to_cpu(job->cpu) != 0) { - check_failed(&job->checks, "C1", "cannot run on cpu %d: %s", job->cpu, - strerror(errno)); - return NULL; - } - if (job->identity) { - check_identity(&job->checks, job->cpu); - check_time_bits(&job->checks, job->cpu); - } - if (job->msrs) - check_msrs(&job->checks, job->cpu, true); - return NULL; -} - -// A boot check that runs in its own thread beside the per-CPU checks, on a -// private copy of the checks. -struct side_job { - pthread_t thread; - struct checks checks; - void (*run)(struct checks *checks, void *context); - void *context; - bool running; -}; - -static void *side_job_run(void *argument) -{ - struct side_job *job = argument; - - job->run(&job->checks, job->context); - return NULL; -} - -// check_cpus() with every CPU in its own thread pinned to it, beside the -// SIDES checks, each in a thread of its own, while the caller runs FOREGROUND -// (when set). Running them in parallel keeps the cost from growing with the -// CPU count and leaves the caller on its warm CPU. The threads share one -// stack mapping, so creating one maps nothing. Each job works on a copy of -// CHECKS whose failures are added back; a job whose thread cannot start runs -// in the caller afterwards. -static void check_cpus_parallel(struct checks *checks, const bool *selected, - bool identity, bool msrs, - struct side_job *sides, int side_count, - void (*foreground)(struct checks *, void *), - void *foreground_context) -{ - enum { STACK_SIZE = 128 * 1024 }; - static struct cpu_job jobs[MAX_CPUS]; - bool serial[MAX_CPUS] = {false}; - bool any_serial = false; - pthread_attr_t attributes; - pthread_attr_t *attr = NULL; - size_t threads = (size_t)side_count; - size_t next = 0; - uint8_t *stacks; - - for (int cpu = 0; cpu < MAX_CPUS; cpu++) - threads += selected[cpu] ? 1 : 0; - // With one CPU, threads only add their own cost. - if (threads <= (size_t)side_count + 1) { - if (foreground != NULL) - foreground(checks, foreground_context); - for (int index = 0; index < side_count; index++) { - struct side_job *side = &sides[index]; - - side->checks = *checks; - side->checks.failures = 0; - side->run(&side->checks, side->context); - checks->failures += side->checks.failures; - } - check_cpus(checks, selected, identity, msrs); - return; - } - stacks = mmap(NULL, threads * STACK_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (stacks != MAP_FAILED && pthread_attr_init(&attributes) == 0) - attr = &attributes; - for (int index = 0; index < side_count; index++) { - struct side_job *side = &sides[index]; - - side->checks = *checks; - side->checks.failures = 0; - if (attr != NULL) - pthread_attr_setstack(attr, stacks + STACK_SIZE * next++, - STACK_SIZE); - side->running = - pthread_create(&side->thread, attr, side_job_run, side) == 0; - } - for (int cpu = 0; cpu < MAX_CPUS; cpu++) { - struct cpu_job *job = &jobs[cpu]; - - job->running = false; - if (!selected[cpu]) - continue; - job->checks = *checks; - job->checks.failures = 0; - job->identity = identity; - job->msrs = msrs; - job->cpu = cpu; - if (attr != NULL) - pthread_attr_setstack(attr, stacks + STACK_SIZE * next++, - STACK_SIZE); - if (pthread_create(&job->thread, attr, cpu_job_run, job) == 0) - job->running = true; - else - serial[cpu] = any_serial = true; - } - if (foreground != NULL) - foreground(checks, foreground_context); - for (int index = 0; index < side_count; index++) { - struct side_job *side = &sides[index]; - - if (side->running) - pthread_join(side->thread, NULL); - else - side->run(&side->checks, side->context); - checks->failures += side->checks.failures; - } - for (int cpu = 0; cpu < MAX_CPUS; cpu++) { - struct cpu_job *job = &jobs[cpu]; - - if (!job->running) - continue; - pthread_join(job->thread, NULL); - checks->failures += job->checks.failures; - // C3 reads the partition-wide rates on CPU 0 only. - if (cpu == 0 && msrs) { - checks->tsc_hz = job->checks.tsc_hz; - checks->lapic_hz = job->checks.lapic_hz; - } - } - if (attr != NULL) - pthread_attr_destroy(attr); - if (stacks != MAP_FAILED) - munmap(stacks, threads * STACK_SIZE); - if (any_serial) - check_cpus(checks, serial, identity, msrs); -} -// --------------------------------------------------------------------------- -// Host time samples and clock adjustment -// --------------------------------------------------------------------------- - -// Latches a sample with selector 0xa7 and reads it from 0xeb. CLOCK_REALTIME -// brackets the selector write, so the host instant lies in [t0, t1]. The -// caller holds the portb lock. -static int take_sample(struct time_sample *sample, char *detail, size_t size) -{ - uint8_t bytes[SAMPLE_SIZE]; - - sample->t0 = clock_ns(CLOCK_REALTIME); - outb(SELECT_TIME_SAMPLE, PORTB_STATUS); - sample->t1 = clock_ns(CLOCK_REALTIME); - portb_read(PORTB_WINDOW, bytes, sizeof(bytes)); - return parse_sample(bytes, sample, detail, size); -} - -static int lock_portb(char *detail, size_t size) -{ - int lock = lock_file(PORTB_LOCK_PATH); - - if (lock < 0) - snprintf(detail, size, "portb lock: %s", strerror(errno)); - return lock; -} - -// Takes up to ATTEMPTS samples of generation G and keeps the first whose -// uncertainty is within BOUND_NS. Returns 0 when one is, 1 when only wider -// samples were seen (the narrowest is returned), and -1 when none was valid -// or the VMM has no time-sample window. A sample from another generation -// stops the search with -2. The caller holds the portb lock. -static int best_sample(int attempts, int64_t bound_ns, uint32_t generation, - struct time_sample *best, int64_t *theta, - int64_t *epsilon, char *detail, size_t size) -{ - unsigned status; - int result = -1; - - *epsilon = INT64_MAX; - if (enable_ports() != 0) { - snprintf(detail, size, "ioperm: %s", strerror(errno)); - return -1; - } - status = inb(PORTB_STATUS); - if ((status & PORTB_TIME_WINDOW) == 0) { - snprintf(detail, size, "portb status 0x%02x has no time-sample window", - status); - return -1; - } - for (int attempt = 0; attempt < attempts; attempt++) { - struct time_sample sample; - int64_t sample_theta; - int64_t sample_epsilon; - - if (take_sample(&sample, detail, size) != 0) - continue; - if (sample.generation != generation) { - snprintf(detail, size, - "time sample generation %" PRIu32 ", expected %" PRIu32, - sample.generation, generation); - return -2; - } - pair_clocks(sample.t0, sample.t1, sample.utc_ns, &sample_theta, - &sample_epsilon); - if (sample_epsilon < *epsilon) { - *best = sample; - *theta = sample_theta; - *epsilon = sample_epsilon; - result = 1; - } - if (sample_epsilon <= bound_ns) - return 0; - snprintf(detail, size, - "time sample uncertainty %" PRId64 " ns exceeds %" PRId64 - " ns", - sample_epsilon, bound_ns); - } - return result; -} - -static int step_clock(int64_t offset_ns) -{ - struct timex tx; - - memset(&tx, 0, sizeof(tx)); - tx.modes = ADJ_SETOFFSET | ADJ_NANO; - offset_to_timeval(offset_ns, &tx.time); - return adjtimex(&tx) < 0 ? -1 : 0; -} - -static int set_frequency(int64_t frequency) -{ - struct timex tx; - - memset(&tx, 0, sizeof(tx)); - tx.modes = ADJ_FREQUENCY | ADJ_STATUS; - tx.freq = (long)frequency; - tx.status = STA_PLL | STA_NANO; - return adjtimex(&tx) < 0 ? -1 : 0; -} - -static int64_t current_frequency(void) -{ - struct timex tx; - - memset(&tx, 0, sizeof(tx)); - return adjtimex(&tx) < 0 ? 0 : (int64_t)tx.freq; -} - -// C12: one sample within 1 ms (three attempts), then a clock step. The boot -// step is not a discontinuity. -static bool check_initial_sample(struct checks *checks, int64_t *theta, - int64_t *epsilon) -{ - struct time_sample sample; - char detail[DETAIL_MAX] = "no valid time sample"; - int result; - int lock; - - lock = lock_portb(detail, sizeof(detail)); - if (lock < 0) - return check_failed(checks, "C12", "%s", detail); - result = best_sample(SAMPLE_ATTEMPTS, BOOT_BOUND_NS, g_generation, &sample, - theta, epsilon, detail, sizeof(detail)); - close(lock); - if (result != 0) - return check_failed(checks, "C12", "%s", detail); - if (step_clock(*theta) != 0) - return check_failed(checks, "C12", "adjtimex step: %s", - strerror(errno)); - return true; -} - -// --------------------------------------------------------------------------- -// Daemon: violation watcher, wall-clock discipline, restore completion -// --------------------------------------------------------------------------- - -struct daemon { - int kmsg; - int signals; - int timer; - int polls; - int64_t last_accepted_ns; - pid_t worker_pid; - int64_t worker_promote_ns; - int64_t worker_deadline_ns; - bool suppression_released; - bool restore_pending; - int64_t restore_due_ns; - int64_t readiness_us; -}; - -static void watcher_consume(void *context, const char *message) -{ - const char *code = match_watch_rule(message); - - (void)context; - if (code != NULL) - fail_fatal(STATUS_VIOLATION, code, "watcher", PHASE_RUNTIME, "%.400s", - message); -} - -static void watch_kernel_log(struct daemon *daemon) -{ - if (kmsg_drain(daemon->kmsg, watcher_consume, daemon) == -EPIPE) - fail_fatal(STATUS_VIOLATION, "G_KMSG_OVERRUN", "watcher", - PHASE_RUNTIME, "kernel log records were lost (EPIPE)"); -} - -static int arm_timer(struct daemon *daemon) -{ - struct itimerspec period; - - memset(&period, 0, sizeof(period)); - period.it_value.tv_sec = - daemon->polls < FAST_POLLS ? FAST_PERIOD_S : SLOW_PERIOD_S; - return timerfd_settime(daemon->timer, 0, &period, NULL); -} - -struct poll_outcome { - bool accepted; - bool stepped; - bool synchronized; - int64_t theta; - int64_t epsilon; - int64_t step_realtime_ns; - int64_t frequency; -}; - -static void apply_poll(struct time_state *state, const void *context) -{ - const struct poll_outcome *outcome = context; - - if (outcome->accepted) { - state->samples++; - state->offset_ns = outcome->theta; - state->uncertainty_ns = outcome->epsilon; - state->frequency_ppb = frequency_to_ppb(outcome->frequency); - snprintf(state->last_sample_error, sizeof(state->last_sample_error), - "none"); - if (outcome->stepped) { - state->discontinuities++; - snprintf(state->last_discontinuity, - sizeof(state->last_discontinuity), "step"); - state->last_step_ns = outcome->theta; - state->last_step_realtime_ns = outcome->step_realtime_ns; - } - } else { - state->rejected_samples++; - snprintf(state->last_sample_error, sizeof(state->last_sample_error), - "G_SAMPLE_UNCERTAIN"); - } - state->synchronized = outcome->synchronized; -} - -// One discipline poll: the RCU stall backstop, then up to three samples. A -// step reapplies the frequency and status that the step's NTP reset clears. -// The poll holds the portb lock from its first sample to the published state, -// as a capture does from its request to the restored clock, so a snapshot -// never contains a half-applied poll: a poll blocked behind a capture samples -// after the restore, sees the new generation, and is discarded. -static void discipline_poll(struct daemon *daemon) -{ - struct poll_outcome outcome; - struct time_sample sample; - char detail[DETAIL_MAX] = ""; - bool fast = daemon->polls < FAST_POLLS; - int64_t stalls = 0; - int result; - int lock; - - memset(&outcome, 0, sizeof(outcome)); - if (read_int64(RCU_STALL_COUNT, &stalls) == 0 && stalls != 0) - fail_fatal(STATUS_VIOLATION, "G_RCU_STALL", "watcher", PHASE_RUNTIME, - "rcu_stall_count is %" PRId64, stalls); - daemon->polls++; - lock = lock_portb(detail, sizeof(detail)); - result = lock < 0 ? -1 - : best_sample(SAMPLE_ATTEMPTS, POLL_BOUND_NS, - g_generation, &sample, &outcome.theta, - &outcome.epsilon, detail, sizeof(detail)); - if (result == -2) { - close(lock); - return; - } - if (result == 0) { - struct timex tx; - int64_t frequency = current_frequency(); - - outcome.stepped = plan_correction(outcome.theta, outcome.epsilon, fast, - &tx); - outcome.accepted = adjtimex(&tx) >= 0 && - (!outcome.stepped || set_frequency(frequency) == 0); - outcome.step_realtime_ns = clock_ns(CLOCK_REALTIME); - outcome.frequency = current_frequency(); - if (outcome.accepted) - daemon->last_accepted_ns = clock_ns(CLOCK_MONOTONIC); - } - outcome.synchronized = - daemon->last_accepted_ns != 0 && - clock_ns(CLOCK_MONOTONIC) - daemon->last_accepted_ns <= - SYNCHRONIZED_AGE_NS; - (void)state_apply(apply_poll, &outcome); - if (lock >= 0) - close(lock); -} - -// Waits for a full normal RCU grace period: with rcu_normal set, the -// synchronize_rcu_expedited() in every unmount waits for one that starts -// after the call, on any number of CPUs. The private mount namespace makes the -// caller a process of its own: the restore worker, or a child. Returns 0 when -// the release completed. -static int grace_period_release(void) -{ - int64_t normal = 0; - bool saved = read_int64(RCU_NORMAL, &normal) == 0 && - write_int64(RCU_NORMAL, 1) == 0; - int status = 1; - - if (saved && unshare(CLONE_NEWNS) == 0 && - mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) == 0) { - (void)mkdir(RCU_SYNC_PATH, 0700); - if (mount("nvx-rcu-sync", RCU_SYNC_PATH, "tmpfs", - MS_NOSUID | MS_NODEV | MS_NOEXEC, "size=4k") == 0 && - umount2(RCU_SYNC_PATH, 0) == 0) - status = 0; - (void)rmdir(RCU_SYNC_PATH); - } - if (saved && write_int64(RCU_NORMAL, normal) != 0) - status = 1; - return status; -} - -// Runs the grace-period release in a child and waits at most TIMEOUT_S -// seconds for it, for callers without the daemon's event loop. A release -// still pending at the deadline counts as done: the grace period is stuck, -// and the kernel then reports the real stall. -static bool release_with_deadline(int64_t timeout_s) -{ - int64_t deadline = clock_ns(CLOCK_MONOTONIC) + timeout_s * NSEC_PER_SEC; - sigset_t mask; - sigset_t previous; - bool released = false; - pid_t pid; - - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - sigprocmask(SIG_BLOCK, &mask, &previous); - pid = fork(); - if (pid == 0) { - sigprocmask(SIG_SETMASK, &previous, NULL); - _exit(grace_period_release()); - } - while (pid > 0) { - int status; - pid_t done = waitpid(pid, &status, WNOHANG); - int64_t remaining = deadline - clock_ns(CLOCK_MONOTONIC); - - if (done == pid) { - released = WIFEXITED(status) && WEXITSTATUS(status) == 0; - break; - } - if (done < 0 && errno != EINTR) - break; - if (remaining <= 0) { - released = true; - break; - } - (void)sigtimedwait(&mask, NULL, - &(struct timespec){remaining / NSEC_PER_SEC, - remaining % NSEC_PER_SEC}); - } - sigprocmask(SIG_SETMASK, &previous, NULL); - return released; -} - -static void run_restore_checks(struct checks *checks, const bool *new_cpus, - int new_count); - -static int parse_new_cpus(const char *text, bool *cpus) -{ - if (strcmp(text, "") == 0 || strcmp(text, "none") == 0) { - memset(cpus, 0, sizeof(bool) * MAX_CPUS); - return 0; - } - return parse_cpu_list(text, cpus); -} - -// Parses the restore record that restore-finish writes for the daemon: the -// generation it finished and the CPUs that activation brought online. -// Returns the number of those CPUs, or -1 when the record is malformed. -static int parse_restore_record(const char *text, unsigned long long *generation, - bool *new_cpus) -{ - char cpus[128] = "none"; - - if (sscanf(text, "generation=%llu new_cpus=%127s", generation, cpus) < 1) - return -1; - return parse_new_cpus(cpus, new_cpus); -} - -// The times that restore_work records. The wall time is READINESS_US, the -// readiness path's, plus the checks' since START_NS, when they were due. The -// CPU time is repair step 7's alone: this process's since CPU_BASE_NS, plus -// the daemon's preparation, PRIOR_CPU_NS up to the fork and the fork's own -// share, which the daemon writes to FORK_FD (-1 when there is none) once the -// fork returns. -struct restore_timing { - int64_t start_ns; - int64_t readiness_us; - int64_t prior_cpu_ns; - int64_t cpu_base_ns; - int fork_fd; -}; - -// Repair step 7's CPU time so far, in a process that TIMING describes. The -// read waits for the daemon's fork share if the daemon has not yet written it. -static int64_t deferred_restore_cpu_ns(const struct restore_timing *timing) -{ - int64_t cpu = timing->prior_cpu_ns + cpu_time_ns() - timing->cpu_base_ns; - int64_t fork_share = 0; - - if (timing->fork_fd >= 0 && - read(timing->fork_fd, &fork_share, sizeof(fork_share)) == - sizeof(fork_share) && - fork_share > 0) - cpu += fork_share; - return cpu; -} - -// Forks the repair step 7 worker at SCHED_IDLE for a daemon whose preparation -// started at CPU_START_NS, its CPU clock, and completes TIMING's CPU fields -// for the child. The parent returns to normal priority as soon as the fork -// does. Its fork costs CPU time after the child's copy of TIMING was taken, so -// it writes that share to a pipe that the child reads. Returns fork()'s -// result. -static pid_t fork_deferred_restore(int64_t cpu_start_ns, - struct restore_timing *timing) -{ - int fds[2] = {-1, -1}; - int64_t fork_share; - pid_t pid; - - if (pipe2(fds, O_CLOEXEC) != 0) - fds[0] = fds[1] = -1; - timing->fork_fd = fds[0]; - timing->cpu_base_ns = 0; - set_idle_priority(0, true); - timing->prior_cpu_ns = cpu_time_ns() - cpu_start_ns; - pid = fork(); - if (pid == 0) { - if (fds[1] >= 0) - close(fds[1]); - return 0; - } - set_idle_priority(0, false); - if (fds[0] >= 0) - close(fds[0]); - timing->fork_fd = -1; - if (fds[1] >= 0) { - fork_share = cpu_time_ns() - cpu_start_ns - timing->prior_cpu_ns; - if (pid > 0) - (void)write_all(fds[1], &fork_share, sizeof(fork_share)); - close(fds[1]); - } - return pid; -} - -// Repair step 7, fail-fast: the restore checks, the grace-period release, the -// saved watchdog settings, the deferred C7, and the restore check's record. A -// child of the daemon runs it at SCHED_IDLE from DEFERRED_START_NS after the -// acknowledgement; the daemon raises it to normal priority IDLE_BOUND_NS later -// and releases the suppression itself at the stall timeout. restore-finish -// runs it inline, with its own deadline (WITH_DEADLINE), in report-only mode -// when no daemon runs. Neither recorded time counts the grace period wait or -// C7. Returns 0, or 1 when a report-only release failed. -static int restore_work(const bool *new_cpus, int new_count, bool with_deadline, - const struct restore_timing *timing) -{ - int64_t timeout_s = DEFAULT_STALL_TIMEOUT_S; - char detail[DETAIL_MAX]; - struct checks checks; - int64_t elapsed; - int64_t cpu; - bool released; - - if (checks_init(&checks, PHASE_RESTORE) != 0) - check_failed(&checks, "C6", "cannot read the CPU sets"); - else - run_restore_checks(&checks, new_cpus, new_count); - elapsed = clock_ns(CLOCK_MONOTONIC) - timing->start_ns; - cpu = deferred_restore_cpu_ns(timing); - if (with_deadline) { - (void)read_int64(RCU_STALL_TIMEOUT, &timeout_s); - released = release_with_deadline(timeout_s); - } else { - released = grace_period_release() == 0; - } - if (!released) - fail_fatal(STATUS_REPAIR, "G_REPAIR_SUPPRESSION", "repair", - PHASE_RESTORE, "the RCU grace-period release failed"); - if (suppression_restore(detail, sizeof(detail), true) != 0) - fail_fatal(STATUS_REPAIR, "G_REPAIR_SUPPRESSION", "repair", - PHASE_RESTORE, "%s", detail); - check_timer_list(&checks, TIMER_LIST_WAIT_NS); - if (record_check(PHASE_RESTORE, "ok", checks.online_count, - timing->readiness_us + elapsed / NSEC_PER_USEC, - cpu / NSEC_PER_USEC, 0, 0, checks.failures) != 0) - check_failed(&checks, "C10", "cannot record the restore check: %s", - strerror(errno)); - return released ? 0 : 1; -} - -// Starts repair step 7 when it is due, DEFERRED_START_NS after the -// acknowledgement, in a child at SCHED_IDLE, so the watcher and the -// discipline keep normal priority; the fork itself also runs at SCHED_IDLE. -// The readiness path's wall time came with the signal. -static void start_restore(struct daemon *daemon) -{ - int64_t cpu_start = cpu_time_ns(); - bool new_cpus[MAX_CPUS] = {false}; - struct restore_timing timing; - struct time_state state; - unsigned long long generation; - int64_t timeout_s = DEFAULT_STALL_TIMEOUT_S; - char text[256]; - int new_count = 0; - pid_t pid; - - if (daemon->worker_pid > 0) - return; - if (state_load(&state) != 0) { - fail_fatal(STATUS_REPAIR, "G_REPAIR_SUPPRESSION", "repair", - PHASE_RESTORE, "the time state is unreadable"); - return; - } - g_generation = (uint32_t)state.generation; - if (read_text(RESTORE_PATH, text, sizeof(text)) >= 0) { - new_count = parse_restore_record(text, &generation, new_cpus); - if (new_count < 0) { - fail_fatal(STATUS_REPAIR, "G_REPAIR_SUPPRESSION", "repair", - PHASE_RESTORE, "the restore record is malformed"); - return; - } - if (generation != state.generation) { - memset(new_cpus, 0, sizeof(new_cpus)); - new_count = 0; - } - } - (void)read_int64(RCU_STALL_TIMEOUT, &timeout_s); - // The worker's wall time runs from when step 7 was due. Its CPU time is - // step 7's: this preparation, the fork, and the worker's own. - timing.start_ns = daemon->restore_due_ns; - timing.readiness_us = daemon->readiness_us; - pid = fork_deferred_restore(cpu_start, &timing); - if (pid == 0) - _exit(restore_work(new_cpus, new_count, false, &timing)); - if (pid < 0) { - fail_fatal(STATUS_REPAIR, "G_REPAIR_SUPPRESSION", "repair", - PHASE_RESTORE, "cannot start the restore work: %s", - strerror(errno)); - return; - } - daemon->worker_pid = pid; - daemon->worker_promote_ns = daemon->restore_due_ns + IDLE_BOUND_NS; - daemon->worker_deadline_ns = - clock_ns(CLOCK_MONOTONIC) + timeout_s * NSEC_PER_SEC; - daemon->suppression_released = false; -} - -// Releases the stall suppression for a worker whose grace period is still -// pending at the stall timeout: the grace period is stuck, and the kernel -// then reports the real stall. -static void release_suppression(struct daemon *daemon) -{ - char detail[DETAIL_MAX]; - - daemon->suppression_released = true; - if (suppression_restore(detail, sizeof(detail), true) != 0) - fail_fatal(STATUS_REPAIR, "G_REPAIR_SUPPRESSION", "repair", - PHASE_RESTORE, "%s", detail); -} - -static void reap_children(struct daemon *daemon) -{ - int status; - pid_t pid; - - while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { - if (pid != daemon->worker_pid) - continue; - daemon->worker_pid = 0; - daemon->worker_promote_ns = 0; - // A failing worker powered the guest off itself, or reported in - // report-only mode; a crash leaves the suppression to the daemon. - if (!WIFEXITED(status)) { - release_suppression(daemon); - fail_fatal(STATUS_REPAIR, "G_REPAIR_SUPPRESSION", "repair", - PHASE_RESTORE, "the restore work ended with signal %d", - WTERMSIG(status)); - } - // Repair step 7 ends by restarting the discipline at the fast cadence. - daemon->polls = 0; - (void)arm_timer(daemon); - } -} - -static void handle_signals(struct daemon *daemon) -{ - struct signalfd_siginfo info; - - while (read(daemon->signals, &info, sizeof(info)) == sizeof(info)) { - if (info.ssi_signo == SIGTERM) - _exit(0); - // sigqueue() carries the readiness path's wall time. Step 7 is due - // DEFERRED_START_NS later; the repair paired the clock with the - // packet's UTC now. - if (info.ssi_signo == SIGUSR1) { - daemon->restore_pending = true; - daemon->last_accepted_ns = clock_ns(CLOCK_MONOTONIC); - daemon->restore_due_ns = - daemon->last_accepted_ns + DEFERRED_START_NS; - daemon->readiness_us = info.ssi_code == SI_QUEUE - ? (int64_t)(info.ssi_ptr & UINT32_MAX) - : 0; - } - } - // A worker that ended, if any: the daemon loop starts a due restore only - // once the previous worker is reaped. - reap_children(daemon); -} - -// Sets up the daemon's signal and timer descriptors and its OOM exemption. -// Returns 0 or an errno value. -static int daemon_setup(struct daemon *daemon) -{ - sigset_t mask; - - sigemptyset(&mask); - sigaddset(&mask, SIGUSR1); - sigaddset(&mask, SIGTERM); - sigaddset(&mask, SIGCHLD); - if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0) - return errno; - signal(SIGHUP, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - daemon->signals = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); - if (daemon->signals < 0) - return errno; - daemon->timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); - if (daemon->timer < 0) - return errno; - if (write_text("/proc/self/oom_score_adj", "-1000") != 0 || - arm_timer(daemon) != 0) - return errno; - return 0; -} - -// Returns the poll timeout in milliseconds until DEADLINE_NS, or -1 when -// DEADLINE_NS is 0, for none. -static int poll_timeout_until(int64_t deadline_ns) -{ - int64_t remaining; - - if (deadline_ns == 0) - return -1; - remaining = deadline_ns - clock_ns(CLOCK_MONOTONIC); - return remaining <= 0 - ? 0 - : (int)((remaining + NSEC_PER_MSEC - 1) / NSEC_PER_MSEC); -} - -// Returns the earlier of two poll timeouts, where -1 is none. -static int earlier_timeout(int first, int second) -{ - if (first < 0) - return second; - if (second < 0) - return first; - return first < second ? first : second; -} - -static void daemon_loop(struct daemon *daemon) -{ - for (;;) { - struct pollfd fds[3] = {{daemon->kmsg, POLLIN, 0}, - {daemon->signals, POLLIN, 0}, - {daemon->timer, POLLIN, 0}}; - bool waiting = daemon->worker_pid > 0 && !daemon->suppression_released; - bool idle = daemon->worker_pid > 0 && daemon->worker_promote_ns != 0; - bool due = daemon->restore_pending && daemon->worker_pid == 0; - int timeout = earlier_timeout( - earlier_timeout( - poll_timeout_until(idle ? daemon->worker_promote_ns : 0), - poll_timeout_until(waiting ? daemon->worker_deadline_ns : 0)), - poll_timeout_until(due ? daemon->restore_due_ns : 0)); - int64_t now; - - if (poll(fds, ARRAY_SIZE(fds), timeout) < 0 && errno != EINTR) { - sleep(1); - continue; - } - if ((fds[0].revents & (POLLIN | POLLERR)) != 0) - watch_kernel_log(daemon); - if ((fds[1].revents & POLLIN) != 0) - handle_signals(daemon); - if ((fds[2].revents & POLLIN) != 0) { - uint64_t expirations; - ssize_t count = read(daemon->timer, &expirations, - sizeof(expirations)); - - (void)count; - discipline_poll(daemon); - arm_timer(daemon); - } - now = clock_ns(CLOCK_MONOTONIC); - // Step 7 starts DEFERRED_START_NS after the ack, and continues at - // normal priority IDLE_BOUND_NS after it starts. - if (daemon->restore_pending && daemon->worker_pid == 0 && - now >= daemon->restore_due_ns) { - daemon->restore_pending = false; - start_restore(daemon); - now = clock_ns(CLOCK_MONOTONIC); - } - if (daemon->worker_pid > 0 && daemon->worker_promote_ns != 0 && - now >= daemon->worker_promote_ns) { - daemon->worker_promote_ns = 0; - set_idle_priority(daemon->worker_pid, false); - } - if (daemon->worker_pid > 0 && !daemon->suppression_released && - now >= daemon->worker_deadline_ns) - release_suppression(daemon); - } -} - -// Turns the boot checker into the time daemon, at normal priority. It keeps -// reading the kernel log from where the boot check positioned KMSG and -// publishes its PID once its descriptors are ready. Returns 0 or -1. -static int become_daemon(struct daemon *daemon, int kmsg, - int64_t last_accepted_ns) -{ - char text[32]; - int error; - - memset(daemon, 0, sizeof(*daemon)); - daemon->kmsg = kmsg; - daemon->last_accepted_ns = last_accepted_ns; - set_idle_priority(0, false); - error = daemon_setup(daemon); - if (error != 0) { - errno = error; - return -1; - } - snprintf(text, sizeof(text), "%d\n", (int)getpid()); - return replace_file(DAEMON_PID_PATH, DAEMON_PID_PATH ".tmp", text); -} - -// --------------------------------------------------------------------------- -// Commands -// --------------------------------------------------------------------------- - -static void load_generation(void) -{ - struct time_state state; - - if (state_load(&state) == 0) - g_generation = (uint32_t)state.generation; -} - -struct boot_log_job { - struct boot_log *log; - char *buffer; - int size; - bool read_log; -}; - -// C4 and K1 over the kernel log, beside the per-CPU checks. The caller -// allocates the buffer before the threads start: freeing a large allocation -// unmaps it, and an unmap in a process with running threads waits for every -// CPU running one of them. -static void boot_kernel_log(struct checks *checks, void *context) -{ - struct boot_log_job *job = context; - char detail[DETAIL_MAX]; - - if (!job->read_log) - return; - if (job->buffer == NULL) { - check_failed(checks, "C4", "no buffer for the %d-byte kernel log", - job->size); - return; - } - if (read_boot_log_into(job->log, job->buffer, job->size) != 0) { - check_failed(checks, "C4", "read the kernel log: %s", strerror(errno)); - return; - } - if (!boot_log_verdict(job->log, detail, sizeof(detail))) - check_failed(checks, "C4", "%s", detail); - // K1 is kernel hardening, not time: a writable and executable kernel - // mapping, such as unprotected ITS thunk pages. - if (job->log->wx[0] != '\0') - check_failed_code(checks, "G_KERNEL_WX", "%s", job->log->wx); -} - -// C7 beside the per-CPU checks. -static void boot_timer_list(struct checks *checks, void *context) -{ - (void)context; - check_timer_list(checks, TIMER_LIST_WAIT_NS); -} - -// C5 beside the per-CPU checks. -static void boot_cpuinfo(struct checks *checks, void *context) -{ - (void)context; - check_cpuinfo(checks, checks->online, true); -} - -struct boot_sample { - int64_t theta; - int64_t epsilon; - int64_t accepted_ns; -}; - -// The boot checks that need no CPU of their own run in the caller while the -// parallel jobs run. -static void boot_foreground(struct checks *checks, void *context) -{ - (void)context; - check_cmdline(checks); - check_platform(checks); - check_clocksource(checks); - check_debug_watchdogs(checks); -} - -// Sleeps until DEADLINE_NS on CLOCK_MONOTONIC. -static void sleep_until(int64_t deadline_ns) -{ - struct timespec when = {(time_t)(deadline_ns / NSEC_PER_SEC), - (long)(deadline_ns % NSEC_PER_SEC)}; - - while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &when, NULL) == - EINTR) - ; -} - -// The asynchronous boot checks: every boot check but C12, fail-fast, from -// DEFERRED_START_NS after the boot step at SCHED_IDLE, and at normal priority -// from IDLE_BOUND_NS after they start. When they pass, the checker becomes the -// daemon and records the boot check, whose wall and CPU times add C12's -// (C12_NS and C12_CPU_NS) and the daemon's start, but not the delay. It never -// returns. -static void boot_checker(const struct boot_sample *sample, int64_t c12_ns, - int64_t c12_cpu_ns, int c12_failures) -{ - // The delay counts from this fork in the time ABI boot step, the one - // point that every guest mode shares: init runs it before the rest of its - // setup, so the checks start somewhat less than DEFERRED_START_NS after - // shell-ready. - int64_t start = clock_ns(CLOCK_MONOTONIC) + DEFERRED_START_NS; - int64_t cpu_start = cpu_time_ns(); - int null = open("/dev/null", O_RDWR | O_CLOEXEC); - struct boot_log_job log_job; - struct promoter promoter; - struct side_job sides[3]; - struct daemon daemon; - struct checks checks; - struct boot_log log; - bool started = false; - bool promoting; - int kmsg; - - g_async_output = true; - (void)setsid(); - if (null >= 0) { - (void)dup2(null, STDIN_FILENO); - (void)dup2(null, STDOUT_FILENO); - (void)dup2(null, STDERR_FILENO); - close(null); - } - if (chdir("/") != 0) - _exit(1); - promoting = promoter_start(&promoter, start); - set_idle_priority(0, true); - sleep_until(start); - if (checks_init(&checks, PHASE_BOOT) != 0) { - check_failed(&checks, "C1", "cannot read the CPU sets"); - _exit(1); - } - checks.failures = c12_failures; - memset(&log, 0, sizeof(log)); - log_job.log = &log; - log_job.read_log = false; - log_job.size = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0); - log_job.buffer = log_job.size > 0 ? malloc((size_t)log_job.size + 1) : NULL; - kmsg = open("/dev/kmsg", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (kmsg < 0 || lseek(kmsg, 0, SEEK_END) < 0) { - check_failed(&checks, "C4", "open /dev/kmsg: %s", strerror(errno)); - if (kmsg >= 0) - close(kmsg); - kmsg = -1; - } else { - log_job.read_log = true; - } - // The kernel log, the tick mode, and cpuinfo run beside the per-CPU - // checks, longest first; the other checks run in the caller meanwhile. - sides[0] = (struct side_job){.run = boot_kernel_log, .context = &log_job}; - sides[1] = (struct side_job){.run = boot_timer_list}; - sides[2] = (struct side_job){.run = boot_cpuinfo}; - check_cpus_parallel(&checks, checks.online, true, true, sides, - ARRAY_SIZE(sides), boot_foreground, NULL); - free(log_job.buffer); - memcpy(checks.cpu_khz, sides[2].checks.cpu_khz, sizeof(checks.cpu_khz)); - memcpy(checks.cpu_khz_known, sides[2].checks.cpu_khz_known, - sizeof(checks.cpu_khz_known)); - // C3 compares CPU 0's declared rate with cpuinfo once both have run. - if (checks.tsc_hz != 0 && checks.cpu_khz_known[0] && - checks.tsc_hz / 1000 != checks.cpu_khz[0]) - check_failed(&checks, "C3", - "cpu 0: floor(F / 1000) is %" PRIu64 - " kHz but cpu MHz is %" PRIu64 " kHz", - checks.tsc_hz / 1000, checks.cpu_khz[0]); - // The daemon blocks its signals in its only thread. - if (promoting) - promoter_stop(&promoter); - if (kmsg >= 0) { - started = become_daemon(&daemon, kmsg, sample->accepted_ns) == 0; - if (!started) - check_failed(&checks, "C10", "cannot start the time daemon: %s", - strerror(errno)); - } - if (record_check(PHASE_BOOT, "ok", checks.online_count, - (c12_ns + clock_ns(CLOCK_MONOTONIC) - start) / - NSEC_PER_USEC, - (c12_cpu_ns + cpu_time_ns() - cpu_start) / NSEC_PER_USEC, - checks.tsc_hz, checks.lapic_hz, checks.failures) != 0) - check_failed(&checks, "C10", "cannot publish the time state: %s", - strerror(errno)); - if (!started) - _exit(1); - daemon_loop(&daemon); - _exit(0); -} - -// Before shell-ready, only C12: the initial time sample and clock step, then -// the published state. Every other boot check runs asynchronously in a child, -// which becomes the daemon. -static int cmd_boot(void) -{ - int64_t start = clock_ns(CLOCK_MONOTONIC); - int64_t cpu_start = cpu_time_ns(); - struct boot_sample sample = {0, 0, 0}; - struct time_state state; - struct checks checks; - int64_t c12_cpu; - pid_t pid; - - g_generation = 0; - make_runtime_directories(); - memset(&checks, 0, sizeof(checks)); - checks.phase = PHASE_BOOT; - if (check_initial_sample(&checks, &sample.theta, &sample.epsilon)) - sample.accepted_ns = clock_ns(CLOCK_MONOTONIC); - state_init(&state); - state.synchronized = sample.accepted_ns != 0; - state.samples = sample.accepted_ns != 0; - state.offset_ns = sample.accepted_ns != 0 ? sample.theta : 0; - state.uncertainty_ns = sample.accepted_ns != 0 ? sample.epsilon : 0; - state.frequency_ppb = frequency_to_ppb(current_frequency()); - if (state_store(&state) != 0) { - check_failed(&checks, "C10", "cannot publish the time state: %s", - strerror(errno)); - return 1; - } - fflush(NULL); - // The child cannot read this process's CPU clock. - c12_cpu = cpu_time_ns() - cpu_start; - pid = fork(); - if (pid == 0) - boot_checker(&sample, clock_ns(CLOCK_MONOTONIC) - start, c12_cpu, - checks.failures); - if (pid < 0) { - check_failed(&checks, "C10", "cannot start the boot checks: %s", - strerror(errno)); - return 1; - } - return 0; -} - -// Capture checks (C6, C7, and C10); they never wait. -static void run_capture_checks(struct checks *checks) -{ - (void)rates_load(&checks->tsc_hz, &checks->lapic_hz); - check_clocksource(checks); - check_timer_list(checks, 0); - check_daemon(checks); -} - -// Restore checks: C1, C2, and C5 on newly onlined CPUs, C3 for CPU 0, C6, -// and C10. They never wait. -static void run_restore_checks(struct checks *checks, const bool *new_cpus, - int new_count) -{ - uint64_t tsc_hz = 0; - uint64_t lapic_hz = 0; - - if (rates_load(&tsc_hz, &lapic_hz) != 0) { - check_failed(checks, "C3", "the boot rates are unavailable"); - } else { - checks->tsc_hz = tsc_hz; - checks->lapic_hz = lapic_hz; - } - if (new_count > 0) { - check_cpus(checks, new_cpus, true, false); - check_cpuinfo(checks, new_cpus, true); - } - check_msrs(checks, 0, false); - check_clocksource(checks); - check_daemon(checks); -} - -static int cmd_check(int argc, char **argv) -{ - int64_t start = clock_ns(CLOCK_MONOTONIC); - int64_t cpu_start = cpu_time_ns(); - bool new_cpus[MAX_CPUS] = {false}; - int new_count = 0; - enum phase phase = PHASE_RUNTIME; - struct checks checks; - - for (int index = 0; index < argc; index++) { - if (strcmp(argv[index], "--phase") == 0 && index + 1 < argc) { - const char *name = argv[++index]; - - phase = strcmp(name, "capture") == 0 ? PHASE_CAPTURE - : strcmp(name, "restore") == 0 ? PHASE_RESTORE - : PHASE_RUNTIME; - } else if (strcmp(argv[index], "--new-cpus") == 0 && index + 1 < argc) { - new_count = parse_new_cpus(argv[++index], new_cpus); - } else { - new_count = -1; - } - } - if (phase == PHASE_RUNTIME || new_count < 0) { - fprintf(stderr, "usage: nvx-time check --phase capture|restore " - "[--new-cpus LIST]\n"); - return 2; - } - load_generation(); - if (checks_init(&checks, phase) != 0) { - check_failed(&checks, "C6", "cannot read the CPU sets"); - return 1; - } - if (phase == PHASE_CAPTURE) - run_capture_checks(&checks); - else - run_restore_checks(&checks, new_cpus, new_count); - (void)record_check(phase, "ok", checks.online_count, - (clock_ns(CLOCK_MONOTONIC) - start) / NSEC_PER_USEC, - (cpu_time_ns() - cpu_start) / NSEC_PER_USEC, 0, 0, - checks.failures); - return checks.failures == 0 ? 0 : 1; -} - -// Whether a recorded check of STATE is still pending: the boot check only -// with BOOT_ONLY, or any. A state without a boot record parses as a pending -// boot check. -static bool checks_pending(const struct time_state *state, bool boot_only) -{ - for (int phase = 0; phase < (boot_only ? 1 : CHECK_PHASES); phase++) { - if (strcmp(state->checks[phase].status, "pending") == 0) - return true; - } - return false; -} - -// Waits until no recorded check (with BOOT_ONLY, no boot check) is pending, -// for at most STATUS_WAIT_NS, and loads the state into STATE; a missing -// state file counts as a pending boot check. Returns 0, or 1 when a check is -// still pending. -static int wait_for_checks(struct time_state *state, bool boot_only) -{ - int64_t deadline = clock_ns(CLOCK_MONOTONIC) + STATUS_WAIT_NS; - - for (;;) { - if (state_load(state) != 0) - state_init(state); - else if (!checks_pending(state, boot_only)) - return 0; - if (clock_ns(CLOCK_MONOTONIC) >= deadline) - return 1; - (void)nanosleep(&(struct timespec){0, STATUS_POLL_NS}, NULL); - } -} - -static int cmd_pre_capture(void) -{ - struct time_state state; - struct checks checks; - int64_t cpu_start; - int64_t elapsed; - int64_t start; - int64_t cpu; - - // Preparation step 1: the asynchronous boot checks start the daemon that - // C10 requires. - if (wait_for_checks(&state, true) != 0) { - memset(&checks, 0, sizeof(checks)); - checks.phase = PHASE_CAPTURE; - check_failed(&checks, "C10", "the boot checks are still pending"); - return 1; - } - start = clock_ns(CLOCK_MONOTONIC); - cpu_start = cpu_time_ns(); - g_generation = (uint32_t)state.generation; - if (checks_init(&checks, PHASE_CAPTURE) != 0) { - check_failed(&checks, "C6", "cannot read the CPU sets"); - return 1; - } - run_capture_checks(&checks); - elapsed = clock_ns(CLOCK_MONOTONIC) - start; - cpu = cpu_time_ns() - cpu_start; - if (suppression_save() != 0) { - char detail[DETAIL_MAX]; - int error = errno; - - if (error != EEXIST) - (void)suppression_restore(detail, sizeof(detail), false); - fprintf(stderr, "nvx-time: cannot suppress the stall detectors: %s\n", - error == EEXIST ? "a capture is already pending" - : strerror(error)); - return 1; - } - // Step 1's record travels in the snapshot. - if (record_check(PHASE_CAPTURE, "ok", checks.online_count, - elapsed / NSEC_PER_USEC, cpu / NSEC_PER_USEC, 0, 0, - checks.failures) != 0) - check_failed(&checks, "C10", "cannot record the capture check: %s", - strerror(errno)); - return 0; -} - -static int cmd_cancel_capture(void) -{ - char detail[DETAIL_MAX]; - - if (suppression_restore(detail, sizeof(detail), false) != 0) { - fprintf(stderr, "nvx-time: %s\n", detail); - return 1; - } - return 0; -} - -struct repair_record { - const struct restore_packet *packet; - int64_t theta; - int64_t epsilon; - int64_t step_realtime_ns; - int64_t frequency; - int64_t elapsed_us; -}; - -static void apply_repair(struct time_state *state, const void *context) -{ - const struct repair_record *repair = context; - - state->generation = repair->packet->generation; - state->discontinuities++; - snprintf(state->last_discontinuity, sizeof(state->last_discontinuity), - "restore"); - state->last_step_ns = repair->theta; - state->last_step_realtime_ns = repair->step_realtime_ns; - state->last_downtime_ns = repair->packet->downtime_ns; - snprintf(state->last_downtime_source, sizeof(state->last_downtime_source), - "%s", - (repair->packet->flags & PACKET_DOWNTIME_UTC) != 0 ? "utc" - : "monotonic"); - state->synchronized = 1; - state->offset_ns = repair->theta; - state->uncertainty_ns = repair->epsilon; - state->frequency_ppb = frequency_to_ppb(repair->frequency); - state->samples++; - snprintf(state->last_sample_error, sizeof(state->last_sample_error), - "none"); - // Repair step 3 marks this restore's check pending, so nvx-time status - // waits for the daemon's step 7. Until then, restore_elapsed_us is the - // readiness path up to the clock step, at last_step_realtime_ns; - // restore_cpu_us counts only step 7, which records it. - snprintf(state->checks[PHASE_RESTORE].status, - sizeof(state->checks[PHASE_RESTORE].status), "pending"); - state->checks[PHASE_RESTORE].cpus = 0; - state->checks[PHASE_RESTORE].elapsed_us = repair->elapsed_us; - state->checks[PHASE_RESTORE].cpu_us = 0; - state->checks[PHASE_RESTORE].generation = state->generation; - state->checks[PHASE_RESTORE].failures = -1; -} - -static int repair_failed(const char *code, const char *detail) -{ - fail_fatal(STATUS_REPAIR, code, "repair", PHASE_RESTORE, "%s", detail); - return 1; -} - -// Repair steps 2 and 3: read the packet with four-byte reads, validate it, -// and set the wall clock from its bracketed UTC, then write the entropy to -// ENTROPY_FD, the caller's file at ENTROPY_PATH. START_NS is when the capture -// request returned. The caller holds the portb lock. With FINISH (untiered) -// and a DAEMON, a restore with no processors or memory to activate needs no -// shell work before the acknowledgement, so steps 6 and 7 start here too -// and no second helper process starts; the metadata then begins with 2 -// instead of 1. -static int repair_restore(const uint8_t *previous_id, const char *entropy_path, - int entropy_fd, int64_t start_ns, bool finish, - pid_t daemon) -{ - static uint8_t body[PACKET_MAX_SIZE]; - static struct restore_packet packet; - uint8_t header[PACKET_HEADER_SIZE]; - struct repair_record repair; - struct time_sample sample; - char detail[DETAIL_MAX]; - char id[GENERATION_ID_SIZE * 2 + 1]; - int64_t t0; - int64_t t1; - int64_t sample_theta; - int64_t sample_epsilon; - bool finishing; - int finished = -1; - - t0 = clock_ns(CLOCK_REALTIME); - outb(SELECT_PACKET, PORTB_STATUS); - t1 = clock_ns(CLOCK_REALTIME); - portb_read(PORTB_DATA, header, sizeof(header)); - if (parse_packet_header(header, &packet, detail, sizeof(detail)) != 0) - return repair_failed("G_REPAIR_PACKET", detail); - if (packet.generation == g_generation) { - close(entropy_fd); - unlink(entropy_path); - puts("0"); - return 0; - } - if ((uint64_t)packet.generation != (uint64_t)g_generation + 1) { - snprintf(detail, sizeof(detail), - "restore packet generation %" PRIu32 - " does not follow recorded generation %" PRIu32, - packet.generation, g_generation); - return repair_failed("G_REPAIR_GENERATION", detail); - } - portb_read(PORTB_DATA, body, packet_body_size(&packet)); - if (parse_packet_body(body, &packet, detail, sizeof(detail)) != 0) - return repair_failed("G_REPAIR_PACKET", detail); - if (memcmp(packet.entropy, previous_id, GENERATION_ID_SIZE) == 0) - return repair_failed("G_REPAIR_GENERATION", - "the VM generation ID did not change"); - g_generation = packet.generation; - pair_clocks(t0, t1, packet.utc_ns, &repair.theta, &repair.epsilon); - if (repair.epsilon > REPAIR_BOUND_NS) { - int result = best_sample(SAMPLE_ATTEMPTS - 1, REPAIR_BOUND_NS, - packet.generation, &sample, &sample_theta, - &sample_epsilon, detail, sizeof(detail)); - - if (result == -2) - return repair_failed("G_REPAIR_GENERATION", detail); - if (result >= 0 && sample_epsilon < repair.epsilon) { - repair.theta = sample_theta; - repair.epsilon = sample_epsilon; - } - if (repair.epsilon > REPAIR_BOUND_NS) { - snprintf(detail, sizeof(detail), - "UTC pairing uncertainty %" PRId64 " ns exceeds 1 ms", - repair.epsilon); - return repair_failed("G_REPAIR_SAMPLE", detail); - } - } - repair.frequency = restore_frequency(packet.rate_deviation); - if (step_clock(repair.theta) != 0 || set_frequency(repair.frequency) != 0) { - snprintf(detail, sizeof(detail), "adjtimex: %s", strerror(errno)); - return repair_failed("G_REPAIR_CLOCK", detail); - } - repair.step_realtime_ns = clock_ns(CLOCK_REALTIME); - repair.elapsed_us = (clock_ns(CLOCK_MONOTONIC) - start_ns) / NSEC_PER_USEC; - repair.packet = &packet; - // A plain untiered restore needs no shell work: this helper also - // acknowledges and hands step 7 to the daemon. - finishing = finish && daemon > 0 && packet.online_vp_count == 0 && - packet.range_count == 0 && - (packet.flags & PACKET_MEMORY_TARGET) == 0; - // The entropy goes to the caller in every case, an untiered restore's - // too; the file already exists, empty, so only its bytes are written. - if (pwrite(entropy_fd, packet.entropy, PACKET_ENTROPY_SIZE, 0) != - PACKET_ENTROPY_SIZE || - close(entropy_fd) != 0) { - snprintf(detail, sizeof(detail), "write %.200s: %s", entropy_path, - strerror(errno)); - return repair_failed("G_REPAIR_PACKET", detail); - } - if (state_apply(apply_repair, &repair) != 0) { - snprintf(detail, sizeof(detail), "record the restore: %s", - strerror(errno)); - return repair_failed("G_REPAIR_CLOCK", detail); - } - format_hex(packet.entropy, GENERATION_ID_SIZE, id); - if (finishing) { - finished = signal_restore(daemon, - (packet.flags & PACKET_ACK_REQUIRED) != 0, - start_ns); - if (finished > 0) - fail_fatal(STATUS_CONFORMANCE, "G_CONFORMANCE_C10", "conformance", - PHASE_RESTORE, "the time daemon is not running"); - } - printf("%d %u %u %u %s", finished >= 0 ? 2 : 1, packet.flags, - packet.online_vp_count, packet.range_count, id); - for (unsigned index = 0; index < packet.range_count; index++) - printf(" %" PRIu64 " %" PRIu64, packet.ranges[index][0], - packet.ranges[index][1]); - putchar('\n'); - return 0; -} - -// Writes the capture request to 0x605. In a restored process the write -// returns with a restore packet available, and repair steps 1 to 3 run here -// before any other guest work. Prints 0 when no restore happened. The portb -// lock is held from the request to the restored clock, so the snapshot never -// contains a discipline poll that would apply a pre-capture sample after the -// restore. -static int cmd_capture(int argc, char **argv) -{ - uint8_t previous_id[GENERATION_ID_SIZE]; - char detail[DETAIL_MAX]; - char *end; - long request; - int64_t start; - unsigned status; - pid_t daemon = -1; - int entropy_fd; - int result; - int lock; - - if ((argc != 3 && (argc != 4 || strcmp(argv[3], "--finish") != 0)) || - (request = strtol(argv[0], &end, 10), *end != '\0') || - (request != 0 && request != 1) || - parse_hex(argv[1], previous_id, sizeof(previous_id)) != 0) { - fprintf(stderr, "usage: nvx-time capture 0|1 GENERATION_ID " - "ENTROPY_FILE [--finish]\n"); - return 2; - } - if (state_load(&(struct time_state){0}) != 0) { - fprintf(stderr, "nvx-time: the time state is unavailable\n"); - return 1; - } - load_generation(); - if (enable_ports() != 0) { - fprintf(stderr, "nvx-time: ioperm: %s\n", strerror(errno)); - return 1; - } - // The daemon keeps its PID in the restored guest; looking it up now keeps - // the file reads off the restore path. - if (argc == 4) - daemon = daemon_pid(); - // The entropy file is created before the capture, empty, so that a - // restored helper only writes its bytes: creating a file right after a - // restore faults in the filesystem's metadata, 4 to 9 ms on WHP. - entropy_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | - O_CLOEXEC, - 0600); - if (entropy_fd < 0) { - fprintf(stderr, "nvx-time: open %s: %s\n", argv[2], strerror(errno)); - return 1; - } - lock = lock_portb(detail, sizeof(detail)); - if (lock < 0) { - fprintf(stderr, "nvx-time: %s\n", detail); - close(entropy_fd); - unlink(argv[2]); - return 1; - } - outb((unsigned char)request, PORT_SNAPSHOT); - start = clock_ns(CLOCK_MONOTONIC); - status = inb(PORTB_STATUS); - if ((status & PORTB_PACKET_AVAILABLE) == 0) { - close(lock); - close(entropy_fd); - unlink(argv[2]); - puts("0"); - return 0; - } - result = repair_restore(previous_id, argv[2], entropy_fd, start, argc == 4, - daemon); - close(lock); - return result; -} - -// Repair steps 6 and 7 after the shell's activation and identity work: the -// acknowledgement when the packet asked for one, then the daemon runs step 7 -// asynchronously. The record tells it which CPUs activation onlined. The -// readiness path started restore_elapsed_us before the helper's clock step, -// at last_step_realtime_ns. -static int cmd_restore_finish(int argc, char **argv) -{ - bool new_cpus[MAX_CPUS] = {false}; - const char *new_list = "none"; - struct restore_timing timing; - struct time_state state; - struct checks checks; - char text[256]; - int64_t start; - bool ack = false; - int new_count = 0; - int signaled; - - for (int index = 0; index < argc; index++) { - if (strcmp(argv[index], "--ack") == 0) { - ack = true; - } else if (strcmp(argv[index], "--new-cpus") == 0 && index + 1 < argc) { - new_list = argv[++index]; - new_count = parse_new_cpus(new_list, new_cpus); - } else { - new_count = -1; - } - } - if (new_count < 0 || strlen(new_list) > 127) { - fprintf(stderr, "usage: nvx-time restore-finish [--ack] " - "[--new-cpus LIST]\n"); - return 2; - } - if (state_load(&state) != 0) - return repair_failed("G_REPAIR_CLOCK", "the time state is unreadable"); - g_generation = (uint32_t)state.generation; - snprintf(text, sizeof(text), "generation=%" PRIu64 "\nnew_cpus=%s\n", - state.generation, new_count > 0 ? new_list : "none"); - if (replace_file(RESTORE_PATH, RESTORE_PATH ".tmp", text) != 0) { - snprintf(text, sizeof(text), "record the restore: %s", - strerror(errno)); - return repair_failed("G_REPAIR_CLOCK", text); - } - if (ack && enable_ports() != 0) { - fprintf(stderr, "nvx-time: ioperm: %s\n", strerror(errno)); - return 1; - } - start = clock_ns(CLOCK_MONOTONIC) - - (clock_ns(CLOCK_REALTIME) - state.last_step_realtime_ns) - - state.checks[PHASE_RESTORE].elapsed_us * NSEC_PER_USEC; - signaled = signal_restore(daemon_pid(), ack, start); - if (signaled == 0) - return 0; - if (signaled > 0 || !g_report_only) { - memset(&checks, 0, sizeof(checks)); - checks.phase = PHASE_RESTORE; - check_failed(&checks, "C10", "the time daemon is not running"); - return 1; - } - // Report-only runs may lack the daemon; finish the restore here, where - // step 7's CPU time is restore_work's own. - if (ack) - outb(SNAPSHOT_ACKNOWLEDGE, PORT_SNAPSHOT); - timing.start_ns = clock_ns(CLOCK_MONOTONIC); - timing.readiness_us = (timing.start_ns - start) / NSEC_PER_USEC; - timing.prior_cpu_ns = 0; - timing.cpu_base_ns = cpu_time_ns(); - timing.fork_fd = -1; - (void)restore_work(new_cpus, new_count, true, &timing); - return 0; -} - -// Formats nvx-time status's lines from STATE: one per recorded phase, in the -// order boot, capture, and restore, with status=pending and neither -// elapsed_us nor cpu_us while its check runs, then the runtime line. -// Report-only mode appends each check's failure count, and a failed check's -// status is fail. Returns whether every phase line reports ok. -static bool format_status(const struct time_state *state, char *text, - size_t size) -{ - size_t length = 0; - bool ok = true; - - text[0] = '\0'; - for (int phase = 0; phase < CHECK_PHASES; phase++) { - const struct check_state *check = &state->checks[phase]; - const char *status = check->status; - char times[64] = ""; - char suffix[40] = ""; - - if (status[0] == '\0') - continue; - if (strcmp(status, "pending") != 0) - snprintf(times, sizeof(times), - " elapsed_us=%" PRId64 " cpu_us=%" PRId64, - check->elapsed_us, check->cpu_us); - if (g_report_only) - snprintf(suffix, sizeof(suffix), " failures=%" PRId64, - check->failures > 0 ? check->failures : 0); - ok = ok && strcmp(status, "ok") == 0; - append_text(text, size, &length, - "%s: v=1 phase=%s status=%s cpus=%" PRIu64 - " tsc_hz=%" PRIu64 " lapic_hz=%" PRIu64 - " generation=%" PRIu64 "%s%s\n", - abi_prefix(), k_phase_names[phase], status, check->cpus, - state->tsc_hz, state->lapic_hz, check->generation, times, - suffix); - } - append_text(text, size, &length, - "%s: v=1 phase=runtime status=%s generation=%" PRIu64 - " discontinuities=%" PRIu64 " offset_ns=%" PRId64 - " uncertainty_ns=%" PRId64 " rejected_samples=%" PRIu64 - " last_sample_error=%s\n", - abi_prefix(), - state->synchronized != 0 ? "synchronized" : "unsynchronized", - state->generation, state->discontinuities, state->offset_ns, - state->uncertainty_ns, state->rejected_samples, - state->last_sample_error); - return ok; -} - -// Prints the time state on demand, after waiting for pending checks: CI and -// the matrix driver run it over the console; production paths never do. -// Exits with 0 only when every phase line reports ok. -static int cmd_status(void) -{ - struct time_state state; - char text[1024]; - int waited = wait_for_checks(&state, false); - bool ok = format_status(&state, text, sizeof(text)); - - fputs(text, stdout); - fflush(stdout); - return waited == 0 && ok ? 0 : 1; -} - -static int cmd_sample(void) -{ - struct time_sample sample; - char detail[DETAIL_MAX] = ""; - int64_t theta; - int64_t epsilon; - unsigned status; - int result; - int lock; - - if (enable_ports() != 0) { - fprintf(stderr, "nvx-time: ioperm: %s\n", strerror(errno)); - return 1; - } - status = inb(PORTB_STATUS); - if ((status & PORTB_TIME_WINDOW) == 0) { - fprintf(stderr, "nvx-time: portb status 0x%02x has no time window\n", - status); - return 1; - } - lock = lock_portb(detail, sizeof(detail)); - result = lock < 0 ? -1 : take_sample(&sample, detail, sizeof(detail)); - if (lock >= 0) - close(lock); - if (result != 0) { - fprintf(stderr, "nvx-time: %s\n", detail); - return 1; - } - pair_clocks(sample.t0, sample.t1, sample.utc_ns, &theta, &epsilon); - printf("flags=0x%02x generation=%" PRIu32 " utc_ns=%" PRIu64 - " t0=%" PRId64 " t1=%" PRId64 " theta_ns=%" PRId64 - " epsilon_ns=%" PRId64 "\n", - sample.flags, sample.generation, sample.utc_ns, sample.t0, - sample.t1, theta, epsilon); - return 0; -} - -static int cmd_generation_id(void) -{ - uint8_t id[GENERATION_ID_SIZE]; - char text[GENERATION_ID_SIZE * 2 + 1]; - unsigned status; - int lock; - - if (enable_ports() != 0) { - fprintf(stderr, "nvx-time: ioperm: %s\n", strerror(errno)); - return 1; - } - status = inb(PORTB_STATUS); - if ((status & PORTB_GENERATION_ID_AVAILABLE) == 0) { - fprintf(stderr, "nvx-time: the VM generation ID is unavailable\n"); - return 1; - } - lock = lock_file(PORTB_LOCK_PATH); - if (lock < 0) { - fprintf(stderr, "nvx-time: portb lock: %s\n", strerror(errno)); - return 1; - } - outb(SELECT_GENERATION_ID, PORTB_STATUS); - portb_read(PORTB_DATA, id, sizeof(id)); - close(lock); - format_hex(id, sizeof(id), text); - puts(text); - return 0; -} - -// --------------------------------------------------------------------------- -// Unit-test entry points: pure decoders and decisions over fixture files -// --------------------------------------------------------------------------- - -static int test_bytes(const char *path, uint8_t *buffer, size_t size) -{ - memset(buffer, 0, size); - return read_bytes(path, buffer, size) < 0 ? -1 : 0; -} - -static int test_text(const char *path, char *buffer, size_t size) -{ - return read_text(path, buffer, size) < 0 ? -1 : 0; -} - -static int test_packet(int argc, char **argv) -{ - static uint8_t bytes[PACKET_MAX_SIZE]; - static struct restore_packet packet; - uint8_t previous[GENERATION_ID_SIZE]; - char detail[DETAIL_MAX]; - char id[GENERATION_ID_SIZE * 2 + 1]; - uint64_t recorded; - - if (argc != 3 || test_bytes(argv[0], bytes, sizeof(bytes)) != 0 || - parse_hex(argv[2], previous, sizeof(previous)) != 0) - return 2; - recorded = strtoull(argv[1], NULL, 10); - if (parse_packet_header(bytes, &packet, detail, sizeof(detail)) != 0) { - printf("error G_REPAIR_PACKET %s\n", detail); - return 0; - } - if (packet.generation == recorded) { - printf("stale\n"); - return 0; - } - if ((uint64_t)packet.generation != recorded + 1) { - printf("error G_REPAIR_GENERATION generation %" PRIu32 "\n", - packet.generation); - return 0; - } - if (parse_packet_body(bytes + PACKET_HEADER_SIZE, &packet, detail, - sizeof(detail)) != 0) { - printf("error G_REPAIR_PACKET %s\n", detail); - return 0; - } - if (memcmp(packet.entropy, previous, GENERATION_ID_SIZE) == 0) { - printf("error G_REPAIR_GENERATION generation ID unchanged\n"); - return 0; - } - format_hex(packet.entropy, GENERATION_ID_SIZE, id); - printf("ok flags=%u online=%u ranges=%u generation=%" PRIu32 - " rate_deviation=%" PRId32 " frequency=%" PRId64 - " downtime_ns=%" PRIu64 " utc_ns=%" PRIu64 " generation_id=%s", - packet.flags, packet.online_vp_count, packet.range_count, - packet.generation, packet.rate_deviation, - restore_frequency(packet.rate_deviation), packet.downtime_ns, - packet.utc_ns, id); - for (unsigned index = 0; index < packet.range_count; index++) - printf(" range=%" PRIu64 ":%" PRIu64, packet.ranges[index][0], - packet.ranges[index][1]); - putchar('\n'); - return 0; -} - -static int test_sample(int argc, char **argv) -{ - uint8_t bytes[SAMPLE_SIZE]; - struct time_sample sample; - char detail[DETAIL_MAX]; - - if (argc != 2 || test_bytes(argv[0], bytes, sizeof(bytes)) != 0) - return 2; - if (parse_sample(bytes, &sample, detail, sizeof(detail)) != 0) - printf("error %s\n", detail); - else if (sample.generation != strtoul(argv[1], NULL, 10)) - printf("error generation %" PRIu32 "\n", sample.generation); - else - printf("ok flags=%u generation=%" PRIu32 " utc_ns=%" PRIu64 "\n", - sample.flags, sample.generation, sample.utc_ns); - return 0; -} - -// Watcher rules per line (KMSG), or the C4 verdict over message lines -// (BOOT_LOG) or SYSLOG_ACTION_READ_ALL text (SYSLOG). -enum line_test { LINES_KMSG, LINES_BOOT_LOG, LINES_SYSLOG }; - -static int test_lines(int argc, char **argv, enum line_test mode) -{ - static char text[TEXT_MAX]; - struct boot_log log; - char detail[DETAIL_MAX]; - bool boot = mode != LINES_KMSG; - - if (argc != 1 || test_text(argv[0], text, sizeof(text)) != 0) - return 2; - memset(&log, 0, sizeof(log)); - if (mode == LINES_SYSLOG) { - boot_log_scan(text, &log); - } else { - for (char *line = strtok(text, "\n"); line != NULL; - line = strtok(NULL, "\n")) { - const char *code = match_watch_rule(line); - - if (boot) - boot_log_add(&log, line); - else - printf("%s\n", code != NULL ? code : "none"); - } - } - if (boot) { - if (boot_log_verdict(&log, detail, sizeof(detail))) - printf("ok\n"); - else - printf("fail %s\n", detail); - if (log.wx[0] != '\0') - printf("wx %s\n", log.wx); - } - return 0; -} - -static int test_timer_list(int argc, char **argv) -{ - static char text[TEXT_MAX]; - bool cpus[MAX_CPUS]; - char detail[DETAIL_MAX]; - - if (argc != 2 || test_text(argv[0], text, sizeof(text)) != 0 || - parse_cpu_list(argv[1], cpus) <= 0) - return 2; - if (timer_list_verdict(text, cpus, detail, sizeof(detail))) - printf("ok\n"); - else - printf("fail %s\n", detail); - return 0; -} - -static int test_checks(const char *name, int argc, char **argv) -{ - static char text[TEXT_MAX]; - struct checks checks; - bool cpus[MAX_CPUS]; - - memset(&checks, 0, sizeof(checks)); - checks.phase = PHASE_BOOT; - if (strcmp(name, "cpuinfo") == 0) { - if (argc != 2 || test_text(argv[0], text, sizeof(text)) != 0 || - parse_cpu_list(argv[1], cpus) <= 0) - return 2; - if (check_cpuinfo_text(&checks, text, cpus, true)) { - printf("ok"); - for (int cpu = 0; cpu < MAX_CPUS; cpu++) { - if (checks.cpu_khz_known[cpu]) - printf(" cpu%d_khz=%" PRIu64, cpu, checks.cpu_khz[cpu]); - } - putchar('\n'); - } - } else if (strcmp(name, "cmdline") == 0) { - if (argc != 1) - return 2; - snprintf(text, sizeof(text), "%s", argv[0]); - if (check_cmdline_text(&checks, text)) - printf("ok\n"); - } else if (strcmp(name, "clocksource") == 0) { - if (argc != 2) - return 2; - snprintf(text, sizeof(text), "%s", argv[1]); - if (check_clocksource_values(&checks, argv[0], text)) - printf("ok\n"); - } else { - return 2; - } - return 0; -} - -static int test_correction(int argc, char **argv) -{ - struct timex tx; - bool step; - - if (argc != 3) - return 2; - step = plan_correction(strtoll(argv[0], NULL, 10), strtoll(argv[1], NULL, 10), - strcmp(argv[2], "fast") == 0, &tx); - if (step) - printf("step modes=0x%x sec=%ld nsec=%ld\n", tx.modes, - (long)tx.time.tv_sec, (long)tx.time.tv_usec); - else - printf("slew modes=0x%x offset=%ld status=0x%x constant=%ld " - "maxerror=%ld esterror=%ld\n", - tx.modes, tx.offset, (unsigned)tx.status, tx.constant, - tx.maxerror, tx.esterror); - return 0; -} - -static int test_state(int argc, char **argv) -{ - static char text[4096]; - struct time_state state; - - if (argc != 1 || test_text(argv[0], text, sizeof(text)) != 0) - return 2; - if (state_parse(text, &state) != 0) { - printf("error\n"); - return 0; - } - state_format(&state, text, sizeof(text)); - fputs(text, stdout); - return 0; -} - -static int test_restore_record(int argc, char **argv) -{ - static char text[4096]; - unsigned long long generation; - bool cpus[MAX_CPUS]; - const char *separator = ""; - int count; - - if (argc != 1 || test_text(argv[0], text, sizeof(text)) != 0) - return 2; - count = parse_restore_record(text, &generation, cpus); - if (count < 0) { - printf("error\n"); - return 0; - } - printf("generation=%llu new_cpus=", generation); - for (int cpu = 0; cpu < MAX_CPUS; cpu++) { - if (cpus[cpu]) { - printf("%s%d", separator, cpu); - separator = ","; - } - } - printf("%s count=%d\n", count == 0 ? "none" : "", count); - return 0; -} - -// A side job and a foreground that fail a known number of times, so the test -// sees whether check_cpus_parallel() merges every job's failures. -static void test_failing_job(struct checks *checks, void *context) -{ - const int *failures = context; - - for (int index = 0; index < *failures; index++) - check_failed(checks, "C4", "side job failure %d", index + 1); -} - -static void test_failing_foreground(struct checks *checks, void *context) -{ - (void)context; - check_failed(checks, "C9", "foreground failure"); -} - -static int test_parallel(int argc, char **argv) -{ - static int failures[3] = {1, 2, 3}; - struct side_job sides[ARRAY_SIZE(failures)]; - struct checks checks; - bool cpus[MAX_CPUS]; - - if (argc != 1 || parse_cpu_list(argv[0], cpus) <= 0) - return 2; - memset(&checks, 0, sizeof(checks)); - checks.phase = PHASE_BOOT; - for (size_t index = 0; index < ARRAY_SIZE(sides); index++) - sides[index] = (struct side_job){.run = test_failing_job, - .context = &failures[index]}; - check_cpus_parallel(&checks, cpus, false, false, sides, ARRAY_SIZE(sides), - test_failing_foreground, NULL); - printf("failures=%d\n", checks.failures); - return 0; -} - -// A thread that an idle thread starts, so it is idle too; it waits until the -// test ends. -struct test_thread { - pthread_mutex_t lock; - pthread_cond_t changed; - pid_t tid; - bool done; -}; - -static void *test_thread_run(void *argument) -{ - struct test_thread *thread = argument; - - pthread_mutex_lock(&thread->lock); - thread->tid = (pid_t)syscall(SYS_gettid); - pthread_cond_broadcast(&thread->changed); - while (!thread->done) - pthread_cond_wait(&thread->changed, &thread->lock); - pthread_mutex_unlock(&thread->lock); - return NULL; -} - -// Prints the policies of an idle caller and of the idle thread it started -// once a promoter fires 50 ms later, then the caller's policy after a -// promoter stopped before its 10 s bound, and how long stopping took. -static int test_promote(void) -{ - struct test_thread thread = {.lock = PTHREAD_MUTEX_INITIALIZER, - .changed = PTHREAD_COND_INITIALIZER}; - struct promoter promoter; - pthread_t handle; - long caller; - long started; - int64_t start; - - if (!promoter_start(&promoter, clock_ns(CLOCK_MONOTONIC) - IDLE_BOUND_NS + - 50 * NSEC_PER_MSEC)) - return 1; - set_idle_priority(0, true); - if (pthread_create(&handle, NULL, test_thread_run, &thread) != 0) - return 1; - pthread_mutex_lock(&thread.lock); - while (thread.tid == 0) - pthread_cond_wait(&thread.changed, &thread.lock); - pthread_mutex_unlock(&thread.lock); - pthread_join(promoter.thread, NULL); - caller = syscall(SYS_sched_getscheduler, 0); - started = syscall(SYS_sched_getscheduler, thread.tid); - pthread_mutex_lock(&thread.lock); - thread.done = true; - pthread_cond_broadcast(&thread.changed); - pthread_mutex_unlock(&thread.lock); - pthread_join(handle, NULL); - printf("promoted caller=%ld thread=%ld\n", caller, started); - - start = clock_ns(CLOCK_MONOTONIC); - if (!promoter_start(&promoter, - start - IDLE_BOUND_NS + 10 * NSEC_PER_SEC)) - return 1; - set_idle_priority(0, true); - promoter_stop(&promoter); - printf("stopped caller=%ld stop_ms=%" PRId64 "\n", - syscall(SYS_sched_getscheduler, 0), - (clock_ns(CLOCK_MONOTONIC) - start) / NSEC_PER_MSEC); - return 0; -} - -static int64_t g_test_fork_burn_ms; - -// Spends MS milliseconds of this process's CPU time. -static void burn_cpu(int64_t ms) -{ - int64_t until = cpu_time_ns() + ms * NSEC_PER_MSEC; - - while (cpu_time_ns() < until) - ; -} - -static void test_fork_burn(void) -{ - burn_cpu(g_test_fork_burn_ms); -} - -// Forks a repair step 7 worker as start_restore does, after PRE_MS of -// preparation, with a fork that costs the parent FORK_MS; the worker spends -// CHILD_MS and prints its preparation share and step 7's CPU time. -static int test_deferred_restore_cpu(int argc, char **argv) -{ - struct restore_timing timing = {0}; - int64_t start = cpu_time_ns(); - int status; - pid_t pid; - - if (argc != 3) - return 2; - g_test_fork_burn_ms = strtoll(argv[1], NULL, 10); - if (pthread_atfork(NULL, test_fork_burn, NULL) != 0) - return 1; - burn_cpu(strtoll(argv[0], NULL, 10)); - pid = fork_deferred_restore(start, &timing); - if (pid == 0) { - burn_cpu(strtoll(argv[2], NULL, 10)); - printf("prior_ms=%" PRId64 " total_ms=%" PRId64 "\n", - timing.prior_cpu_ns / NSEC_PER_MSEC, - deferred_restore_cpu_ns(&timing) / NSEC_PER_MSEC); - fflush(stdout); - _exit(0); - } - if (pid < 0 || waitpid(pid, &status, 0) != pid) - return 1; - return WIFEXITED(status) ? WEXITSTATUS(status) : 1; -} - -static int cmd_test(int argc, char **argv) -{ - const char *name = argc > 0 ? argv[0] : ""; - - g_test = true; - g_report_only = true; - argc--; - argv++; - if (strcmp(name, "packet") == 0) - return test_packet(argc, argv); - if (strcmp(name, "sample") == 0) - return test_sample(argc, argv); - if (strcmp(name, "pair") == 0 && argc == 3) { - int64_t theta; - int64_t epsilon; - - pair_clocks(strtoll(argv[0], NULL, 10), strtoll(argv[1], NULL, 10), - strtoull(argv[2], NULL, 10), &theta, &epsilon); - printf("theta_ns=%" PRId64 " epsilon_ns=%" PRId64 "\n", theta, epsilon); - return 0; - } - if (strcmp(name, "kmsg") == 0) - return test_lines(argc, argv, LINES_KMSG); - if (strcmp(name, "console") == 0 && argc == 2) { - g_async_output = strcmp(argv[1], "async") == 0; - console_write(argv[0]); - return 0; - } - if (strcmp(name, "exhaustive-leaf") == 0 && argc == 9) { - uint32_t value[4]; - uint32_t out_of_range[4]; - char detail[DETAIL_MAX]; - - for (int index = 0; index < 4; index++) { - value[index] = (uint32_t)strtoul(argv[1 + index], NULL, 0); - out_of_range[index] = (uint32_t)strtoul(argv[5 + index], NULL, 0); - } - if (exhaustive_leaf_ok((uint32_t)strtoul(argv[0], NULL, 0), value, - out_of_range, detail, sizeof(detail))) - printf("pass\n"); - else - printf("fail %s\n", detail); - return 0; - } - if (strcmp(name, "exhaustive-report") == 0 && argc == 4) { - struct exhaustive state; - - memset(&state, 0, sizeof(state)); - exhaustive_report(&state, argv[0], atoi(argv[1]), - strcmp(argv[2], "pass") == 0, argv[3]); - printf("failures=%d\n", state.failures); - return 0; - } - if (strcmp(name, "exhaustive-summary") == 0 && argc == 2) { - struct exhaustive state; - int status; - - memset(&state, 0, sizeof(state)); - state.failures = atoi(argv[1]); - status = exhaustive_summary(&state, atoi(argv[0])); - printf("exit=%d\n", status); - return 0; - } - if (strcmp(name, "boot-log") == 0) - return test_lines(argc, argv, LINES_BOOT_LOG); - if (strcmp(name, "syslog") == 0) - return test_lines(argc, argv, LINES_SYSLOG); - if (strcmp(name, "timer-list") == 0) - return test_timer_list(argc, argv); - if (strcmp(name, "correction") == 0) - return test_correction(argc, argv); - if (strcmp(name, "frequency") == 0 && argc == 1) { - int64_t frequency = restore_frequency((int32_t)strtol(argv[0], NULL, 10)); - - printf("frequency=%" PRId64 " ppb=%" PRId64 "\n", frequency, - frequency_to_ppb(frequency)); - return 0; - } - if (strcmp(name, "event") == 0 && argc == 6) { - char line[EVENT_MAX + 1]; - enum phase phase = PHASE_RUNTIME; - - for (size_t index = 0; index < ARRAY_SIZE(k_phase_names); index++) { - if (strcmp(argv[2], k_phase_names[index]) == 0) - phase = (enum phase)index; - } - g_report_only = false; - format_event(line, argv[0], argv[1], phase, - (uint32_t)strtoul(argv[3], NULL, 10), - strtoll(argv[4], NULL, 10), argv[5]); - fputs(line, stdout); - return 0; - } - if (strcmp(name, "state") == 0) - return test_state(argc, argv); - if (strcmp(name, "status") == 0 && argc == 2) { - static char text[4096]; - struct time_state state; - char out[1024]; - bool ok; - - if (test_text(argv[0], text, sizeof(text)) != 0 || - state_parse(text, &state) != 0) - return 2; - g_report_only = strcmp(argv[1], "report-only") == 0; - ok = format_status(&state, out, sizeof(out)); - fputs(out, stdout); - printf("ok=%d\n", ok ? 1 : 0); - return 0; - } - if (strcmp(name, "restore-record") == 0) - return test_restore_record(argc, argv); - if (strcmp(name, "deferred-restore-cpu") == 0) - return test_deferred_restore_cpu(argc, argv); - if (strcmp(name, "deferral") == 0 && argc == 0) { - printf("deferred_start_ms=%" PRId64 " idle_bound_ms=%" PRId64 "\n", - (int64_t)(DEFERRED_START_NS / NSEC_PER_MSEC), - (int64_t)(IDLE_BOUND_NS / NSEC_PER_MSEC)); - return 0; - } - if (strcmp(name, "parallel") == 0) - return test_parallel(argc, argv); - if (strcmp(name, "promote") == 0 && argc == 0) - return test_promote(); - if (strcmp(name, "idle-priority") == 0 && argc == 0) { - long idle; - - set_idle_priority(0, true); - idle = syscall(SYS_sched_getscheduler, 0); - set_idle_priority(0, false); - printf("idle=%ld normal=%ld\n", idle, - syscall(SYS_sched_getscheduler, 0)); - return 0; - } - return test_checks(name, argc, argv); -} - -// --------------------------------------------------------------------------- -// Entry point -// --------------------------------------------------------------------------- - -static bool cmdline_requests_report_only(void) -{ - char text[4096]; - - if (read_text("/proc/cmdline", text, sizeof(text)) < 0) - return false; - for (char *token = strtok(text, " \t\n"); token != NULL; - token = strtok(NULL, " \t\n")) { - if (strcmp(token, "nvx_time_abi=report-only") == 0) - return true; - } - return false; -} - -static int usage(void) -{ - fprintf(stderr, - "usage: nvx-time [--report-only] COMMAND\n" - " boot\n" - " check --phase capture|restore [--new-cpus LIST]\n" - " pre-capture\n" - " capture 0|1 GENERATION_ID ENTROPY_FILE [--finish]\n" - " cancel-capture\n" - " restore-finish [--ack] [--new-cpus LIST]\n" - " status\n" - " sample\n" - " generation-id\n" - " exhaustive\n" - " test NAME ARGUMENTS...\n"); - return 2; -} - -int main(int argc, char **argv) -{ - char *arguments[32]; - int count = 0; - const char *command; - - for (int index = 1; index < argc; index++) { - if (strcmp(argv[index], "--report-only") == 0) - g_report_only = true; - else if (count < (int)ARRAY_SIZE(arguments)) - arguments[count++] = argv[index]; - else - return usage(); - } - if (count == 0) - return usage(); - command = arguments[0]; - if (strcmp(command, "test") == 0) - return cmd_test(count - 1, arguments + 1); - // An inherited SIG_IGN would make the kernel reap the release child and - // the daemon before waitpid() sees them. - signal(SIGCHLD, SIG_DFL); - if (cmdline_requests_report_only()) - g_report_only = true; - if (strcmp(command, "boot") == 0 && count == 1) - return cmd_boot(); - if (strcmp(command, "check") == 0) - return cmd_check(count - 1, arguments + 1); - if (strcmp(command, "pre-capture") == 0 && count == 1) - return cmd_pre_capture(); - if (strcmp(command, "capture") == 0) - return cmd_capture(count - 1, arguments + 1); - if (strcmp(command, "cancel-capture") == 0 && count == 1) - return cmd_cancel_capture(); - if (strcmp(command, "restore-finish") == 0) - return cmd_restore_finish(count - 1, arguments + 1); - if (strcmp(command, "status") == 0 && count == 1) - return cmd_status(); - if (strcmp(command, "sample") == 0 && count == 1) - return cmd_sample(); - if (strcmp(command, "generation-id") == 0 && count == 1) - return cmd_generation_id(); - if (strcmp(command, "exhaustive") == 0 && count == 1) - return exhaustive_run(); - return usage(); -} diff --git a/nvx-rs/Cargo.toml b/nvx-rs/Cargo.toml index 577d0936d..ec40b88d3 100644 --- a/nvx-rs/Cargo.toml +++ b/nvx-rs/Cargo.toml @@ -19,14 +19,25 @@ name = "nvx" [features] default = ["host"] # The host side: artifact discovery, launch, network and filesystem lowering, control sessions, -# process and platform primitives, and the supervision of VM instances. -host = ["serde", "dep:getrandom", "dep:serde_json", "dep:nix", "dep:windows-sys"] +# process and platform primitives, the supervision of VM instances, and the sandbox launch +# profile, whose aggregate child names are SHA-256 digests. +host = [ + "serde", + "dep:getrandom", + "dep:serde_json", + "dep:sha2", + "dep:nix", + "dep:windows-sys", +] # Stages the OpenVMM executable, guest kernel, and control initramfs at build time; see # `Artifacts::bundled`. The build script downloads the release pinned in `artifacts.json`, or # copies the release directory named by `NVX_BUNDLE_DIR`. bundled = ["host", "dep:sha2"] # Test doubles: the fake OpenVMM of `testing::fake_openvmm` and its `nvx-fake-openvmm` binary. testing = ["dep:getrandom", "dep:serde", "dep:serde_json", "dep:nix", "dep:windows-sys"] +# The `nvx-rs` command-line interface of `host::cli` and its `nvx-rs` binary, which `nvx.py` +# runs for the steps that it delegates. +cli = ["host"] # The serde form of `Hypervisor`. The `host` feature enables it. serde = ["dep:serde"] # The programs that run in Linux guests: the `guest` module and the `nvx-*` guest binaries. Build @@ -37,12 +48,15 @@ guest = ["dep:nix", "dep:getrandom"] getrandom = { version = "0.4", optional = true } serde = { version = "1.0.200", features = ["derive"], optional = true } serde_json = { version = "1.0.120", optional = true } +# The same release as the build script's: `host` enables both. +sha2 = { version = "0.10", optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.61", optional = true, features = [ "Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", + "Win32_System_Console", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Pipes", @@ -69,7 +83,8 @@ nix = { version = "0.31.3", default-features = false, optional = true, features ] } [build-dependencies] -# Only the `bundled` build script reads JSON; `host`, which `bundled` enables, enables it here too. +# Only the `bundled` build script reads JSON and computes digests; `host`, which `bundled` +# enables, enables both here too. serde_json = { version = "1.0.120", optional = true } sha2 = { version = "0.10", optional = true } @@ -83,6 +98,12 @@ path = "src/bin/nvx-fake-openvmm.rs" required-features = ["testing"] doc = false +[[bin]] +name = "nvx-rs" +path = "src/bin/nvx-rs.rs" +required-features = ["cli"] +doc = false + # The guest binaries. Their unit tests live in the library, so the binaries have no test harness. [[bin]] name = "nvx-console-pending" diff --git a/nvx-rs/README.md b/nvx-rs/README.md index 242a03c91..d80929ad3 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -4,9 +4,12 @@ control protocol, and the launch, policy lowering, and supervision of NVX microVMs. Its library is called `nvx`. The [`aci_edge_sandboxes` crate](../aci_edge_sandboxes/README.md) builds on it and -re-exports it as `aci_edge_sandboxes::nvx`, and later phases of -[microsoft/nvx#356](https://github.com/microsoft/nvx/issues/356) add the guest -programs and a host CLI to it. +re-exports it as `aci_edge_sandboxes::nvx`. The crate also holds the programs +that run in NVX's Linux guests, and the `nvx-rs` command-line interface, the +backend to which `nvx.py` delegates its sandbox, egress-policy, and artifact +steps, as phases of +[microsoft/nvx#356](https://github.com/microsoft/nvx/issues/356) move them to +Rust. An NVX sandbox is a microVM that OpenVMM runs with the managed lifecycle: the guest's init starts a managed agent, which runs the workloads that the host @@ -25,11 +28,15 @@ nvx-rs/ │ ├── net/ CIDR parsing and subtraction, and hostname and guest-network forms │ ├── hypervisor.rs │ ├── error.rs nvx::Error and its ErrorKind -│ ├── host/ feature `host` (default): artifacts, launch, network, filesystem, session, -│ │ process, platform, VM instances, and outcome reports +│ ├── host/ feature `host` (default): artifacts, launch, network and egress policy files, +│ │ filesystem, session, process, platform, VM instances, outcome reports, and +│ │ the sandbox launch profile of `nvx.py`, with the Python semantics they keep; +│ │ with feature `cli`, the `nvx-rs` CLI │ ├── testing/ feature `testing`: the fake OpenVMM │ ├── guest/ feature `guest`: the programs that run in Linux guests -│ └── bin/ nvx-fake-openvmm and the guest binaries +│ └── bin/ nvx-rs, nvx-fake-openvmm, and the guest binaries +├── tests/ fake-OpenVMM, CLI, and guest-binary tests, and the golden fixtures that Python +│ recorded for the ports of its code, in fixtures/ ├── build.rs bundled-release staging └── artifacts.json the NVX release that the `bundled` feature stages ``` @@ -40,26 +47,313 @@ nvx-rs/ | `nvx::contract` | The OpenVMM microVM ABI, broker protocol, and control-contract revision that the crate speaks, and `manifest_is_compatible` for a release's `SOURCE-MANIFEST.json` | | `nvx::net` | `Cidr`: IPv4 and IPv6 networks and their subtraction; `is_valid_hostname` and `is_valid_guest_network`, the forms of a VM's hostname and guest address | | `nvx::Hypervisor` | KVM, MSHV, or WHP, with its serde form behind the `serde` feature | -| `nvx::host::artifacts` | `Artifacts` (also `nvx::Artifacts`): the OpenVMM executable, guest kernel, and Alpine initramfs, from a release, a repository checkout, the environment, a directory next to the executable, or the bundle | +| `nvx::host::artifacts` | `Artifacts` (also `nvx::Artifacts`): the OpenVMM executable, guest kernel, and Alpine initramfs, from a release, a repository checkout, the environment, a directory next to the executable, or the bundle. `ArtifactDir` and `Guest`: every artifact of the guests that `nvx.py` runs, the OpenVMM executable, the kernel, the Alpine, Ubuntu, and Azure Linux initramfs images and their package manifests, and the Ubuntu distro layer and its manifest, in a checkout's `build/` and `openvmm/target/release/` or a release's `guest/` and `bin/`, checked in the order and with the messages of `nvx.py`. `ArtifactPaths` lists them all and serializes as JSON | | `nvx::host::launch` | `LaunchSpec` and OpenVMM's command line: the managed lifecycle, the `WorkloadIdentity`, the 1024-byte kernel command line, its reserved parameters, the 32,767-character limit of a Windows command line, and `LaunchSpec::check`, which every command line applies to the memory size, the non-root identity, the hostname, the guest network, and the kernel parameters | -| `nvx::host::network` | Egress rules and their exact expansion into OpenVMM's IPv4 and IPv6 rules: exceptions, protocol-only rules, and port ranges, with at most 256 rules for each action | +| `nvx::host::network` | `NetworkPolicy` and `openvmm_arguments`: the egress default and rules, ingress, host-loopback access and forwards as OpenVMM options. Egress rules are lowered exactly into OpenVMM's IPv4 and IPv6 rules by the lowering of egress policy files, with at most 256 rules for each action | +| `nvx::host::network::policy` | Egress policy files: `compile_policy_file`, `compile_policy_bytes`, and `compile_policy` compile one to a `CompiledEgressPolicy` of native rules; see [Egress policy files](#egress-policy-files) | | `nvx::host::filesystem` | The `ExportPlan` of host paths through OpenVMM's aggregate virtio-fs device, the host-mapping table, the reserved guest targets, and `guest_path` and `resolve_guest_path` (also at `nvx::`) | | `nvx::host::instance` | `Instance`: a VM in a directory of its own. It starts OpenVMM with a fresh capability and waits until the guest is ready and has mounted the mapped paths, reconciles recorded state, recovers interrupted launches, runs workloads and pumps their output, and stops the VM with a forced fallback | | `nvx::host::session`, `process`, `platform`, `report` | The authenticated control session, OpenVMM processes, Linux and Windows primitives, and OpenVMM's outcome report | -| `nvx::testing::fake_openvmm` | A fake OpenVMM that serves the control console and runs scripted workloads without a VM | +| `nvx::host::sandbox` | The launch profile of `nvx.py sandbox`, ported from `scripts/nvx_tools/sandbox.py`: `SandboxLaunch`, with one to three EROFS `SandboxLayer`s in role order, the ext4 scratch disk, the entrypoint, arguments, hostname, workload identity, and resource limits, and its `--microvm-sandbox-block` and `--microvm-workload-identity` arguments and kernel command line, within the 1024-byte budget that OpenVMM leaves it. `mount`: `SandboxMount` live shares of host directories and files, their guest targets and the reserved ones, their access policies and owner, the aggregate of several shares, and the checks of their host paths. `options`: the checks that `nvx.py sandbox` and `nvx.py run` apply to their options: the attribution of `--mount-deny`, `--mount-allow`, and `--mount-write` to shares, the systemd policy, the targets of `nvx.py run` shares, and the managed exec environment; see [Python parity](#python-parity) | +| `nvx::testing::fake_openvmm` | A fake OpenVMM that serves the control console and runs scripted workloads without a VM, for direct launches and for the managed and one-shot sandboxes of `nvx-rs sandbox` and `nvx.py sandbox` | +| `nvx::host::cli` | Feature `cli`: the `nvx-rs` executable; see [The nvx-rs CLI](#the-nvx-rs-cli) | | `nvx::guest` | The programs that run in Linux guests; see [Guest binaries](#guest-binaries) | Lowering functions take the names by which messages refer to their inputs, such as `EgressRules::name` and `PathList::name`, so callers' messages name their own fields. +## Egress policy files + +`nvx::host::network::policy` compiles the egress policy files that +`nvx.py run` and `nvx.py sandbox` take with `--network-egress-policy-file` +([format](../doc/run.md#run-openvmm-directly)). It is a faithful port of +[`egress_policy.py`](../scripts/nvx_tools/egress_policy.py), which stays +authoritative until `nvx.py` delegates to `nvx-rs`: it accepts the same files, +compiles them to the same rules, and fails with the same messages. + +- A policy is at most 1 MiB of UTF-8 JSON whose root has only `allow` and + `deny` arrays. JSON is read as Python's `json.loads` reads it, so `NaN` and + `Infinity` are numbers, while a repeated field and an integer of more than 64 + digits are errors. +- Rules take the MXC form, with optional `to` destinations (`cidr` and + `except`) and `ports` selectors (`protocol`, `port`, and `endPort`), or the + legacy flat form (`cidr`, `except`, `protocol`, `port`, and `endPort`). + Protocols are `tcp`, `udp`, `icmp`, and `any`, and TCP and UDP take one port + or a range. A CIDR is read as Python's `ipaddress.ip_interface` reads it: + host bits are cleared, IPv4 netmasks such as `/255.255.0.0` are accepted, and + IPv6 scopes are refused. +- Each action, `allow` or `deny`, lowers to at most 256 native rules: + exceptions are subtracted, destinations merge into the fewest networks, rules + for every port of a protocol subsume the narrower rules that they cover, and + a sweep over the port boundaries merges overlapping and adjacent ranges. The + rules sort by family, address, prefix length, protocol, and ports, and read + `CIDR`, `CIDR:PROTOCOL`, `CIDR:PROTOCOL:PORT`, or `CIDR:PROTOCOL:FIRST-LAST`. + +`openvmm_arguments` lowers `NetworkPolicy` rules, and so `aci_edge_sandboxes` +policies, the same way: an `EgressRule` is a policy file's MXC rule, its +messages name `EgressRules::name` where a file's name `allow` or `deny`, such +as `network.egress.allow[0].to[1].cidr is not a valid IPv4 or IPv6 CIDR: x`, +and the checks that only `NetworkPolicy` has, such as the host-loopback ones, +stay in `openvmm_arguments`. + +Messages match Python's byte for byte, but two cases have no Rust equivalent. A +string that Python would hold with an unpaired surrogate, such as one decoded +from a `\ud800` escape or a path that is not Unicode, shows the surrogate as +`\udXXX`, which is how Python prints it, and paths show as +`str(pathlib.Path(path))` does. Python's decoder raises `RecursionError` for a +document nested more deeply than its stack allows; this reader has no such +limit, so those documents fail as invalid policies instead. + +[`scripts/nvx_rs_fixtures.py`](../scripts/nvx_rs_fixtures.py) records the +Python compiler's outcomes for every policy that `scripts/test_egress_policy.py` +compiles, hand-written edge cases, and a seeded random corpus in +[`tests/fixtures/egress-policy.json`](tests/fixtures/egress-policy.json), which +the tests replay without Python; `NVX_EGRESS_POLICY_FIXTURE` points the tests at +a larger corpus that the script writes with `--output` and `--random-cases`. See +[Python parity](#python-parity) for how to regenerate and check the fixtures. + +## Python parity + +`nvx::host::network::policy`, `nvx::host::sandbox`, and the guest lookups of +`nvx::host::artifacts` replace Python code that `nvx.py` runs, so they keep its +contract byte for byte: its limits, option semantics, outputs, and messages. +They check values as Python does: whitespace is what `str.isspace` accepts, +integers are what `int` reads, UUIDs what `uuid.UUID` reads, and JSON what +`json.loads` reads, failing with its messages; host paths are normalized as +`pathlib` normalizes them, with backslashes on Windows, and resolved as +`Path.resolve` resolves them; file identities are those of `os.stat`, which on +Windows are the volume serial number and the 128-bit file ID; and messages +quote values as `repr` does. The private `python` module implements these +semantics after CPython 3.14, with the Unicode 16.0.0 tables of Python 3.14. +Python decodes operating-system strings that are not valid Unicode into +surrogates: paths print as Python prints them, and the filesystem queries +refuse them. + +[`scripts/nvx_rs_fixtures.py`](../scripts/nvx_rs_fixtures.py) records what the +Python code does with fixed inputs and seeded random corpora in +`tests/fixtures`: `python.json` for the Python semantics, `sandbox.json` and +`sandbox_options.json` for the launch profile and the sandbox options, for +POSIX and Windows paths alike, `egress-policy.json` for the policy compiler, +and `cli.json` for `nvx.py sandbox` as a command; see +[Golden scenarios](#golden-scenarios). It also generates the Unicode tables of +`src/host/python/unicode.rs`. The golden tests compare the ports with these +records and need no Python. The records depend on the interpreter, so +regenerate them with CPython 3.14 after a change to the Python code or the +generator; CI's Pythons are older, so CI does not check them: + +```bash +python3 scripts/nvx_rs_fixtures.py # rewrite every generated file +python3 scripts/nvx_rs_fixtures.py --check # fail if any is out of date +``` + +## The nvx-rs CLI + +The `cli` feature builds the `nvx-rs` executable, a faithful port of the +runtime steps of `nvx.py` that `nvx.py` delegates to it +([microsoft/nvx#359](https://github.com/microsoft/nvx/issues/359)): + +```text +nvx-rs [-h] [--root PATH] artifacts +nvx-rs [-h] [--root PATH] egress compile PATH +nvx-rs [-h] [--root PATH] sandbox [run|provision|start|exec|stop|deprovision] [OPTIONS] +``` + +```bash +cd nvx-rs +cargo build --release --features cli # target/release/nvx-rs +``` + +Errors print `error: MESSAGE` to standard error and exit with status 1, as +`nvx.py` reports a `ScriptError`. Usage errors print the parser's usage and +`PROG: error: MESSAGE` and exit with status 2, as `argparse` does, and help +exits with status 0. Ctrl-C prints `Interrupted` and exits with status 130. +`sandbox run` and `sandbox exec` exit with the status of OpenVMM or of the +workload. Text ends its lines as Python's `print` ends them, with `\r\n` on +Windows and `\n` elsewhere, while workload output passes through unchanged. + +### Artifact root + +Every command uses the artifacts of one root: a repository checkout, with the +guest artifacts in `build/` and OpenVMM in `openvmm/target/release/`, or an +extracted release, with them in `guest/` and `bin/`. + +- `--root PATH` names the root, as `nvx.py`, which knows its repository root, + passes it. A directory that holds `bin/openvmm[.exe]`, `guest/vmlinux`, or + `guest/initramfs.cpio.gz` is a release, and any other is a checkout. +- Without `--root`, the root is the release whose `bin/` holds the executable, + as for a release's `bin/nvx-rs[.exe]`, or the checkout whose `nvx-rs/target/` + holds it at any depth, as after `cargo build`. Either must have its + `SOURCE-MANIFEST.json`; otherwise the command fails and asks for `--root`. +- The `NVX_OPENVMM` environment variable names an OpenVMM executable to run + instead of the root's, such as `nvx-fake-openvmm` in tests. A relative path is + relative to the working directory. + +### `artifacts` + +Prints the paths of the artifacts as JSON, as `json.dumps(value, indent=2, +sort_keys=True)` writes it. Paths are absolute and not checked: + +```json +{ + "guests": [ + { + "distribution": "Alpine Linux", + "distro_layer": null, + "distro_manifest": null, + "initramfs": "/nvx/build/initramfs.cpio.gz", + "name": "alpine", + "package_manifest": "/nvx/build/initramfs.cpio.gz.packages.json" + }, + { + "distribution": "Ubuntu Base", + "distro_layer": "/nvx/build/ubuntu-distro.erofs", + "distro_manifest": "/nvx/build/ubuntu-distro.erofs.manifest.json", + "initramfs": "/nvx/build/initramfs-ubuntu.cpio.gz", + "name": "ubuntu", + "package_manifest": "/nvx/build/initramfs-ubuntu.cpio.gz.packages.json" + }, + { + "distribution": "Azure Linux", + "distro_layer": null, + "distro_manifest": null, + "initramfs": "/nvx/build/initramfs-azurelinux.cpio.gz", + "name": "azurelinux", + "package_manifest": "/nvx/build/initramfs-azurelinux.cpio.gz.packages.json" + } + ], + "kernel": "/nvx/build/vmlinux", + "layout": "repository", + "openvmm": "/nvx/openvmm/target/release/openvmm", + "root": "/nvx" +} +``` + +`layout` is `repository` or `release`, the guests are listed in the order of +`nvx.py`, a guest without a distro layer has `null` for it and its manifest, +and `openvmm` is `NVX_OPENVMM` when that is set. + +### `egress compile PATH` + +Compiles an egress policy file, as `--network-egress-policy-file` does, and +prints its native OpenVMM rules in the same JSON form: +`{"allow": [RULE, ...], "deny": [RULE, ...]}`, each list in the order in which +OpenVMM receives the rules, which are `--network-egress-allow` and +`--network-egress-deny` values. A file that `nvx.py` refuses fails with its +message. + +### `sandbox` + +`nvx-rs sandbox` keeps the contract of `nvx.py sandbox`: every option of its +parser, with the same names, defaults, and value checks, and the same +operations, checks, messages, outputs, exit statuses, outcome reports, and +state directories. The operation defaults to `run`. Options take their values +as `--option VALUE` or `--option=VALUE`, repeated options accumulate, and +`--mount-deny`, `--mount-allow`, and `--mount-write` apply to the `--mount` +before them. + +- `run` prints `>> COMMAND`, OpenVMM's command line, quoted as + `subprocess.list2cmdline` quotes it on Windows and as `shlex.join` does + elsewhere. Unless `--dry-run` is given, it then runs OpenVMM with the + standard streams and exits with its status. `--outcome-report` asks OpenVMM + for its outcome report. +- `provision` records the sandbox in its state directory. `start` launches + OpenVMM detached with the managed lifecycle, passes it a fresh capability + through its standard input, and waits until the guest answers. `exec` runs a + workload, prints its standard output and then its standard error, and exits + with its status; `--outcome-report` writes + `{"operation_id": HEX, "outcome": {"category": CATEGORY, "operation": + "exec", "status_code": STATUS}, "schema_version": 1}`. `stop` shuts the VM + down and prints nothing. `deprovision` removes the state directory. + +The state directory and its files are those of `nvx.py`, so either +implementation can operate a sandbox that the other provisioned or started: + +| File | Contents | +| --- | --- | +| `config.json` | The provisioned sandbox, in the lowest of formats 1 to 6 that holds it, written as `json.dumps(value, indent=2, sort_keys=True)` writes it, with mode 0600, through a temporary file | +| `runtime.json` | Format 1: the `pid` and `start_time` of the running OpenVMM, as Python identifies a process, and its `control_endpoint`: `STATE/control.sock`, or `//./pipe/openvmm-microvm-HEX` on Windows | +| `control.capability` | The 32-byte capability of the running OpenVMM, with mode 0600 | +| `control.sock` | The control endpoint on POSIX hosts | +| `openvmm.log` | OpenVMM's output, appended by each start | +| `outcome.json` | OpenVMM's outcome report of the last start | +| `lifecycle.lock` | The lock that serializes lifecycle transitions, an exclusive `flock` on POSIX and a lock of its first byte on Windows, as Python takes it; deprovision leaves `deprovisioned` in it while it removes the directory | + +#### Guest features + +After the guest answers, `start` asks it for its control features and +requires `GuestFeatures::LAYERED_SANDBOX`, the features that its launches use: +per-execution environments (bit 4), since `exec` sends a working directory and +an environment. It needs neither cancellation (bit 0), since the CLI never +cancels a workload, and Ctrl-C ends the session, nor workload accounts +(bit 2, [#566](https://github.com/microsoft/nvx/issues/566)). A guest image +that lacks a required feature, or that predates the request, is stopped as a +failed start is, and the start fails with +`the guest image lacks control features that layered sandboxes need (...)`, +which names the missing features. `nvx.py` does not ask; this is the one +intended change of behavior. The guest agents of NVX advertise bits 0, 2, and 4 +in sandbox mode. + +#### Other differences from nvx.py + +- Long options cannot be abbreviated; `nvx.py` passes them in full. +- Help and usage are formatted for 80 columns, as `argparse` formats them for a + pipe, and an invalid choice lists the choices quoted, as Python 3.14.5 and + later do. +- Where `nvx.py` would end with a Python traceback, as for a configuration + without a field that `start` reads, `nvx-rs` prints `error: MESSAGE`, with the + message of the exception, and exits with status 1. +- Text goes to pipes and files in UTF-8, where Python on Windows uses the ANSI + code page unless `PYTHONIOENCODING` or UTF-8 mode says otherwise. +- `start` refuses a control endpoint that another process serves, which it + treats as one that has not appeared yet, and, on Windows, starts OpenVMM + without a console window, as `nvx::host::process` starts it. +- `exec` refuses more than 1 MiB of workload output, which the guest agent + never forwards. + +### Test hooks + +`--root` and `NVX_OPENVMM` let tests run the CLI without a VM. `nvx-fake-openvmm` +serves the sandbox profile: its managed lifecycle, with the capability on its +standard input, the control console, and an outcome report at stop, and its +one-shot runs, which run `/bin/echo`, `/bin/sleep`, `/bin/true`, or +`/bin/false`, write an outcome report if asked, and exit with the workload's +status. Kernel command-line tokens make it fail: `fake_exit_on_start=STATUS`, +`fake_exit_status=STATUS`, `fake_guest_features=MASK`, and +`fake_legacy_guest=1`, among others; see `nvx::testing::fake_openvmm`. + +```bash +cd nvx-rs +cargo build --features cli,testing --bins +NVX_OPENVMM=target/debug/nvx-fake-openvmm \ + target/debug/nvx-rs --root .. sandbox run --layer distro,LAYER,UUID \ + --scratch SCRATCH --entrypoint /bin/echo --arg hello +``` + +### Golden scenarios + +`tests/fixtures/cli.json` holds scenarios that the fixture script records from +`nvx.py sandbox`, run as a command with the root's OpenVMM a copy of +`nvx-fake-openvmm`, and from the Python functions behind `egress compile` and +`artifacts`: the option and usage errors, dry runs, one-shot runs, the +lifecycles of sandboxes in every configuration format, and the refusals of the +lifecycle operations. Each step records the exit status, standard output and +error, and the state files, with the root, process IDs, and random identifiers +as placeholders. Its `posix` and `windows` sections are recorded on each kind +of host, and the script rewrites or checks the section of the host that runs it. +It then also plays every scenario with `nvx-rs`, and each lifecycle with +`nvx.py` and `nvx-rs` taking turns both ways, and fails if any step differs +from `nvx.py`. `tests/cli.rs` replays the section of its host with `nvx-rs`, +without Python. + ## Features | Feature | Contents | | --- | --- | -| `host` (default) | The `host` module. It enables `serde` | +| `host` (default) | The `host` module. It enables `serde`, and `sha2`, which names the children of a sandbox's aggregate share | | `bundled` | Stages a release at build time; see [Bundled artifacts](#bundled-artifacts) | | `testing` | The fake OpenVMM and the `nvx-fake-openvmm` binary | +| `cli` | The `nvx-rs` CLI of `host::cli` and its `nvx-rs` binary; it enables `host`. See [The nvx-rs CLI](#the-nvx-rs-cli) | | `serde` | The serde form of `Hypervisor` | | `guest` | The `guest` module and the guest binaries; see [Guest binaries](#guest-binaries) | @@ -155,8 +449,8 @@ as `nvx/`. ## Guest binaries The `guest` feature adds the programs that run in NVX's Linux guests: the -`nvx::guest` module and one binary per program. Guest images install these -executables as `/sbin/nvx-*`. +`nvx::guest` module and one binary per program. The Alpine, Ubuntu, and Azure +Linux images ship all nine at their established `/sbin/nvx-*` paths. | Binary | Program | | --- | --- | @@ -181,20 +475,25 @@ python3 scripts/nvx.py build-guest-binaries \ --output build/nvx-rs-guest-binaries ``` +Native guest builds, the digest-pinned Docker Rust stage, and CI all call this +routine. Docker builds the set once and installs identical bytes in all three +guest images; Ubuntu determinism checks rebuild from fresh targets. + ## Testing ```bash cd nvx-rs -NVX_BUNDLE=skip cargo test --all-features # unit, golden-fixture, and fake-OpenVMM tests +NVX_BUNDLE=skip cargo test --all-features # unit, golden-fixture, parity-fixture, and fake-OpenVMM tests ``` The tests need no hypervisor. The fake-OpenVMM test drives `Instance` end to end -against `nvx-fake-openvmm`, and the protocol tests compare the wire constants -with the guest agent's source in `guest/common/nvx-managed-agent.c`. On Linux, -a loopback test drives the host `ControlSession` through a broker shim into the -real managed agent's session, dispatch, and execution engine, and `nvx.py` also -lints and tests the guest binaries, and runs their integration tests against a -build of them: +against `nvx-fake-openvmm`, the CLI test replays the +[golden scenarios](#golden-scenarios) and checks the guest-feature +requirement and, on Linux, Ctrl-C, and shared protocol definitions plus golden +byte fixtures cover both sides of every record. On Linux, a loopback test drives +the host `ControlSession` through a broker shim into the real managed agent's +session, dispatch, and execution engine, and `nvx.py` also lints and tests the +guest binaries, and runs their integration tests against a build of them: ```bash python3 ../scripts/nvx.py check-guest-binaries diff --git a/nvx-rs/src/bin/nvx-rs.rs b/nvx-rs/src/bin/nvx-rs.rs new file mode 100644 index 000000000..fa8bf161a --- /dev/null +++ b/nvx-rs/src/bin/nvx-rs.rs @@ -0,0 +1,5 @@ +//! The `nvx-rs` command-line interface; see `nvx::host::cli`. + +fn main() { + std::process::exit(nvx::host::cli::main()); +} diff --git a/nvx-rs/src/guest/reseed.rs b/nvx-rs/src/guest/reseed.rs index 877825329..1328f15d8 100644 --- a/nvx-rs/src/guest/reseed.rs +++ b/nvx-rs/src/guest/reseed.rs @@ -22,7 +22,7 @@ const SEED_BYTES: usize = 64; const GENERATION_ID_BYTES: usize = 16; /// Number of random bytes printed by `--sample`. const RANDOM_SAMPLE_BYTES: usize = 32; -/// Exact usage diagnostic inherited from the C program. +/// Exact public usage diagnostic. const USAGE: &[u8] = b"usage: nvx-reseed --sample | [--generation-only] SEED_FILE [PREVIOUS_GENERATION_ID]\n"; @@ -52,8 +52,8 @@ fn run( { return print_random_sample(out, err); } - // As in the C program, a first argument of `--generation-only` names the seed file unless - // three arguments follow the program name. + // A first argument of `--generation-only` names the seed file unless three arguments follow + // the program name. let (generation_only, seed_path, previous) = match args { [_, seed] => (false, seed, None), [_, seed, previous] => (false, seed, Some(previous)), @@ -107,8 +107,7 @@ fn reseed(random: &CStr, seed: &[u8; SEED_BYTES], err: &mut dyn Write) -> Result }) } -/// Reads exactly `bytes.len()` bytes from the file at `path`. Like the C program, it does not -/// retry after `EINTR`. +/// Reads exactly `bytes.len()` bytes from the file at `path`, without retrying after `EINTR`. fn read_seed(path: &CStr, bytes: &mut [u8], err: &mut dyn Write) -> Result<(), CommandError> { let fail = |err: &mut dyn Write, operation: &[u8], reason: &[u8]| { let path = path.to_bytes(); diff --git a/nvx-rs/src/guest/time/mod.rs b/nvx-rs/src/guest/time/mod.rs index 8ab087643..2ec1b791c 100644 --- a/nvx-rs/src/guest/time/mod.rs +++ b/nvx-rs/src/guest/time/mod.rs @@ -1,12 +1,12 @@ -//! `nvx-time`, the guest side of NVX time ABI v1, which replaces `guest/common/nvx-time.c`. +//! `nvx-time`, the guest side of NVX time ABI v1. //! //! It runs the conformance checks, the violation watcher and the wall-clock discipline in one //! daemon, and the snapshot agent's time steps: the guest preparation steps of the architecture //! guide's section 5.3.2 and the guest repair steps of its section 5.5.7, which the comments cite //! by number. Section 3.2.10 is the contract that it enforces. It prints nothing on success: //! checks record their results in the state file, and only violation events reach the console. -//! The C program defines the check IDs, the guest codes, and the formats of the output lines and -//! the state file, which this program keeps. +//! It keeps the check IDs, the guest codes, and the formats of the output lines and the state +//! file of the C program that it replaced. //! //! `nvx-time test NAME ARGUMENTS...` runs one of the C program's test hooks, which exercise its //! decoders, formats, and process structure without the hypervisor. diff --git a/nvx-rs/src/guest/time_probe/mod.rs b/nvx-rs/src/guest/time_probe/mod.rs index bf5b27750..4ca4ddc4a 100644 --- a/nvx-rs/src/guest/time_probe/mod.rs +++ b/nvx-rs/src/guest/time_probe/mod.rs @@ -1,12 +1,11 @@ -//! `nvx-time-probe`, the guest-side diagnostics for the NVX time ABI, which replaces -//! `guest/common/nvx-time-probe.c`. +//! `nvx-time-probe`, the guest-side diagnostics for the NVX time ABI. //! -//! It keeps the C program's commands, output, and exit statuses: 0 on success, 1 for a warp -//! check that fails, 2 for an invalid command line, and 3 for a measurement that could not -//! run. Its numeric options are plain decimal, or `0x`-prefixed hexadecimal for the integers, -//! and its rate options decimal floating point, as the other guest programs read their -//! arguments. It reads the kernel's numbers, such as `/proc/cpuinfo`'s rates, only in the form -//! in which the kernel writes them. +//! It keeps the commands, output, and exit statuses of the C program that it replaced: 0 on +//! success, 1 for a warp check that fails, 2 for an invalid command line, and 3 for a +//! measurement that could not run. Its numeric options are plain decimal, or `0x`-prefixed +//! hexadecimal for the integers, and its rate options decimal floating point, as the other guest +//! programs read their arguments. It reads the kernel's numbers, such as `/proc/cpuinfo`'s +//! rates, only in the form in which the kernel writes them. mod clock; mod format; diff --git a/nvx-rs/src/host/artifacts.rs b/nvx-rs/src/host/artifacts.rs index 20a9fe1a0..a23d69489 100644 --- a/nvx-rs/src/host/artifacts.rs +++ b/nvx-rs/src/host/artifacts.rs @@ -1,20 +1,43 @@ -//! Locating the OpenVMM executable and the NVX guest kernel and initramfs. +//! Locating the OpenVMM executable and the NVX guest artifacts. //! //! [`Artifacts`] names the three files that a VM runs. They come from an extracted NVX release //! archive, an NVX repository checkout, explicit paths, or a bundle that the build script staged //! with the `bundled` feature. [`Artifacts::discover`] tries these sources in a fixed order so //! consumers such as MXC need no NVX-specific configuration. +//! +//! [`ArtifactDir`] names every artifact of the guests that `nvx.py` runs, which [`Guest`] +//! describes, in a repository checkout or an extracted release: +//! +//! | Artifact | Repository layout | Release layout | +//! | --- | --- | --- | +//! | OpenVMM | `openvmm/target/release/openvmm[.exe]` | `bin/openvmm[.exe]` | +//! | Kernel | `build/vmlinux` | `guest/vmlinux` | +//! | Alpine initramfs | `build/initramfs.cpio.gz` | `guest/initramfs.cpio.gz` | +//! | Ubuntu initramfs | `build/initramfs-ubuntu.cpio.gz` | `guest/initramfs-ubuntu.cpio.gz` | +//! | Azure Linux initramfs | `build/initramfs-azurelinux.cpio.gz` | `guest/initramfs-azurelinux.cpio.gz` | +//! | Ubuntu distro layer | `build/ubuntu-distro.erofs` | `guest/ubuntu-distro.erofs` | +//! +//! Each initramfs has a package manifest beside it, `NAME.packages.json`, and the distro layer a +//! manifest, `NAME.manifest.json`. A release package holds the guest artifacts that its +//! `SOURCE-MANIFEST.json` declares, and `nvx.py download` installs them into the repository +//! layout of a checkout, with the release's OpenVMM executable. The paths and messages are those +//! of `artifact_path`, `openvmm_binary_path`, and `require_file` in +//! `scripts/nvx_tools/common.py`, so the `nvx.py` commands can look their artifacts up here. use std::env; use std::ffi::OsString; +use std::fmt; use std::fs; use std::path::{Path, PathBuf}; +use serde::Serialize; use serde_json::Value; use crate::contract; use crate::error::{Error, Result}; use crate::host::platform; +use crate::host::python::builtins::repr; +use crate::host::sandbox::require_file; /// Name of the release manifest at the root of a release archive or repository checkout. pub const MANIFEST_NAME: &str = "SOURCE-MANIFEST.json"; @@ -123,12 +146,7 @@ impl Artifacts { /// other directory is a repository checkout. pub fn from_dir(directory: impl AsRef) -> Result { let directory = directory.as_ref(); - let release_artifacts = [ - directory.join("bin").join(openvmm_executable()), - directory.join(RELEASE_KERNEL[0]).join(RELEASE_KERNEL[1]), - directory.join(RELEASE_INITRD[0]).join(RELEASE_INITRD[1]), - ]; - if release_artifacts.iter().any(|path| path.is_file()) { + if is_release_dir(directory) { Self::from_release_dir(directory) } else { Self::from_repo_layout(directory) @@ -235,6 +253,18 @@ fn discover_with( ))) } +/// Returns whether `directory` is in the release layout: whether it contains `bin/openvmm[.exe]`, +/// `guest/vmlinux`, or `guest/initramfs.cpio.gz`. +fn is_release_dir(directory: &Path) -> bool { + [ + directory.join("bin").join(openvmm_executable()), + directory.join(RELEASE_KERNEL[0]).join(RELEASE_KERNEL[1]), + directory.join(RELEASE_INITRD[0]).join(RELEASE_INITRD[1]), + ] + .iter() + .any(|path| path.is_file()) +} + /// Returns the file name of the OpenVMM executable on this host. pub fn openvmm_executable() -> &'static str { if cfg!(windows) { @@ -282,6 +312,343 @@ pub fn check_manifest(path: &Path) -> Result<()> { } } +/// File name of the guest kernel in both layouts. +pub const KERNEL_NAME: &str = "vmlinux"; +/// Suffix of the package manifest that lists the packages of an initramfs. +pub const PACKAGE_MANIFEST_SUFFIX: &str = ".packages.json"; +/// Suffix of the manifest of a distro layer, which binds the layer's digest and packages. +pub const DISTRO_MANIFEST_SUFFIX: &str = ".manifest.json"; +/// Repository-layout path of the OpenVMM build output directory. +const REPO_OPENVMM_DIR: [&str; 3] = ["openvmm", "target", "release"]; +/// Release-layout directory of the OpenVMM executable. +const RELEASE_BIN_DIR: &str = "bin"; +/// Release-layout directory of the guest artifacts. +const RELEASE_GUEST_DIR: &str = "guest"; + +/// A Linux guest that `nvx.py` builds and runs, as `scripts/nvx_tools/guests.py` describes it. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Guest { + /// Alpine Linux: the default guest of `nvx.py run`, and the one that runs sandboxes. + Alpine, + /// Ubuntu Base, which also provides the distro layer of sandbox workloads. + Ubuntu, + /// Azure Linux. + AzureLinux, +} + +impl Guest { + /// Every guest, in the order in which `nvx.py` lists them. + pub const ALL: [Self; 3] = [Self::Alpine, Self::Ubuntu, Self::AzureLinux]; + + /// Returns the guest's name in `nvx.py` options: `alpine`, `ubuntu`, or `azurelinux`. + pub const fn name(self) -> &'static str { + match self { + Self::Alpine => "alpine", + Self::Ubuntu => "ubuntu", + Self::AzureLinux => "azurelinux", + } + } + + /// Returns the guest named `name`. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) and the message of + /// `guest_descriptor` in `nvx.py`: `unsupported guest 'NAME'; choose alpine, ubuntu, + /// azurelinux`. + pub fn from_name(name: &str) -> Result { + Self::ALL + .into_iter() + .find(|guest| guest.name() == name) + .ok_or_else(|| { + let names: Vec<&str> = Self::ALL.iter().map(|guest| guest.name()).collect(); + Error::unsupported(format!( + "unsupported guest {}; choose {}", + repr(name), + names.join(", ") + )) + }) + } + + /// Returns the name of the guest's distribution, as messages show it: `Alpine Linux`, + /// `Ubuntu Base`, or `Azure Linux`. + pub const fn distribution(self) -> &'static str { + match self { + Self::Alpine => "Alpine Linux", + Self::Ubuntu => "Ubuntu Base", + Self::AzureLinux => "Azure Linux", + } + } + + /// Returns the file name of the guest's initramfs. + pub const fn initramfs_name(self) -> &'static str { + match self { + Self::Alpine => "initramfs.cpio.gz", + Self::Ubuntu => "initramfs-ubuntu.cpio.gz", + Self::AzureLinux => "initramfs-azurelinux.cpio.gz", + } + } + + /// Returns the file name of the package manifest of the guest's initramfs. + pub fn package_manifest_name(self) -> String { + format!("{}{PACKAGE_MANIFEST_SUFFIX}", self.initramfs_name()) + } + + /// Returns the file name of the guest's EROFS distro layer, `GUEST-distro.erofs`, if the + /// guest provides one: only Ubuntu does. + pub const fn distro_layer_name(self) -> Option<&'static str> { + match self { + Self::Ubuntu => Some("ubuntu-distro.erofs"), + Self::Alpine | Self::AzureLinux => None, + } + } + + /// Returns the file name of the manifest of the guest's distro layer, if it has one. + pub fn distro_manifest_name(self) -> Option { + self.distro_layer_name() + .map(|name| format!("{name}{DISTRO_MANIFEST_SUFFIX}")) + } +} + +impl fmt::Display for Guest { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.name()) + } +} + +/// The layout of a directory of NVX artifacts. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Layout { + /// A repository checkout: the guest artifacts in `build/`, and OpenVMM in + /// `openvmm/target/release/`, where a local build writes them and where `nvx.py download` + /// installs a release. + Repository, + /// An extracted release package: the guest artifacts in `guest/`, and OpenVMM in `bin/`. + Release, +} + +/// A directory of the artifacts that `nvx.py` runs: a repository checkout or an extracted +/// release, with the paths that `artifact_path` and `openvmm_binary_path` of +/// `scripts/nvx_tools/common.py` give for a checkout. +/// +/// Paths are joined to the directory, made absolute, without checking that they exist; the +/// `require_*` methods check them with the messages of `nvx.py`. Unlike [`Artifacts`], nothing +/// here reads the release's `SOURCE-MANIFEST.json`, which `nvx.py` does not check either. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ArtifactDir { + /// The absolute directory. + root: PathBuf, + /// Its layout. + layout: Layout, +} + +impl ArtifactDir { + /// Uses a repository checkout. + /// + /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) if a relative + /// `root` meets an unreadable current directory. + pub fn repository(root: impl AsRef) -> Result { + Self::new(root.as_ref(), Layout::Repository) + } + + /// Uses an extracted release package. + /// + /// Fails as [`repository`](Self::repository) does. + pub fn release(root: impl AsRef) -> Result { + Self::new(root.as_ref(), Layout::Release) + } + + /// Uses a directory in the layout that its built artifacts reveal, as + /// [`Artifacts::from_dir`] tells them apart: a directory that contains `bin/openvmm[.exe]`, + /// `guest/vmlinux`, or `guest/initramfs.cpio.gz` is a release, and any other directory is a + /// repository checkout. + /// + /// Fails as [`repository`](Self::repository) does. + pub fn from_dir(directory: impl AsRef) -> Result { + let directory = directory.as_ref(); + let layout = if is_release_dir(directory) { + Layout::Release + } else { + Layout::Repository + }; + Self::new(directory, layout) + } + + /// Returns the directory of `layout` at `root`, made absolute. + fn new(root: &Path, layout: Layout) -> Result { + Ok(Self { + root: absolute(root)?, + layout, + }) + } + + /// Returns the absolute directory. + pub fn root(&self) -> &Path { + &self.root + } + + /// Returns the directory's layout. + pub fn layout(&self) -> Layout { + self.layout + } + + /// Returns the path of the guest artifact `name`, `artifact_path(name)` of `nvx.py`: + /// `build/NAME` in a checkout, and `guest/NAME` in a release. + pub fn artifact(&self, name: &str) -> PathBuf { + let directory = match self.layout { + Layout::Repository => REPO_BUILD_DIR, + Layout::Release => RELEASE_GUEST_DIR, + }; + self.root.join(directory).join(name) + } + + /// Returns the path of the OpenVMM executable, `openvmm_binary_path()` of `nvx.py`: + /// `openvmm/target/release/openvmm[.exe]` in a checkout, and `bin/openvmm[.exe]` in a + /// release. + pub fn openvmm(&self) -> PathBuf { + let directory = match self.layout { + Layout::Repository => REPO_OPENVMM_DIR.iter().collect(), + Layout::Release => PathBuf::from(RELEASE_BIN_DIR), + }; + self.root.join(directory).join(openvmm_executable()) + } + + /// Returns the path of the guest kernel, which every guest boots. + pub fn kernel(&self) -> PathBuf { + self.artifact(KERNEL_NAME) + } + + /// Returns the path of a guest's initramfs. + pub fn initramfs(&self, guest: Guest) -> PathBuf { + self.artifact(guest.initramfs_name()) + } + + /// Returns the path of the package manifest of a guest's initramfs. + pub fn package_manifest(&self, guest: Guest) -> PathBuf { + self.artifact(&guest.package_manifest_name()) + } + + /// Returns the path of a guest's distro layer, if the guest provides one. + pub fn distro_layer(&self, guest: Guest) -> Option { + guest.distro_layer_name().map(|name| self.artifact(name)) + } + + /// Returns the path of the manifest of a guest's distro layer, if the guest provides one. + pub fn distro_manifest(&self, guest: Guest) -> Option { + guest + .distro_manifest_name() + .map(|name| self.artifact(&name)) + } + + /// Checks that the OpenVMM executable is a regular file and returns its path. + /// + /// Fails as [`require_file`] does, with the message `OpenVMM release binary not found: + /// PATH`. + pub fn require_openvmm(&self) -> Result { + required(self.openvmm(), "OpenVMM release binary") + } + + /// Checks that the guest kernel is a regular file and returns its path. + /// + /// Fails as [`require_file`] does, with the message `Linux direct kernel not found: PATH`. + pub fn require_kernel(&self) -> Result { + required(self.kernel(), "Linux direct kernel") + } + + /// Checks that a guest's initramfs is a regular file and returns its path, as `nvx.py run` + /// does before it boots the guest. + /// + /// Fails as [`require_file`] does, with the message `DISTRIBUTION initramfs not found: + /// PATH`, such as `Ubuntu Base initramfs not found: PATH`. + pub fn require_initramfs(&self, guest: Guest) -> Result { + required( + self.initramfs(guest), + &format!("{} initramfs", guest.distribution()), + ) + } + + /// Returns the artifacts of a sandbox, as `nvx.py sandbox run` and `nvx.py sandbox start` + /// require them: the OpenVMM executable, the kernel, and the Alpine initramfs, in that order. + /// + /// Fails as [`require_file`] does for the first one that is not a regular file, with the + /// messages of [`require_openvmm`](Self::require_openvmm) and + /// [`require_kernel`](Self::require_kernel), or `initramfs not found: PATH`. + pub fn sandbox_artifacts(&self) -> Result { + let openvmm = self.require_openvmm()?; + let kernel = self.require_kernel()?; + let initrd = required(self.initramfs(Guest::Alpine), "initramfs")?; + Ok(Artifacts::new(openvmm, kernel, initrd)) + } + + /// Returns the paths of every artifact, for a caller to show or record. + pub fn paths(&self) -> ArtifactPaths { + ArtifactPaths { + layout: self.layout, + root: self.root.clone(), + openvmm: self.openvmm(), + kernel: self.kernel(), + guests: Guest::ALL + .iter() + .map(|&guest| GuestPaths { + name: guest.name(), + distribution: guest.distribution(), + initramfs: self.initramfs(guest), + package_manifest: self.package_manifest(guest), + distro_layer: self.distro_layer(guest), + distro_manifest: self.distro_manifest(guest), + }) + .collect(), + } + } +} + +/// Checks that `path` is a regular file, as `require_file` of `nvx.py` does, and returns it. +fn required(path: PathBuf, description: &str) -> Result { + require_file(&path, description)?; + Ok(path) +} + +/// The paths of the artifacts of an [`ArtifactDir`], which serialize as a JSON object such as: +/// +/// ```json +/// {"layout": "repository", "root": "/nvx", "openvmm": "/nvx/openvmm/target/release/openvmm", +/// "kernel": "/nvx/build/vmlinux", "guests": [{"name": "alpine", +/// "distribution": "Alpine Linux", "initramfs": "/nvx/build/initramfs.cpio.gz", +/// "package_manifest": "/nvx/build/initramfs.cpio.gz.packages.json", "distro_layer": null, +/// "distro_manifest": null}, ...]} +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[non_exhaustive] +pub struct ArtifactPaths { + /// The directory's layout. + pub layout: Layout, + /// The absolute directory. + pub root: PathBuf, + /// The OpenVMM executable. + pub openvmm: PathBuf, + /// The guest kernel. + pub kernel: PathBuf, + /// The artifacts of each guest, in the order of [`Guest::ALL`]. + pub guests: Vec, +} + +/// The paths of the artifacts of one guest. +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[non_exhaustive] +pub struct GuestPaths { + /// The guest's name, [`Guest::name`]. + pub name: &'static str, + /// The guest's distribution, [`Guest::distribution`]. + pub distribution: &'static str, + /// The initramfs. + pub initramfs: PathBuf, + /// The package manifest of the initramfs. + pub package_manifest: PathBuf, + /// The distro layer, if the guest provides one. + pub distro_layer: Option, + /// The manifest of the distro layer, if the guest provides one. + pub distro_manifest: Option, +} + #[cfg(test)] mod tests { use std::collections::HashMap; @@ -393,6 +760,9 @@ mod tests { let artifacts = Artifacts::from_dir(root).unwrap(); let kernel = std::path::absolute(root).unwrap().join("build"); assert_eq!(artifacts.kernel, kernel.join("vmlinux")); + let directory = ArtifactDir::from_dir(root).unwrap(); + assert_eq!(directory.layout(), Layout::Repository); + assert_eq!(directory.kernel(), artifacts.kernel); } } @@ -494,6 +864,237 @@ mod tests { } } + #[test] + fn guests_name_their_artifacts_as_nvx_py_does() { + assert_eq!( + Guest::ALL.map(Guest::name), + ["alpine", "ubuntu", "azurelinux"] + ); + for guest in Guest::ALL { + assert_eq!(Guest::from_name(guest.name()).unwrap(), guest); + assert_eq!(guest.to_string(), guest.name()); + } + let error = Guest::from_name("Ubuntu").unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unsupported); + assert_eq!( + error.message(), + "unsupported guest 'Ubuntu'; choose alpine, ubuntu, azurelinux" + ); + assert_eq!( + Guest::ALL.map(Guest::distribution), + ["Alpine Linux", "Ubuntu Base", "Azure Linux"] + ); + assert_eq!( + Guest::ALL.map(Guest::package_manifest_name), + [ + "initramfs.cpio.gz.packages.json", + "initramfs-ubuntu.cpio.gz.packages.json", + "initramfs-azurelinux.cpio.gz.packages.json", + ] + ); + assert_eq!( + Guest::ALL.map(Guest::distro_layer_name), + [None, Some("ubuntu-distro.erofs"), None] + ); + assert_eq!( + Guest::Ubuntu.distro_manifest_name().as_deref(), + Some("ubuntu-distro.erofs.manifest.json") + ); + assert_eq!(Guest::Alpine.distro_manifest_name(), None); + } + + #[test] + fn a_checkout_holds_the_guest_artifacts_in_build() { + let directory = tempfile::tempdir().unwrap(); + checkout(directory.path()); + let artifacts = ArtifactDir::from_dir(directory.path()).unwrap(); + let root = std::path::absolute(directory.path()).unwrap(); + let build = root.join("build"); + assert_eq!(artifacts.layout(), Layout::Repository); + assert_eq!(artifacts.root(), root); + assert_eq!( + artifacts.openvmm(), + root.join("openvmm") + .join("target") + .join("release") + .join(openvmm_executable()) + ); + assert_eq!(artifacts.kernel(), build.join("vmlinux")); + assert_eq!( + Guest::ALL.map(|guest| artifacts.initramfs(guest)), + [ + build.join("initramfs.cpio.gz"), + build.join("initramfs-ubuntu.cpio.gz"), + build.join("initramfs-azurelinux.cpio.gz"), + ] + ); + assert_eq!( + artifacts.package_manifest(Guest::Ubuntu), + build.join("initramfs-ubuntu.cpio.gz.packages.json") + ); + assert_eq!( + Guest::ALL.map(|guest| artifacts.distro_layer(guest)), + [None, Some(build.join("ubuntu-distro.erofs")), None] + ); + assert_eq!( + artifacts.distro_manifest(Guest::Ubuntu), + Some(build.join("ubuntu-distro.erofs.manifest.json")) + ); + assert_eq!( + artifacts.artifact("vmlinux.provenance.json"), + build.join("vmlinux.provenance.json") + ); + + // A relative directory is made absolute, and an explicit layout overrides detection. + let relative = ArtifactDir::repository(".").unwrap(); + assert_eq!(relative.root(), std::path::absolute(".").unwrap()); + release(directory.path()); + assert_eq!( + ArtifactDir::repository(directory.path()).unwrap().layout(), + Layout::Repository + ); + } + + #[test] + fn a_release_holds_the_guest_artifacts_in_guest() { + let directory = tempfile::tempdir().unwrap(); + release(directory.path()); + let artifacts = ArtifactDir::from_dir(directory.path()).unwrap(); + let root = std::path::absolute(directory.path()).unwrap(); + let guest = root.join("guest"); + assert_eq!(artifacts.layout(), Layout::Release); + assert_eq!(artifacts, ArtifactDir::release(directory.path()).unwrap()); + assert_eq!( + artifacts.openvmm(), + root.join("bin").join(openvmm_executable()) + ); + assert_eq!(artifacts.kernel(), guest.join("vmlinux")); + assert_eq!( + Guest::ALL.map(|guest| artifacts.initramfs(guest)), + [ + guest.join("initramfs.cpio.gz"), + guest.join("initramfs-ubuntu.cpio.gz"), + guest.join("initramfs-azurelinux.cpio.gz"), + ] + ); + assert_eq!( + artifacts.distro_layer(Guest::Ubuntu), + Some(guest.join("ubuntu-distro.erofs")) + ); + + // The release's artifacts are the ones that discovery finds for its directory. + let discovered = Artifacts::from_dir(directory.path()).unwrap(); + assert_eq!(artifacts.sandbox_artifacts().unwrap(), discovered); + } + + #[test] + fn guest_artifacts_are_required_in_the_order_and_words_of_nvx_py() { + let directory = tempfile::tempdir().unwrap(); + checkout(directory.path()); + let artifacts = ArtifactDir::repository(directory.path()).unwrap(); + fn missing(result: Result, description: &str, path: PathBuf) { + let error = result.unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unavailable); + assert_eq!( + error.message(), + format!("{description} not found: {}", path.display()) + ); + } + let create = |path: PathBuf| { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, b"artifact").unwrap(); + }; + missing( + artifacts.sandbox_artifacts(), + "OpenVMM release binary", + artifacts.openvmm(), + ); + create(artifacts.openvmm()); + missing( + artifacts.sandbox_artifacts(), + "Linux direct kernel", + artifacts.kernel(), + ); + create(artifacts.kernel()); + missing( + artifacts.sandbox_artifacts(), + "initramfs", + artifacts.initramfs(Guest::Alpine), + ); + create(artifacts.initramfs(Guest::Alpine)); + assert_eq!( + artifacts.sandbox_artifacts().unwrap(), + Artifacts::new( + artifacts.openvmm(), + artifacts.kernel(), + artifacts.initramfs(Guest::Alpine) + ) + ); + + assert_eq!(artifacts.require_openvmm().unwrap(), artifacts.openvmm()); + assert_eq!(artifacts.require_kernel().unwrap(), artifacts.kernel()); + missing( + artifacts.require_initramfs(Guest::Ubuntu), + "Ubuntu Base initramfs", + artifacts.initramfs(Guest::Ubuntu), + ); + missing( + artifacts.require_initramfs(Guest::AzureLinux), + "Azure Linux initramfs", + artifacts.initramfs(Guest::AzureLinux), + ); + create(artifacts.initramfs(Guest::Ubuntu)); + assert_eq!( + artifacts.require_initramfs(Guest::Ubuntu).unwrap(), + artifacts.initramfs(Guest::Ubuntu) + ); + + // A directory is not a regular file. + fs::create_dir_all(artifacts.initramfs(Guest::AzureLinux)).unwrap(); + missing( + artifacts.require_initramfs(Guest::AzureLinux), + "Azure Linux initramfs", + artifacts.initramfs(Guest::AzureLinux), + ); + } + + #[test] + fn artifact_paths_serialize_every_guest_in_order() { + let directory = tempfile::tempdir().unwrap(); + release(directory.path()); + let artifacts = ArtifactDir::from_dir(directory.path()).unwrap(); + let text = |path: PathBuf| Value::from(path.to_str().unwrap()); + let value = serde_json::to_value(artifacts.paths()).unwrap(); + assert_eq!(value["layout"], "release"); + assert_eq!(value["root"], text(artifacts.root().to_path_buf())); + assert_eq!(value["openvmm"], text(artifacts.openvmm())); + assert_eq!(value["kernel"], text(artifacts.kernel())); + let guests = value["guests"].as_array().unwrap(); + assert_eq!(guests.len(), Guest::ALL.len()); + for (paths, guest) in guests.iter().zip(Guest::ALL) { + assert_eq!(paths["name"], guest.name()); + assert_eq!(paths["distribution"], guest.distribution()); + assert_eq!(paths["initramfs"], text(artifacts.initramfs(guest))); + assert_eq!( + paths["package_manifest"], + text(artifacts.package_manifest(guest)) + ); + assert_eq!( + paths["distro_layer"], + artifacts.distro_layer(guest).map_or(Value::Null, text) + ); + assert_eq!( + paths["distro_manifest"], + artifacts.distro_manifest(guest).map_or(Value::Null, text) + ); + } + let repository = ArtifactDir::repository(directory.path()).unwrap(); + assert_eq!( + serde_json::to_value(repository.paths()).unwrap()["layout"], + "repository" + ); + } + #[cfg(feature = "bundled")] #[test] fn the_bundle_is_a_compatible_release() { diff --git a/nvx-rs/src/host/cli/args.rs b/nvx-rs/src/host/cli/args.rs new file mode 100644 index 000000000..ced120406 --- /dev/null +++ b/nvx-rs/src/host/cli/args.rs @@ -0,0 +1,651 @@ +//! A command-line parser with the semantics of Python's `argparse` for the parsers of `nvx.py` +//! that `nvx-rs` replaces: long options that take one value or none, and positionals that take +//! exactly one value or at most one. +//! +//! It follows `ArgumentParser.parse_args` step by step: +//! +//! - Each argument is classified as `argparse` classifies it: the first `--` ends the options, +//! and every argument after it is a positional; a known option, or one with `=VALUE` attached, +//! is an option; an argument that does not start with `-`, a lone `-`, an argument that starts +//! like a negative number, such as `-1` or `-.5`, and one that contains a space are +//! positionals; and any other argument that starts with `-` is an unknown option. +//! - Options and positionals are consumed in `argparse`'s alternation: the positionals before +//! each option, then the option with its value, which must be the next argument and a +//! positional by that classification, or the value after `=`. A value that starts with `-` +//! therefore needs the `--option=VALUE` form. A positional takes the `--` next to its value, +//! and an optional positional that would take nothing before an option waits for the +//! arguments after it. +//! - Each value is handed to the caller as it is consumed, so conversion and choice errors +//! surface in command-line order, as `argument NAME: MESSAGE`, and end the parse. +//! - Unknown options and positionals beyond those that the parser takes are returned for the +//! caller to report last, as `unrecognized arguments: ...`, since `argparse` reports them from +//! the top-level parser. +//! - `-h` and `--help` stop the parse for the help as soon as they are consumed. +//! +//! Unlike `argparse`, it does not expand unambiguous prefixes of option names: `nvx.py` passes +//! canonical options, so `--dry` is an unknown option rather than `--dry-run`. + +use crate::host::python::builtins::repr; + +/// How a parse ends early: with a usage error, or with the help that `-h` asked for. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Stop { + /// `PROG: error: MESSAGE` after the parser's usage, with status 2. + Usage(String), + /// The help text, on standard output, with status 0. + Help, +} + +/// How many values an option or positional takes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Arity { + /// No value: a flag, such as `store_true`. + Flag, + /// Exactly one value. + One, + /// At most one value: an optional positional, `nargs='?'`. + Optional, +} + +/// An argument that the parser recognizes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Spec { + /// The option string, such as `--layer`, or the name with which messages refer to a + /// positional, such as `sandbox_operation`. + pub(crate) name: &'static str, + /// How many values it takes. + pub(crate) arity: Arity, +} + +impl Spec { + /// An option that takes no value. + pub(crate) const fn flag(name: &'static str) -> Self { + Self { + name, + arity: Arity::Flag, + } + } + + /// An option or positional that takes exactly one value. + pub(crate) const fn one(name: &'static str) -> Self { + Self { + name, + arity: Arity::One, + } + } + + /// A positional that takes at most one value. + pub(crate) const fn optional(name: &'static str) -> Self { + Self { + name, + arity: Arity::Optional, + } + } +} + +/// Receives each argument as the parser consumes it, in `argparse`'s order. +pub(crate) trait Receiver { + /// Takes `value` for the option or positional `name`: `None` for a flag, and for an optional + /// positional that received no value, which then takes its default. An error message ends + /// the parse as `argument NAME: MESSAGE`. + fn take(&mut self, name: &'static str, value: Option<&str>) -> Result<(), String>; +} + +impl) -> Result<(), String>> Receiver for F { + fn take(&mut self, name: &'static str, value: Option<&str>) -> Result<(), String> { + self(name, value) + } +} + +/// The classification of one argument. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Kind { + /// An option that the parser knows, with the byte offset of the value attached after `=`, + /// if any. + Option(usize, Option), + /// An option-like argument that the parser does not know. + Unknown, + /// A positional value. + Positional, + /// The `--` that ends the options. + End, +} + +/// A parser of options and positionals. +pub(crate) struct Parser { + /// The options, such as `--layer`, and `-h` and `--help`, which every parser has. + options: Vec, + /// The positionals, in order. + positionals: Vec, +} + +/// What an argument is to a parser, as [`Parser::token`] classifies it. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Token<'a> { + /// A known option, with the value attached after `=`, if any. + Option(&'static str, Option<&'a str>), + /// An option-like argument that the parser does not know. + Unknown, + /// A positional value. + Positional, + /// The `--` that ends the options. + End, +} + +impl Parser { + /// Returns the parser of `options` and `positionals`, which also knows `-h` and `--help`. + pub(crate) fn new(options: &[Spec], positionals: &[Spec]) -> Self { + let mut all = vec![Spec::flag("-h"), Spec::flag("--help")]; + all.extend_from_slice(options); + Self { + options: all, + positionals: positionals.to_vec(), + } + } + + /// Classifies one argument before the options end, for a caller that walks the arguments of + /// a parser whose last positional takes all the arguments after it, such as a command. + pub(crate) fn token<'a>(&self, argument: &'a str) -> Token<'a> { + if argument == "--" { + return Token::End; + } + match self.classify(argument) { + Kind::Option(option, attached) => Token::Option( + self.options[option].name, + attached.map(|offset| &argument[offset..]), + ), + Kind::Unknown => Token::Unknown, + Kind::Positional | Kind::End => Token::Positional, + } + } + + /// Returns the option whose string is `name`. + fn option(&self, name: &str) -> Option { + self.options.iter().position(|spec| spec.name == name) + } + + /// Classifies `argument` as `ArgumentParser._parse_optional` does, without abbreviations. + fn classify(&self, argument: &str) -> Kind { + if !argument.starts_with('-') { + return Kind::Positional; + } + if let Some(option) = self.option(argument) { + return Kind::Option(option, None); + } + if argument.len() == 1 { + return Kind::Positional; + } + if let Some((name, _)) = argument.split_once('=') + && let Some(option) = self.option(name) + { + return Kind::Option(option, Some(name.len() + 1)); + } + if looks_like_negative_number(argument) || argument.contains(' ') { + return Kind::Positional; + } + Kind::Unknown + } + + /// Walks `arguments` up to the command that the parser's last positional names, as + /// `argparse` walks the arguments of a parser with subparsers: the options before the + /// command go to `receiver`, unknown ones are collected, and the arguments from the command + /// on are the command's. Returns the index of the command, which `--` may precede, and the + /// unknown arguments. + pub(crate) fn split_command( + &self, + arguments: &[String], + receiver: &mut dyn Receiver, + ) -> Result<(usize, Vec), Stop> { + let command = self.positionals.last().map_or("command", |spec| spec.name); + let missing = || Stop::Usage(format!("the following arguments are required: {command}")); + let mut extras = Vec::new(); + let mut index = 0; + loop { + let argument = arguments.get(index).ok_or_else(missing)?; + match self.token(argument) { + Token::End => { + return if index + 1 < arguments.len() { + Ok((index + 1, extras)) + } else { + Err(missing()) + }; + } + Token::Positional => return Ok((index, extras)), + Token::Unknown => { + extras.push(argument.clone()); + index += 1; + } + Token::Option(name, attached) => { + let spec = self.options[self.option(name).unwrap_or_default()]; + let help = name == "-h" || name == "--help"; + let label = if help { "-h/--help" } else { name }; + let take = |receiver: &mut dyn Receiver, value: Option<&str>| { + receiver + .take(name, value) + .map_err(|message| Stop::Usage(format!("argument {label}: {message}"))) + }; + match (spec.arity, attached) { + (Arity::Flag, Some(value)) => { + return Err(Stop::Usage(format!( + "argument {label}: ignored explicit argument {}", + repr(value) + ))); + } + (Arity::Flag, None) if help => return Err(Stop::Help), + (Arity::Flag, None) => { + take(receiver, None)?; + index += 1; + } + (_, Some(value)) => { + take(receiver, Some(value))?; + index += 1; + } + (_, None) => match arguments.get(index + 1) { + Some(value) if self.token(value) == Token::Positional => { + take(receiver, Some(value))?; + index += 2; + } + _ => { + return Err(Stop::Usage(format!( + "argument {label}: expected one argument" + ))); + } + }, + } + } + } + } + } + + /// Parses `arguments`, handing each value to `receiver`, and returns the arguments that the + /// parser does not recognize, for the caller to report. + pub(crate) fn parse( + &self, + arguments: &[String], + receiver: &mut dyn Receiver, + ) -> Result, Stop> { + let mut kinds = Vec::with_capacity(arguments.len()); + let mut ended = false; + for argument in arguments { + kinds.push(if ended { + Kind::Positional + } else if argument == "--" { + ended = true; + Kind::End + } else { + self.classify(argument) + }); + } + let mut state = State { + parser: self, + arguments, + kinds: &kinds, + positionals: 0, + extras: Vec::new(), + receiver, + }; + let options: Vec = (0..kinds.len()) + .filter(|&index| matches!(kinds[index], Kind::Option(..) | Kind::Unknown)) + .collect(); + let mut start = 0; + while let Some(&next) = options.iter().find(|&&index| index >= start) { + if start != next { + let end = state.consume_positionals(start)?; + if end > start { + start = end; + continue; + } + state.extras.extend_from_slice(&arguments[start..next]); + start = next; + } + start = state.consume_option(start)?; + } + let stop = state.consume_positionals(start)?; + state.extras.extend_from_slice(&arguments[stop..]); + let missing: Vec<&str> = self.positionals[state.positionals..] + .iter() + .filter(|spec| spec.arity == Arity::One) + .map(|spec| spec.name) + .collect(); + if !missing.is_empty() { + return Err(Stop::Usage(format!( + "the following arguments are required: {}", + missing.join(", ") + ))); + } + Ok(state.extras) + } +} + +/// Returns whether `argument` starts like a negative number, as `argparse`'s +/// `_negative_number_matcher`, `-\.?\d`, finds it. +fn looks_like_negative_number(argument: &str) -> bool { + let rest = &argument.as_bytes()[1..]; + let rest = rest.strip_prefix(b".").unwrap_or(rest); + rest.first().is_some_and(u8::is_ascii_digit) +} + +/// The progress of one parse. +struct State<'a> { + /// The parser. + parser: &'a Parser, + /// The arguments. + arguments: &'a [String], + /// Their classifications. + kinds: &'a [Kind], + /// How many positionals have been consumed. + positionals: usize, + /// The arguments that the parser does not recognize. + extras: Vec, + /// The receiver of the values. + receiver: &'a mut dyn Receiver, +} + +impl State<'_> { + /// Hands `value` to the receiver, turning its error into a usage error for `name`. + fn take(&mut self, name: &'static str, value: Option<&str>) -> Result<(), Stop> { + self.receiver + .take(name, value) + .map_err(|message| Stop::Usage(format!("argument {name}: {message}"))) + } + + /// Consumes the option at `start` and its value, as `consume_optional` does, and returns the + /// index after them. + fn consume_option(&mut self, start: usize) -> Result { + let Kind::Option(option, attached) = self.kinds[start] else { + self.extras.push(self.arguments[start].clone()); + return Ok(start + 1); + }; + let spec = self.parser.options[option]; + let attached = attached.map(|offset| &self.arguments[start][offset..]); + let help = spec.name == "-h" || spec.name == "--help"; + let name = if help { "-h/--help" } else { spec.name }; + match (spec.arity, attached) { + (Arity::Flag, Some(value)) => Err(Stop::Usage(format!( + "argument {name}: ignored explicit argument {}", + repr(value) + ))), + (Arity::Flag, None) if help => Err(Stop::Help), + (Arity::Flag, None) => { + self.take(spec.name, None)?; + Ok(start + 1) + } + (_, Some(value)) => { + self.take(spec.name, Some(value))?; + Ok(start + 1) + } + (_, None) => match self.kinds.get(start + 1) { + Some(Kind::Positional) => { + self.take(spec.name, Some(&self.arguments[start + 1]))?; + Ok(start + 2) + } + _ => Err(Stop::Usage(format!( + "argument {name}: expected one argument" + ))), + }, + } + } + + /// Consumes as many positionals as the arguments from `start` provide, as + /// `consume_positionals` does, and returns the index after them. + fn consume_positionals(&mut self, mut start: usize) -> Result { + while let Some(&spec) = self.parser.positionals.get(self.positionals) { + // A positional takes the `--` before and after its value, which `argparse` drops. + let mut end = start; + if self.kinds.get(end) == Some(&Kind::End) { + end += 1; + } + let value = match self.kinds.get(end) { + Some(Kind::Positional) => { + end += 1; + Some(self.arguments[end - 1].as_str()) + } + _ if spec.arity == Arity::One => return Ok(start), + _ => None, + }; + if self.kinds.get(end) == Some(&Kind::End) { + end += 1; + } + // An optional positional that would take nothing before an option waits for the + // arguments after the option. + if end == start && matches!(self.kinds.get(end), Some(Kind::Option(..) | Kind::Unknown)) + { + return Ok(start); + } + self.positionals += 1; + self.take(spec.name, value)?; + start = end; + } + Ok(start) + } +} + +/// Returns `argparse`'s message for `value` that is not one of `choices`. +pub(crate) fn invalid_choice(value: &str, choices: &[&str]) -> String { + let listed: Vec = choices.iter().map(|choice| repr(choice)).collect(); + format!( + "invalid choice: {} (choose from {})", + repr(value), + listed.join(", ") + ) +} + +/// Returns `argparse`'s message for a value that a `type` function refused with `ValueError`, +/// such as `invalid int value: 'x'`. +pub(crate) fn invalid_value(type_name: &str, value: &str) -> String { + format!("invalid {type_name} value: {}", repr(value)) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Records what the parser hands over, refusing the value `bad`. + #[derive(Default)] + struct Recorder(Vec<(&'static str, Option)>); + + impl Receiver for Recorder { + fn take(&mut self, name: &'static str, value: Option<&str>) -> Result<(), String> { + if value == Some("bad") { + return Err(invalid_value("int", "bad")); + } + self.0.push((name, value.map(str::to_owned))); + Ok(()) + } + } + + type Events = Vec<(&'static str, Option)>; + + fn parse_with(parser: &Parser, arguments: &[&str]) -> (Result, Stop>, Events) { + let arguments: Vec = arguments.iter().map(|&a| a.to_owned()).collect(); + let mut recorder = Recorder::default(); + let result = parser.parse(&arguments, &mut recorder); + (result, recorder.0) + } + + fn parse(arguments: &[&str]) -> (Result, Stop>, Events) { + let parser = Parser::new( + &[Spec::one("--layer"), Spec::flag("--dry-run")], + &[Spec::optional("operation")], + ); + parse_with(&parser, arguments) + } + + fn taken(pairs: &[(&'static str, Option<&str>)]) -> Events { + pairs + .iter() + .map(|&(name, value)| (name, value.map(str::to_owned))) + .collect() + } + + fn strings(values: &[&str]) -> Vec { + values.iter().map(|&value| value.to_owned()).collect() + } + + fn usage(message: &str) -> Result, Stop> { + Err(Stop::Usage(message.to_owned())) + } + + #[test] + fn options_and_positionals_are_consumed_in_order() { + let (result, events) = parse(&["--layer", "a", "start", "--dry-run", "--layer=-b"]); + assert_eq!(result, Ok(Vec::new())); + assert_eq!( + events, + taken(&[ + ("--layer", Some("a")), + ("operation", Some("start")), + ("--dry-run", None), + ("--layer", Some("-b")), + ]) + ); + let (result, events) = parse(&["--dry-run"]); + assert_eq!(result, Ok(Vec::new())); + assert_eq!(events, taken(&[("--dry-run", None), ("operation", None)])); + let (_, events) = parse(&["--layer=a=b", "--layer="]); + assert_eq!( + events[..2], + taken(&[("--layer", Some("a=b")), ("--layer", Some(""))]) + ); + } + + #[test] + fn values_that_look_like_options_need_the_attached_form() { + let expected = usage("argument --layer: expected one argument"); + assert_eq!(parse(&["--layer", "-c"]).0, expected); + assert_eq!(parse(&["--layer", "--", "x"]).0, expected); + assert_eq!(parse(&["--layer"]).0, expected); + assert_eq!(parse(&["--layer", "--dry-run"]).0, expected); + for value in ["-1abc", "-.5x", "-", "-a b", ""] { + let (result, events) = parse(&["--layer", value]); + assert_eq!(result, Ok(Vec::new()), "{value:?}"); + assert_eq!(events[0], ("--layer", Some(value.to_owned()))); + } + } + + #[test] + fn unknown_arguments_are_returned_for_the_caller() { + let (result, events) = parse(&["--bogus", "run", "extra", "-x"]); + assert_eq!(result, Ok(strings(&["--bogus", "extra", "-x"]))); + assert_eq!(events, taken(&[("operation", Some("run"))])); + // A prefix of an option is unknown: options are not abbreviated. + assert_eq!(parse(&["--dry"]).0, Ok(strings(&["--dry"]))); + let (result, events) = parse(&["run", "start", "--dry-run"]); + assert_eq!(result, Ok(strings(&["start"]))); + assert_eq!( + events, + taken(&[("operation", Some("run")), ("--dry-run", None)]) + ); + } + + #[test] + fn errors_end_the_parse_where_they_occur() { + let (result, events) = parse(&["--layer", "a", "--layer", "bad", "--layer", "c"]); + assert_eq!(result, usage("argument --layer: invalid int value: 'bad'")); + assert_eq!(events, taken(&[("--layer", Some("a"))])); + assert_eq!( + parse(&["--dry-run=1"]).0, + usage("argument --dry-run: ignored explicit argument '1'") + ); + assert_eq!( + parse(&["--help=x"]).0, + usage("argument -h/--help: ignored explicit argument 'x'") + ); + assert_eq!( + parse(&["--layer", "a", "-h", "--layer", "bad"]).0, + Err(Stop::Help) + ); + assert_eq!( + parse(&["--layer", "bad", "--help"]).0, + usage("argument --layer: invalid int value: 'bad'") + ); + // An unknown option before a positional leaves the positional to its parser. + let (result, events) = parse(&["--bogus", "bad"]); + assert_eq!( + result, + usage("argument operation: invalid int value: 'bad'") + ); + assert!(events.is_empty()); + } + + #[test] + fn double_dashes_end_the_options() { + let (result, events) = parse(&["--", "--layer"]); + assert_eq!(result, Ok(Vec::new())); + assert_eq!(events, taken(&[("operation", Some("--layer"))])); + let (result, events) = parse(&["run", "--", "start"]); + assert_eq!(result, Ok(strings(&["start"]))); + assert_eq!(events, taken(&[("operation", Some("run"))])); + let (result, events) = parse(&["--layer=1", "--", "a", "--", "b"]); + assert_eq!(result, Ok(strings(&["--", "b"]))); + assert_eq!( + events, + taken(&[("--layer", Some("1")), ("operation", Some("a"))]) + ); + let (result, events) = parse(&["--dry-run", "--"]); + assert_eq!(result, Ok(Vec::new())); + assert_eq!(events, taken(&[("--dry-run", None), ("operation", None)])); + let (result, _) = parse(&["run", "--dry-run", "--", "start"]); + assert_eq!(result, Ok(strings(&["--", "start"]))); + } + + #[test] + fn required_positionals_must_be_given() { + let parser = Parser::new(&[], &[Spec::one("PATH")]); + assert_eq!( + parse_with(&parser, &[]).0, + usage("the following arguments are required: PATH") + ); + let (result, events) = parse_with(&parser, &["policy.json", "more"]); + assert_eq!(result, Ok(strings(&["more"]))); + assert_eq!(events, taken(&[("PATH", Some("policy.json"))])); + assert_eq!( + parse_with(&parser, &["-x"]).0, + usage("the following arguments are required: PATH") + ); + } + + #[test] + fn commands_split_after_the_options_before_them() { + let parser = Parser::new(&[Spec::one("--root")], &[Spec::one("command")]); + let split = |arguments: &[&str]| { + let mut recorder = Recorder::default(); + let result = parser.split_command(&strings(arguments), &mut recorder); + (result, recorder.0) + }; + let (result, events) = split(&["--root", "/r", "--x", "sandbox", "--root", "y"]); + assert_eq!(result, Ok((3, strings(&["--x"])))); + assert_eq!(events, taken(&[("--root", Some("/r"))])); + assert_eq!(split(&["--root=-r", "--", "-x"]).0, Ok((2, Vec::new()))); + assert_eq!( + split(&["--root", "-x", "sandbox"]).0, + Err(Stop::Usage( + "argument --root: expected one argument".to_owned() + )) + ); + let required = Err(Stop::Usage( + "the following arguments are required: command".to_owned(), + )); + assert_eq!(split(&["--root", "/r"]).0, required); + assert_eq!(split(&["--"]).0, required); + assert_eq!(split(&["-h", "sandbox"]).0, Err(Stop::Help)); + assert_eq!( + split(&["--root", "bad"]).0, + Err(Stop::Usage( + "argument --root: invalid int value: 'bad'".to_owned() + )) + ); + } + + #[test] + fn choices_and_values_are_described_as_argparse_describes_them() { + assert_eq!( + invalid_choice("x", &["auto", "whp"]), + "invalid choice: 'x' (choose from 'auto', 'whp')" + ); + assert_eq!( + invalid_value("positive_float", "x'y"), + "invalid positive_float value: \"x'y\"" + ); + } +} diff --git a/nvx-rs/src/host/cli/artifacts.rs b/nvx-rs/src/host/cli/artifacts.rs new file mode 100644 index 000000000..68d61cb5e --- /dev/null +++ b/nvx-rs/src/host/cli/artifacts.rs @@ -0,0 +1,179 @@ +//! `nvx-rs artifacts`: the paths of the artifacts that `nvx.py` runs, as JSON, and the artifact +//! root that every command uses. +//! +//! The root is the directory that `--root` names, in the layout that its built artifacts reveal, +//! as [`ArtifactDir::from_dir`] tells a release from a repository checkout. Without `--root`, it +//! is the release whose `bin/` holds the executable, or the repository checkout whose +//! `nvx-rs/target/` holds it, each with its `SOURCE-MANIFEST.json`. The `NVX_OPENVMM` +//! environment variable names an OpenVMM executable to run instead of the root's, such as a test +//! double. + +use std::path::{Path, PathBuf}; + +use super::args::Parser; +use super::{Exit, Result, output}; +use crate::host::artifacts::{ArtifactDir, ArtifactPaths, MANIFEST_NAME, absolute}; +use crate::host::python::builtins::Int; +use crate::host::python::dumps::{self, Value}; + +/// The environment variable that names the OpenVMM executable to run instead of the root's. +pub(crate) const OPENVMM_VARIABLE: &str = "NVX_OPENVMM"; + +/// The name of the parser in usage messages. +const PROG: &str = "nvx-rs artifacts"; +/// The usage of the parser. +const USAGE: &str = "usage: nvx-rs artifacts [-h]"; +/// The help of the parser. +const HELP: &str = "\ +usage: nvx-rs artifacts [-h] + +Print the paths of the NVX artifacts as a JSON object: the layout and root of +the artifact directory, the OpenVMM executable, the guest kernel, and for each +guest, its name and distribution, its initramfs and package manifest, and its +distro layer and manifest, or null. + +options: + -h, --help show this help message and exit"; + +/// Parses the arguments of `artifacts` and returns those that it does not recognize. +pub(super) fn parse(arguments: &[String]) -> Result> { + Parser::new(&[], &[]) + .parse(arguments, &mut |_: &'static str, _: Option<&str>| Ok(())) + .map_err(|stop| Exit::from_stop(stop, PROG, USAGE, HELP)) +} + +/// Prints the paths of the artifacts of the root. +pub(super) fn run(root: Option<&str>) -> Result { + let located = locate(root)?; + let mut paths: ArtifactPaths = located.dir.paths(); + paths.openvmm = located.openvmm; + let value = serde_json::to_value(&paths) + .map_err(|error| Exit::error(format!("cannot encode the artifact paths: {error}")))?; + output::print_stdout(&dumps::dumps(&json_value(&value))) + .map_err(|error| Exit::error(output::stream_error(&error))) +} + +/// The artifacts that a command runs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Located { + /// The artifact directory. + pub(crate) dir: ArtifactDir, + /// The OpenVMM executable: [`OPENVMM_VARIABLE`] if set, or the directory's. + pub(crate) openvmm: PathBuf, +} + +/// Returns the artifacts of `--root`, or of the directory that holds the executable. +pub(crate) fn locate(root: Option<&str>) -> Result { + let dir = match root { + Some(root) => ArtifactDir::from_dir(root)?, + None => { + let executable = std::env::current_exe().map_err(|error| { + Exit::error(format!("cannot locate the nvx-rs executable: {error}")) + })?; + default_dir(&executable).ok_or_else(|| { + Exit::error( + "cannot locate the NVX artifacts: pass --root with an NVX repository \ + checkout or extracted release, or run the nvx-rs of a release's bin \ + directory or of a checkout's nvx-rs/target directory", + ) + })?? + } + }; + let openvmm = match std::env::var_os(OPENVMM_VARIABLE).filter(|value| !value.is_empty()) { + Some(value) => { + if value.to_str().is_none() { + return Err(Exit::error(format!( + "{OPENVMM_VARIABLE} is not valid Unicode" + ))); + } + absolute(Path::new(&value))? + } + None => dir.openvmm(), + }; + Ok(Located { dir, openvmm }) +} + +/// Returns the artifact directory that holds `executable`, if any: the release whose `bin/` +/// holds it, or the repository checkout whose `nvx-rs/target/` holds it, at any depth below, as +/// for a build for another target. Either must have its `SOURCE-MANIFEST.json`. +fn default_dir(executable: &Path) -> Option> { + let directory = executable.parent()?; + let has_manifest = |root: &Path| root.join(MANIFEST_NAME).is_file(); + if directory.file_name().is_some_and(|name| name == "bin") + && let Some(root) = directory.parent() + && has_manifest(root) + { + return Some(ArtifactDir::release(root)); + } + let target = directory.ancestors().find(|ancestor| { + ancestor.file_name().is_some_and(|name| name == "target") + && ancestor + .parent() + .and_then(Path::file_name) + .is_some_and(|name| name == "nvx-rs") + })?; + let root = target.parent()?.parent()?; + has_manifest(root).then(|| ArtifactDir::repository(root)) +} + +/// Returns the [`Value`] of a JSON value without floats, as `serde_json` serializes the paths. +fn json_value(value: &serde_json::Value) -> Value { + use serde_json::Value as Json; + + match value { + Json::Null => Value::Null, + Json::Bool(value) => Value::Bool(*value), + Json::Number(number) => { + let text = number.to_string(); + Int::parse(&text).map_or(Value::Str(text), Value::Int) + } + Json::String(text) => Value::Str(text.clone()), + Json::Array(items) => Value::List(items.iter().map(json_value).collect()), + Json::Object(members) => Value::Dict( + members + .iter() + .map(|(name, member)| (name.clone(), json_value(member))) + .collect(), + ), + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use super::*; + use crate::host::artifacts::Layout; + + #[test] + fn executables_find_the_release_or_checkout_that_holds_them() { + let directory = tempfile::tempdir().unwrap(); + let root = directory.path(); + let executable = |path: &str| root.join(path).join("nvx-rs"); + // Without a manifest, nothing is recognized. + assert!(default_dir(&executable("bin")).is_none()); + fs::write(root.join(MANIFEST_NAME), "{}").unwrap(); + let release = default_dir(&executable("bin")).unwrap().unwrap(); + assert_eq!(release.layout(), Layout::Release); + assert_eq!(release.root(), absolute(root).unwrap()); + for build in [ + "nvx-rs/target/release", + "nvx-rs/target/x86_64-unknown-linux-musl/debug", + ] { + let checkout = default_dir(&executable(build)).unwrap().unwrap(); + assert_eq!(checkout.layout(), Layout::Repository, "{build}"); + assert_eq!(checkout.root(), absolute(root).unwrap(), "{build}"); + } + assert!(default_dir(&executable("target/release")).is_none()); + assert!(default_dir(&executable("elsewhere")).is_none()); + } + + #[test] + fn json_values_keep_their_shape() { + let value = serde_json::json!({"b": [null, true, "x"], "a": {}}); + assert_eq!( + dumps::dumps(&json_value(&value)), + "{\n \"a\": {},\n \"b\": [\n null,\n true,\n \"x\"\n ]\n}" + ); + } +} diff --git a/nvx-rs/src/host/cli/deadline.rs b/nvx-rs/src/host/cli/deadline.rs new file mode 100644 index 000000000..e752fad0b --- /dev/null +++ b/nvx-rs/src/host/cli/deadline.rs @@ -0,0 +1,94 @@ +//! Deadlines with Python's float seconds, as `nvx.py` computes them with `time.monotonic()`. + +use std::thread; +use std::time::{Duration, Instant}; + +use super::{Result, check_interrupt}; + +/// The longest that a wait sleeps between checks for Ctrl-C and for the condition it awaits. +pub(crate) const RETRY_INTERVAL: Duration = Duration::from_millis(25); + +/// A point in time `timeout` seconds after it was taken, or none when that is beyond what an +/// [`Instant`] holds, which then never passes. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct Deadline(Option); + +impl Deadline { + /// Returns the deadline `seconds` from now: `time.monotonic() + seconds`. + pub(crate) fn after(seconds: f64) -> Self { + Self( + Duration::try_from_secs_f64(seconds) + .ok() + .and_then(|duration| Instant::now().checked_add(duration)), + ) + } + + /// Returns the time left, which is zero once the deadline has passed, or `None` if the + /// deadline never passes. + pub(crate) fn remaining(self) -> Option { + self.0 + .map(|instant| instant.saturating_duration_since(Instant::now())) + } + + /// Returns whether the deadline has passed: `time.monotonic() >= deadline`. + pub(crate) fn passed(self) -> bool { + self.0.is_some_and(|instant| Instant::now() >= instant) + } + + /// Returns the deadline as an [`Instant`], which for a deadline that never passes is a + /// century away. + pub(crate) fn instant(self) -> Instant { + self.0.unwrap_or_else(|| { + let now = Instant::now(); + now.checked_add(Duration::from_secs(100 * 365 * 24 * 3600)) + .unwrap_or(now) + }) + } + + /// Waits for the next retry of a step that the deadline bounds, as `_pause` does: returns + /// `false` at once if the deadline has passed, and otherwise sleeps for `interval` or the + /// time left, whichever is shorter, and returns whether time is left. Fails if Ctrl-C + /// arrives. + pub(crate) fn pause(self, interval: Duration) -> Result { + let remaining = self.remaining(); + if remaining.is_some_and(|remaining| remaining.is_zero()) { + return Ok(false); + } + sleep(remaining.map_or(interval, |remaining| remaining.min(interval)))?; + Ok(!self.passed()) + } +} + +/// Sleeps for `duration`, as `time.sleep` does, failing if Ctrl-C arrives meanwhile. +pub(crate) fn sleep(duration: Duration) -> Result { + let end = Instant::now().checked_add(duration); + loop { + check_interrupt()?; + let left = end.map_or(RETRY_INTERVAL, |end| { + end.saturating_duration_since(Instant::now()) + }); + if left.is_zero() { + return Ok(()); + } + thread::sleep(left.min(RETRY_INTERVAL)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deadlines_pass_after_their_seconds() { + assert!(!Deadline::after(60.0).passed()); + assert!(Deadline::after(1e-300).passed()); + let unbounded = Deadline::after(1e300); + assert_eq!(unbounded.remaining(), None); + assert!(!unbounded.passed()); + assert!(unbounded.instant() > Instant::now()); + let short = Deadline::after(0.01); + thread::sleep(Duration::from_millis(20)); + assert!(!short.pause(RETRY_INTERVAL).unwrap()); + assert_eq!(short.remaining(), Some(Duration::ZERO)); + } +} diff --git a/nvx-rs/src/host/cli/egress.rs b/nvx-rs/src/host/cli/egress.rs new file mode 100644 index 000000000..ea53c25b7 --- /dev/null +++ b/nvx-rs/src/host/cli/egress.rs @@ -0,0 +1,87 @@ +//! `nvx-rs egress compile PATH`: compiles an egress policy file, as `nvx.py run` and `nvx.py +//! sandbox` compile their `--network-egress-policy-file`, and prints the native OpenVMM rules as +//! a JSON object, `{"allow": [...], "deny": [...]}`, with the rules of each action in the order +//! in which OpenVMM receives them. + +use std::path::Path; + +use super::args::{Parser, Spec}; +use super::{Exit, Result, args, output}; +use crate::host::network::policy::compile_policy_file; +use crate::host::python::dumps::{self, Value}; + +/// The name of the parser in usage messages. +const PROG: &str = "nvx-rs egress"; +/// The usage of the parser. +const USAGE: &str = "usage: nvx-rs egress [-h] {compile} ..."; +/// The help of the parser. +const HELP: &str = "\ +usage: nvx-rs egress [-h] {compile} ... + +Compile egress policy files. + +positional arguments: + {compile} + compile compile an egress policy file into native OpenVMM rules + +options: + -h, --help show this help message and exit"; +/// The actions, in the order in which usage messages list them. +const ACTIONS: [&str; 1] = ["compile"]; +/// The name of the `compile` parser in usage messages. +const COMPILE_PROG: &str = "nvx-rs egress compile"; +/// The usage of the `compile` parser. +const COMPILE_USAGE: &str = "usage: nvx-rs egress compile [-h] PATH"; +/// The help of the `compile` parser. +const COMPILE_HELP: &str = "\ +usage: nvx-rs egress compile [-h] PATH + +Compile the egress policy file at PATH, as --network-egress-policy-file does, +and print its native rules as a JSON object: {\"allow\": [...], \"deny\": [...]}. + +positional arguments: + PATH the egress policy file: at most 1 MiB of JSON with allow and deny + lists of rules + +options: + -h, --help show this help message and exit"; + +/// Parses the arguments of `egress`, and returns the policy file and the arguments that no +/// parser recognizes. +pub(super) fn parse(arguments: &[String]) -> Result<(String, Vec)> { + let parser = Parser::new(&[], &[Spec::one("action")]); + let (index, mut extras) = parser + .split_command(arguments, &mut |_: &'static str, _: Option<&str>| Ok(())) + .map_err(|stop| Exit::from_stop(stop, PROG, USAGE, HELP))?; + if arguments[index] != "compile" { + return Err(Exit::Usage { + usage: USAGE, + prog: PROG, + message: format!( + "argument action: {}", + args::invalid_choice(&arguments[index], &ACTIONS) + ), + }); + } + let mut path = String::new(); + let unrecognized = Parser::new(&[], &[Spec::one("PATH")]) + .parse( + &arguments[index + 1..], + &mut |_: &'static str, value: Option<&str>| { + path = value.unwrap_or_default().to_owned(); + Ok(()) + }, + ) + .map_err(|stop| Exit::from_stop(stop, COMPILE_PROG, COMPILE_USAGE, COMPILE_HELP))?; + extras.extend(unrecognized); + Ok((path, extras)) +} + +/// Compiles the policy file at `path` and prints its rules. +pub(super) fn run(path: &str) -> Result { + let policy = compile_policy_file(Path::new(path))?; + let rules = |rules: Vec| Value::List(rules.into_iter().map(Value::Str).collect()); + let value = dumps::dict([("allow", rules(policy.allow)), ("deny", rules(policy.deny))]); + output::print_stdout(&dumps::dumps(&value)) + .map_err(|error| Exit::error(output::stream_error(&error))) +} diff --git a/nvx-rs/src/host/cli/mod.rs b/nvx-rs/src/host/cli/mod.rs new file mode 100644 index 000000000..79283b1f5 --- /dev/null +++ b/nvx-rs/src/host/cli/mod.rs @@ -0,0 +1,278 @@ +//! The `nvx-rs` command-line interface, the backend of the runtime steps of `nvx.py`. +//! +//! ```text +//! nvx-rs [-h] [--root PATH] artifacts +//! nvx-rs [-h] [--root PATH] egress compile PATH +//! nvx-rs [-h] [--root PATH] sandbox [run|provision|start|exec|stop|deprovision] [OPTIONS] +//! ``` +//! +//! - `artifacts` prints the paths of the artifacts that `nvx.py` runs as JSON. +//! - `egress compile PATH` compiles an egress policy file of `--network-egress-policy-file` and +//! prints its native rules as JSON. +//! - `sandbox` is `nvx.py sandbox`: the same options, outputs, exit statuses, outcome reports, +//! and state directories. +//! +//! The artifact root is `--root`, or else the release or repository checkout that holds the +//! executable, and the `NVX_OPENVMM` environment variable names another OpenVMM executable. +//! +//! Errors print `error: MESSAGE` to standard error and exit with status 1, as `nvx.py` reports a +//! `ScriptError`. Usage errors print the usage and `PROG: error: MESSAGE`, and exit with status +//! 2, as `argparse` does. Ctrl-C prints `Interrupted` and exits with status 130. Text ends its +//! lines as Python's `print` ends them: `\r\n` on Windows, and `\n` elsewhere. + +mod args; +mod artifacts; +mod deadline; +mod egress; +mod output; +mod sandbox; +mod session; + +use std::ffi::OsString; + +use self::args::{Parser, Receiver, Spec, Stop}; +use crate::host::platform; +use crate::host::python::builtins::repr; + +/// The name of the program in usage messages. +const PROGRAM: &str = "nvx-rs"; +/// The commands, in the order in which usage messages list them. +const COMMANDS: [&str; 3] = ["artifacts", "egress", "sandbox"]; +/// The usage of the top-level parser. +const USAGE: &str = "usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ..."; +/// The help of the top-level parser. +const HELP: &str = "\ +usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ... + +Run the NVX steps that nvx.py delegates: artifact lookup, egress policy +compilation, and sandboxes. + +positional arguments: + {artifacts,egress,sandbox} + artifacts print the paths of the NVX artifacts as JSON + egress compile egress policy files + sandbox run or manage workloads over EROFS layers and private + ext4 scratch + +options: + -h, --help show this help message and exit + --root PATH the NVX repository checkout or extracted release whose + artifacts to use (default: the release or checkout that + holds this executable) + +environment: + NVX_OPENVMM the OpenVMM executable to run instead of the one of the + artifact root"; + +/// How a command ends other than by succeeding. +#[derive(Debug)] +pub(crate) enum Exit { + /// `error: MESSAGE` on standard error, and status 1: what `nvx.py` does for a `ScriptError`, + /// an `OSError`, a `RuntimeError`, or a `ValueError`. + Error(String), + /// OpenVMM closed the control endpoint, as it does when it shuts down: Python's + /// `ControlEndpointClosed`, which `nvx.py` reports as any other error. + EndpointClosed, + /// A usage error of the parser `prog`: its usage, then `PROG: error: MESSAGE`, and status 2. + Usage { + /// The parser's usage line. + usage: &'static str, + /// The parser's name, such as `nvx-rs sandbox`. + prog: &'static str, + /// The message. + message: String, + }, + /// Help on standard output, and status 0. + Help(&'static str), + /// Ctrl-C: `Interrupted` on standard error, and status 130. + Interrupted, + /// `SystemExit(status)`: the status of OpenVMM or of a managed workload. + Status(i32), +} + +/// The message of [`Exit::EndpointClosed`], `str(ControlEndpointClosed())`. +pub(crate) const ENDPOINT_CLOSED: &str = "managed control endpoint closed"; + +impl Exit { + /// Returns the error that prints `message`. + pub(crate) fn error(message: impl Into) -> Self { + Self::Error(message.into()) + } + + /// Returns the end of a parse that `stop` ended, for the parser `prog` with `usage` and + /// `help`. + pub(crate) fn from_stop( + stop: Stop, + prog: &'static str, + usage: &'static str, + help: &'static str, + ) -> Self { + match stop { + Stop::Usage(message) => Self::Usage { + usage, + prog, + message, + }, + Stop::Help => Self::Help(help), + } + } + + /// Returns the message that the end prints after `error: `, if it is an error. + pub(crate) fn message(&self) -> Option<&str> { + match self { + Self::Error(message) => Some(message), + Self::EndpointClosed => Some(ENDPOINT_CLOSED), + _ => None, + } + } + + /// Reports the end on the standard streams and returns the exit status. + fn report(self) -> i32 { + match self { + Self::Error(_) | Self::EndpointClosed => { + output::print_stderr(&format!("error: {}", self.message().unwrap_or_default())); + 1 + } + Self::Usage { + usage, + prog, + message, + } => { + output::print_stderr(&format!("{usage}\n{prog}: error: {message}")); + 2 + } + Self::Help(text) => match output::print_stdout(text) { + Ok(()) => 0, + Err(error) => Self::Error(output::stream_error(&error)).report(), + }, + Self::Interrupted => { + output::print_stderr("Interrupted"); + 130 + } + Self::Status(status) => status, + } + } +} + +impl From for Exit { + fn from(error: crate::Error) -> Self { + Self::Error(error.message().to_owned()) + } +} + +/// The result of a command. +pub(crate) type Result = std::result::Result; + +/// Fails with [`Exit::Interrupted`] if Ctrl-C arrived. +pub(crate) fn check_interrupt() -> Result { + if platform::interrupted() { + Err(Exit::Interrupted) + } else { + Ok(()) + } +} + +/// Runs `nvx-rs` with the process's arguments and returns its exit status. +/// +/// The CLI runs on the calling thread, which should be the main thread of the process, and from +/// here on, Ctrl-C ends the command that runs, with status 130, instead of the process. +pub fn main() -> i32 { + // Like Python, which installs its handler as it starts, run on without one if that fails. + let _ = platform::watch_interrupts(); + let arguments: Vec = std::env::args_os().skip(1).collect(); + let result = run(&arguments); + // Python raises KeyboardInterrupt wherever Ctrl-C finds its command, so a command that Ctrl-C + // reached ends as interrupted even if it finished meanwhile, as a run does whose OpenVMM the + // same Ctrl-C ended. + if platform::interrupted() { + return Exit::Interrupted.report(); + } + match result { + Ok(()) => 0, + Err(exit) => exit.report(), + } +} + +/// A parsed command. +enum Command { + /// `artifacts`. + Artifacts, + /// `egress compile PATH`. + EgressCompile(String), + /// `sandbox ...`. + Sandbox(Box), +} + +/// The top-level options. +#[derive(Default)] +struct TopLevel { + /// `--root`, if given. + root: Option, +} + +impl Receiver for TopLevel { + fn take( + &mut self, + _name: &'static str, + value: Option<&str>, + ) -> std::result::Result<(), String> { + self.root = value.map(str::to_owned); + Ok(()) + } +} + +/// Parses the arguments, reports the unrecognized ones, and runs the command. +fn run(arguments: &[OsString]) -> Result { + let usage = |message: String| Exit::Usage { + usage: USAGE, + prog: PROGRAM, + message, + }; + let mut strings = Vec::with_capacity(arguments.len()); + for (index, argument) in arguments.iter().enumerate() { + let text = argument.to_str().ok_or_else(|| { + usage(format!( + "argument {} is not valid Unicode: {}", + index + 1, + repr(&argument.to_string_lossy()) + )) + })?; + strings.push(text.to_owned()); + } + let parser = Parser::new(&[Spec::one("--root")], &[Spec::one("command")]); + let mut top = TopLevel::default(); + let (index, mut extras) = parser + .split_command(&strings, &mut top) + .map_err(|stop| Exit::from_stop(stop, PROGRAM, USAGE, HELP))?; + let rest = &strings[index + 1..]; + let (command, unrecognized) = match strings[index].as_str() { + "artifacts" => (Command::Artifacts, artifacts::parse(rest)?), + "egress" => { + let (path, unrecognized) = egress::parse(rest)?; + (Command::EgressCompile(path), unrecognized) + } + "sandbox" => { + let (options, unrecognized) = sandbox::parse(rest)?; + (Command::Sandbox(Box::new(options)), unrecognized) + } + other => { + return Err(usage(format!( + "argument command: {}", + args::invalid_choice(other, &COMMANDS) + ))); + } + }; + extras.extend(unrecognized); + if !extras.is_empty() { + return Err(usage(format!( + "unrecognized arguments: {}", + extras.join(" ") + ))); + } + let root = top.root.as_deref(); + match command { + Command::Artifacts => artifacts::run(root), + Command::EgressCompile(path) => egress::run(&path), + Command::Sandbox(options) => sandbox::run(root, *options), + } +} diff --git a/nvx-rs/src/host/cli/output.rs b/nvx-rs/src/host/cli/output.rs new file mode 100644 index 000000000..d698687b0 --- /dev/null +++ b/nvx-rs/src/host/cli/output.rs @@ -0,0 +1,65 @@ +//! The standard streams, written as `nvx.py` writes them. +//! +//! Python's `print` ends lines with `os.linesep`, `\r\n` on Windows and `\n` elsewhere, and +//! translates every `\n` of the text it writes, so [`print_stdout`] and [`print_stderr`] do too. +//! Workload output passes through [`write_stdout`] and [`write_stderr`] unchanged, as +//! `sys.stdout.buffer.write` passes it. + +use std::io::{self, Write}; + +use crate::host::python::fs::os_error_message; + +/// The line separator of this host, `os.linesep`. +pub(crate) const LINE_SEPARATOR: &str = if cfg!(windows) { "\r\n" } else { "\n" }; + +/// Returns `text` and a line end, with each `\n` translated to [`LINE_SEPARATOR`]. +pub(crate) fn line(text: &str) -> String { + let mut output = text.replace('\n', LINE_SEPARATOR); + output.push_str(LINE_SEPARATOR); + output +} + +/// Prints `text` and a line end to standard output, and flushes it. +pub(crate) fn print_stdout(text: &str) -> io::Result<()> { + write_stdout(line(text).as_bytes()) +} + +/// Prints `text` and a line end to standard error. Like Python, which prints errors on a stream +/// that it cannot write anyway, it ignores a failure. +pub(crate) fn print_stderr(text: &str) { + let _ = write_stderr(line(text).as_bytes()); +} + +/// Writes `data` to standard output as it is, and flushes it. +pub(crate) fn write_stdout(data: &[u8]) -> io::Result<()> { + let mut stdout = io::stdout().lock(); + stdout.write_all(data)?; + stdout.flush() +} + +/// Writes `data` to standard error as it is, and flushes it. +pub(crate) fn write_stderr(data: &[u8]) -> io::Result<()> { + let mut stderr = io::stderr().lock(); + stderr.write_all(data)?; + stderr.flush() +} + +/// Returns the message of the `OSError` that writing to a standard stream raises, such as +/// `[Errno 32] Broken pipe`. +pub(crate) fn stream_error(error: &io::Error) -> String { + os_error_message(error, None) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lines_end_as_python_prints_them() { + if cfg!(windows) { + assert_eq!(line("a\nb"), "a\r\nb\r\n"); + } else { + assert_eq!(line("a\nb"), "a\nb\n"); + } + } +} diff --git a/nvx-rs/src/host/cli/sandbox/identity.rs b/nvx-rs/src/host/cli/sandbox/identity.rs new file mode 100644 index 000000000..01b6a3f72 --- /dev/null +++ b/nvx-rs/src/host/cli/sandbox/identity.rs @@ -0,0 +1,138 @@ +//! The identity of the OpenVMM process of a running sandbox, as `sandbox_lifecycle.py` records and +//! checks it: `_process_start_time` and `_process_running`. +//! +//! A runtime record names OpenVMM by its process ID and start time, which together never repeat: +//! on Linux the `starttime` field of `/proc/PID/stat`, in clock ticks since boot, and on Windows +//! the creation time of the process as a 64-bit `FILETIME`. Both implementations compute the same +//! value, so a record that either wrote identifies the process for the other. + +use crate::host::cli::{Exit, Result}; +use crate::host::python::builtins::Int; +#[cfg(any(target_os = "linux", windows))] +use crate::host::python::fs::os_error_message; + +/// Returns the start time of a live process, or `None` if no live process has the ID, as +/// `_process_start_time` does, or the message of the error with which the state of the process +/// cannot be determined. +/// +/// A zombie counts as exited. On Windows, so does a process that the caller cannot open: OpenVMM +/// runs as the caller, so such a process cannot be its VM. Other hosts have no `/proc`, so every +/// process counts as exited there, as in Python. +pub(crate) fn process_start_time(pid: &Int) -> std::result::Result, String> { + #[cfg(target_os = "linux")] + { + let path = format!("/proc/{pid}/stat"); + let stat = match std::fs::read(&path) { + Ok(stat) => stat, + Err(error) + if error.kind() == std::io::ErrorKind::NotFound + || error.raw_os_error() == Some(nix::errno::Errno::ESRCH as i32) => + { + return Ok(None); + } + Err(error) => return Err(os_error_message(&error, Some(&path))), + }; + linux_start_time(&stat) + } + #[cfg(windows)] + { + let Some(pid) = pid.to_u32() else { + return Ok(None); + }; + crate::host::platform::process_start_time(pid) + .map_err(|error| os_error_message(&error, None)) + } + #[cfg(not(any(target_os = "linux", windows)))] + { + let _ = pid; + Ok(None) + } +} + +/// Reads the start time from the `/proc/PID/stat` line of a process, as `_linux_start_time` +/// does: the command name may contain spaces and parentheses, so the fields after it start at +/// its last `)`, with the state as field 3 and `starttime` as field 22. +#[cfg(any(target_os = "linux", test))] +fn linux_start_time(stat: &[u8]) -> std::result::Result, String> { + let unexpected = || "process status has an unexpected format".to_owned(); + let fields: Vec<&[u8]> = match stat.iter().rposition(|&byte| byte == b')') { + Some(end) => stat[end + 1..] + .split(|byte| matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c)) + .filter(|field| !field.is_empty()) + .collect(), + None => Vec::new(), + }; + let start = fields.get(19).copied().unwrap_or_default(); + if fields.len() < 20 || start.is_empty() || !start.iter().all(u8::is_ascii_digit) { + return Err(unexpected()); + } + if matches!(fields[0], b"Z" | b"X") { + return Ok(None); + } + std::str::from_utf8(start) + .ok() + .and_then(|start| start.parse().ok()) + .map(Some) + .ok_or_else(unexpected) +} + +/// Returns whether the OpenVMM process that a runtime record names still runs, as +/// `_process_running` does. A record without a start time, written by an earlier NVX version, +/// identifies OpenVMM by its process ID alone. +pub(crate) fn process_running(pid: &Int, start_time: Option<&Int>) -> Result { + if !pid.is_positive() { + return Ok(false); + } + let current = process_start_time(pid).map_err(|message| { + Exit::error(format!( + "cannot determine whether OpenVMM process {pid} is running: {message}" + )) + })?; + Ok(match start_time { + None => current.is_some(), + Some(start_time) => current.map(Int::from).as_ref() == Some(start_time), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stat_lines_give_the_start_time_of_live_processes() { + let line = |state: &str| { + format!( + "123 (my (odd) prog) {state} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 4242 19 20\n" + ) + }; + assert_eq!(linux_start_time(line("S").as_bytes()), Ok(Some(4242))); + assert_eq!(linux_start_time(line("R").as_bytes()), Ok(Some(4242))); + assert_eq!(linux_start_time(line("Z").as_bytes()), Ok(None)); + assert_eq!(linux_start_time(line("X").as_bytes()), Ok(None)); + for malformed in [ + &b"123 (prog) S 1 2"[..], + b"no parenthesis", + b"1 (p) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 x", + ] { + assert_eq!( + linux_start_time(malformed), + Err("process status has an unexpected format".to_owned()) + ); + } + } + + #[test] + fn this_process_runs_and_others_are_identified_by_start_time() { + let pid = Int::from(std::process::id()); + let start_time = process_start_time(&pid).unwrap(); + if cfg!(any(target_os = "linux", windows)) { + let start_time = Int::from(start_time.expect("this process runs")); + assert!(process_running(&pid, Some(&start_time)).unwrap()); + assert!(!process_running(&pid, Some(&start_time.plus_one())).unwrap()); + assert!(process_running(&pid, None).unwrap()); + } + assert!(!process_running(&Int::zero(), None).unwrap()); + assert!(!process_running(&Int::parse("-5").unwrap(), None).unwrap()); + assert!(!process_running(&Int::parse("99999999999999999999").unwrap(), None).unwrap()); + } +} diff --git a/nvx-rs/src/host/cli/sandbox/lifecycle.rs b/nvx-rs/src/host/cli/sandbox/lifecycle.rs new file mode 100644 index 000000000..b75b18755 --- /dev/null +++ b/nvx-rs/src/host/cli/sandbox/lifecycle.rs @@ -0,0 +1,1005 @@ +//! The lifecycle of a managed sandbox, a port of `scripts/nvx_tools/sandbox_lifecycle.py`. +//! +//! A sandbox lives in its state directory: +//! +//! ```text +//! / +//! config.json the launch that provision records, in formats 1 to 6 +//! lifecycle.lock the lock that serializes provision, start, stop, and deprovision +//! runtime.json format 1: the pid, start_time, and control_endpoint of OpenVMM +//! control.capability the 32-byte capability of the running OpenVMM +//! control.sock the control endpoint of the running OpenVMM, on POSIX +//! openvmm.log OpenVMM's output, appended by each start +//! outcome.json OpenVMM's outcome report of the latest start +//! ``` +//! +//! [`provision`], [`start`], [`stop`], and [`deprovision`] hold the [`LifecycleLock`] for their +//! whole transition; [`exec_workload`] does not, so a long workload cannot delay a stop. Every +//! state file is written as `sandbox_lifecycle.py` writes it, and a state directory that either +//! implementation wrote serves the other. + +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::time::Duration; + +use super::Artifacts; +use super::identity::{process_running, process_start_time}; +use super::lock::{ + LifecycleLock, OsErrorClass, directory_not_empty, directory_status, entries, lingers, + os_error_class, +}; +use super::state::{ + CONFIG_FORMATS, STATE_FORMAT, config_int, config_text, deserialize_launch, hex, member, + random_bytes, read_json, serialize_launch, temporary_path, uuid4_hex, write_json, +}; +use crate::host::cli::deadline::{Deadline, RETRY_INTERVAL, sleep}; +use crate::host::cli::session::{EXIT_CATEGORIES, ExecRequest, ExecResult, Session}; +use crate::host::cli::{Exit, Result}; +use crate::host::process::{self, Launched}; +use crate::host::python::builtins::{Int, repr}; +use crate::host::python::dumps::{self, Value, dict}; +use crate::host::python::fs::{self, os_error_message}; +use crate::host::python::json::{Document, Node}; +use crate::host::python::path::PurePath; +use crate::host::python::value::py_str; +use crate::host::sandbox::{SandboxLaunch, require_file}; +use crate::protocol::{CAPABILITY_LEN, GuestFeatures}; + +/// The launch configuration. +pub(crate) const CONFIG_NAME: &str = "config.json"; +/// The record of the running OpenVMM process. +pub(crate) const RUNTIME_NAME: &str = "runtime.json"; +/// The control capability of the running OpenVMM process. +pub(crate) const CAPABILITY_NAME: &str = "control.capability"; +/// OpenVMM's output. +pub(crate) const LOG_NAME: &str = "openvmm.log"; +/// The control endpoint on POSIX. +pub(crate) const CONTROL_SOCKET_NAME: &str = "control.sock"; +/// OpenVMM's outcome report. +pub(crate) const OUTCOME_NAME: &str = "outcome.json"; +/// The lifecycle lock. +pub(crate) const LOCK_NAME: &str = "lifecycle.lock"; +/// The files that exist only from the start that launches an OpenVMM process until the stop or +/// failed start that ends it, whether or not the runtime record names it. +pub(crate) const RUNTIME_FILE_NAMES: [&str; 3] = + [RUNTIME_NAME, CAPABILITY_NAME, CONTROL_SOCKET_NAME]; +/// How often a caller retries a lifecycle lock that another caller holds, and the removal of a +/// state directory that a removed lock file still keeps. +pub(crate) const LOCK_RETRY_INTERVAL: Duration = RETRY_INTERVAL; +/// What deprovision writes into the lock file before it removes the file, so that a waiter that +/// then takes the lock of the removed file knows that the directory goes away. +pub(crate) const LOCK_TOMBSTONE: &[u8] = b"deprovisioned\n"; +/// The version of outcome reports. +pub(crate) const OUTCOME_SCHEMA_VERSION: u64 = 1; + +/// The options of a launch, besides the launch itself, that `provision` records. +pub(crate) struct ProvisionConfig<'a> { + /// `--hypervisor`, resolved. + pub(crate) hypervisor: &'a str, + /// `--memory-mib`. + pub(crate) memory_mib: &'a Int, + /// `--net`. + pub(crate) net: Option<&'a str>, + /// `--network-profile`. + pub(crate) network_profile: Option<&'a str>, + /// `--network-egress`. + pub(crate) network_egress: Option<&'a str>, + /// `--network-ingress`. + pub(crate) network_ingress: Option<&'a str>, + /// The egress allow rules. + pub(crate) network_egress_allow: &'a [String], + /// The egress deny rules. + pub(crate) network_egress_deny: &'a [String], + /// `--host-loopback`. + pub(crate) host_loopback: Option<&'a str>, + /// `--network-proxy`. + pub(crate) network_proxy: Option<&'a str>, + /// The `--host-loopback-forward` options. + pub(crate) host_loopback_forward: &'a [String], + /// `--cmdline`. + pub(crate) cmdline: &'a str, +} + +/// Returns the error for an `OSError` that an operation on `path` raised. +fn os_error(error: &std::io::Error, path: &PurePath) -> Exit { + Exit::error(os_error_message(error, Some(path.as_str()))) +} + +/// Removes `path` unless it is missing, as `Path.unlink(missing_ok=True)` does. +fn unlink_missing_ok(path: &PurePath) -> Result { + match std::fs::remove_file(path.as_path()) { + Err(error) if os_error_class(&error) != OsErrorClass::NotFound => { + Err(os_error(&error, path)) + } + _ => Ok(()), + } +} + +/// Returns a builder of directories of `mode`, which Windows ignores, as `os.mkdir` does. +fn dir_builder(mode: u32) -> std::fs::DirBuilder { + #[cfg_attr(not(unix), allow(unused_mut))] + let mut builder = std::fs::DirBuilder::new(); + #[cfg(unix)] + std::os::unix::fs::DirBuilderExt::mode(&mut builder, mode); + #[cfg(not(unix))] + let _ = mode; + builder +} + +/// Creates `path` and its missing parents, as `Path.mkdir(mode, parents=True, exist_ok=True)` +/// does: parents with the default mode, `path` with `mode`, and no error if `path` is a +/// directory already. +fn make_directories(path: &PurePath, mode: u32) -> Result { + match dir_builder(mode).create(path.as_path()) { + Ok(()) => Ok(()), + Err(error) if os_error_class(&error) == OsErrorClass::NotFound => { + match path.parents().next() { + Some(parent) if parent != *path => { + make_directories(&parent, 0o777)?; + make_directories_once(path, mode) + } + _ => Err(os_error(&error, path)), + } + } + Err(_) if fs::is_dir(path.as_path()) => Ok(()), + Err(error) => Err(os_error(&error, path)), + } +} + +/// Creates `path` once its parent exists, as `self.mkdir(mode, parents=False, exist_ok=True)` +/// does. +fn make_directories_once(path: &PurePath, mode: u32) -> Result { + match dir_builder(mode).create(path.as_path()) { + Ok(()) => Ok(()), + Err(_) if fs::is_dir(path.as_path()) => Ok(()), + Err(error) => Err(os_error(&error, path)), + } +} + +/// Gives a directory mode 0700, as `os.chmod(path, 0o700)` does: on Windows, that only clears the +/// read-only attribute. +fn make_private(path: &PurePath) -> Result { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + std::fs::set_permissions(path.as_path(), std::fs::Permissions::from_mode(0o700)) + .map_err(|error| os_error(&error, path)) + } + #[cfg(not(unix))] + { + let metadata = std::fs::metadata(path.as_path()).map_err(|error| os_error(&error, path))?; + let mut permissions = metadata.permissions(); + if permissions.readonly() { + #[allow(clippy::permissions_set_readonly_false)] + permissions.set_readonly(false); + std::fs::set_permissions(path.as_path(), permissions) + .map_err(|error| os_error(&error, path))?; + } + Ok(()) + } +} + +/// Resolves the state directory at `path`, and creates it with `create`, as +/// `_prepare_state_directory` does. It must be a directory and not a symbolic link. +pub(crate) fn prepare_state_directory(path: &str, create: bool) -> Result { + let resolved = fs::resolve(&PurePath::native(path)) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + if create { + make_directories(&resolved, 0o700)?; + make_private(&resolved)?; + } + if fs::is_symlink(resolved.as_path()) || !fs::is_dir(resolved.as_path()) { + return Err(Exit::error(format!( + "sandbox state path is not a plain directory: {resolved}" + ))); + } + Ok(resolved) +} + +/// Returns the destination of an outcome report, as `_outcome_destination` does: in a plain +/// directory, with a file name, and new. +fn outcome_destination(path: &str) -> Result { + let path = PurePath::native(path); + let candidate = if path.is_absolute() { + path + } else { + fs::cwd() + .map_err(|error| Exit::error(os_error_message(&error, None)))? + .join(path.as_str()) + }; + let parent = candidate + .parents() + .next() + .unwrap_or_else(|| candidate.clone()); + if fs::is_symlink(parent.as_path()) || !fs::is_dir(parent.as_path()) { + return Err(Exit::error(format!( + "outcome report parent is not a plain directory: {parent}" + ))); + } + if candidate.name().is_empty() { + return Err(Exit::error("outcome report path has no filename")); + } + let resolved = fs::resolve(&parent) + .map_err(|error| Exit::error(os_error_message(&error, None)))? + .join(candidate.name()); + if fs::lexists(resolved.as_path()) { + return Err(Exit::error(format!( + "outcome report already exists: {resolved}" + ))); + } + Ok(resolved) +} + +/// Checks the destination of an outcome report before a workload runs, as +/// `validate_outcome_destination` does. +pub(crate) fn validate_outcome_destination(path: &str) -> Result { + outcome_destination(path).map(drop) +} + +/// Writes `value` to a new file at `path`, as `_write_new_json` does: through a temporary file of +/// mode 0600, synced, then linked to its name, which must not exist, with `\n` line ends on every +/// host. +fn write_new_json(path: &str, value: &Value) -> Result { + let resolved = outcome_destination(path)?; + let temporary = temporary_path(&resolved)?; + let mut options = OpenOptions::new(); + options.write(true).create_new(true); + #[cfg(unix)] + std::os::unix::fs::OpenOptionsExt::mode(&mut options, 0o600); + let mut file = match options.open(temporary.as_path()) { + Ok(file) => file, + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => { + return Err(Exit::error( + "failed to reserve an outcome report staging file", + )); + } + Err(error) => return Err(os_error(&error, &temporary)), + }; + let written = (|| -> Result { + let text = format!("{}\n", dumps::dumps(value)); + file.write_all(text.as_bytes()) + .and_then(|()| file.sync_all()) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + match std::fs::hard_link(temporary.as_path(), resolved.as_path()) { + Ok(()) => Ok(()), + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => Err(Exit::error( + format!("outcome report already exists: {resolved}"), + )), + Err(_) => Err(Exit::error(format!( + "failed to publish outcome report: {resolved}" + ))), + } + })(); + drop(file); + let _ = std::fs::remove_file(temporary.as_path()); + written +} + +/// The sections of an OpenVMM outcome report that a reader requires. +const OUTCOME_SECTIONS: [&str; 3] = ["outcome", "network_policy", "teardown"]; + +/// Reads an OpenVMM outcome report, as `_read_openvmm_outcome` does: of schema version 1, with +/// `outcome`, `network_policy`, and `teardown` objects. +fn read_openvmm_outcome(path: &PurePath) -> Result { + let document = read_json( + path, + "OpenVMM outcome report", + "schema_version", + &[OUTCOME_SCHEMA_VERSION], + )?; + for name in OUTCOME_SECTIONS { + let section = member(&document, document.root(), name); + if !section.is_some_and(|section| matches!(document.node(section), Node::Object(_))) { + return Err(Exit::error(format!( + "OpenVMM outcome report has an invalid {name} section: {path}" + ))); + } + } + Ok(document) +} + +/// Writes the outcome report of a managed workload, as `write_exec_outcome` does: its category +/// and status, with a random operation ID, and nothing of its output. +pub(crate) fn write_exec_outcome(path: &str, result: &ExecResult) -> Result { + if !EXIT_CATEGORIES.contains(&result.category) { + return Err(Exit::error( + "managed workload returned an unsupported outcome category", + )); + } + let operation_id = hex(&random_bytes::<16>()?); + write_new_json( + path, + &dict([ + ("schema_version", Value::from(OUTCOME_SCHEMA_VERSION)), + ("operation_id", Value::from(operation_id)), + ( + "outcome", + dict([ + ("operation", Value::from("exec")), + ("category", Value::from(result.category)), + ("status_code", Value::from(i64::from(result.returncode))), + ]), + ), + ]), + ) +} + +/// Records a launch in a new state directory, as `provision` does. +pub(crate) fn provision( + state_path: &str, + launch: &SandboxLaunch, + config: &ProvisionConfig<'_>, + timeout: f64, +) -> Result { + let state_dir = prepare_state_directory(state_path, true)?; + let config_path = state_dir.join(CONFIG_NAME); + let runtime_path = state_dir.join(RUNTIME_NAME); + let _lock = LifecycleLock::acquire(&state_dir, timeout, true)?; + if fs::exists(config_path.as_path()) || fs::exists(runtime_path.as_path()) { + return Err(Exit::error("sandbox is already provisioned")); + } + launch.validated()?; + write_json(&config_path, &serialize_launch(launch, config)?) +} + +/// The pid and start time that a runtime record names, as `_runtime_process` reads them. +struct RuntimeProcess { + /// The process ID, of any size. + pid: Int, + /// The start time, if the record has one. + start_time: Option, +} + +/// Reads the pid and start time of a runtime record, as `_runtime_process` does. +fn runtime_process(document: &Document) -> Result { + let invalid_pid = || Exit::error("sandbox runtime state has an invalid process ID"); + let root = document.root(); + let pid = member(document, root, "pid").ok_or_else(invalid_pid)?; + let pid = crate::host::python::value::py_int(document, pid).map_err(|error| match error { + crate::host::python::value::IntError::Invalid => invalid_pid(), + crate::host::python::value::IntError::Overflow => { + Exit::error("cannot convert float infinity to integer") + } + })?; + let start_time = match member(document, root, "start_time").map(|id| document.node(id)) { + None | Some(Node::Null) => None, + Some(Node::Integer(literal)) => { + let value = Int::parse(literal).map_err(|_| invalid_start_time())?; + if value.is_negative() { + return Err(invalid_start_time()); + } + Some(value) + } + Some(_) => return Err(invalid_start_time()), + }; + Ok(RuntimeProcess { pid, start_time }) +} + +/// Returns the error of a runtime record with an invalid start time. +fn invalid_start_time() -> Exit { + Exit::error("sandbox runtime state has an invalid process start time") +} + +/// A running sandbox, as `_load_running` finds it. +struct Running { + /// The runtime record. + runtime: Document, + /// The control capability. + capability: [u8; CAPABILITY_LEN], +} + +/// Reads the runtime record and capability of a running sandbox, as `_load_running` does. +fn load_running(state_dir: &PurePath) -> Result { + let runtime_path = state_dir.join(RUNTIME_NAME); + if !fs::is_file(runtime_path.as_path()) { + return Err(Exit::error("sandbox is not running")); + } + let runtime = read_json( + &runtime_path, + "sandbox runtime state", + "format", + &[STATE_FORMAT], + )?; + let process = runtime_process(&runtime)?; + if !process_running(&process.pid, process.start_time.as_ref())? { + return Err(Exit::error( + "sandbox runtime state is stale because the OpenVMM process is not running", + )); + } + let capability_path = state_dir.join(CAPABILITY_NAME); + require_file(capability_path.as_path(), "sandbox control capability")?; + let bytes = std::fs::read(capability_path.as_path()) + .map_err(|error| os_error(&error, &capability_path))?; + let capability: [u8; CAPABILITY_LEN] = bytes + .try_into() + .ok() + .filter(|capability| *capability != [0; CAPABILITY_LEN]) + .ok_or_else(|| Exit::error("sandbox control capability is invalid"))?; + Ok(Running { + runtime, + capability, + }) +} + +/// Returns the control endpoint of a runtime record, as `_endpoint` does. +fn endpoint(runtime: &Document) -> Result { + let value = member(runtime, runtime.root(), "control_endpoint") + .ok_or_else(|| Exit::error("sandbox runtime state has no control endpoint"))?; + py_str(runtime, value) + .ok_or_else(|| Exit::error("sandbox runtime state has no control endpoint")) +} + +/// Connects to the running sandbox of `running`. +fn connect(running: &Running, timeout: f64) -> Result { + let endpoint = endpoint(&running.runtime)?; + let process = runtime_process(&running.runtime)?; + // A running process has an ID of 32 bits. + let pid = process.pid.to_u32().unwrap_or_default(); + Session::connect(&endpoint, pid, &running.capability, timeout) +} + +/// Starts a provisioned sandbox, as `start` does. +pub(crate) fn start(state_path: &str, artifacts: &Artifacts<'_>, timeout: f64) -> Result { + let state_dir = prepare_state_directory(state_path, false)?; + let _lock = LifecycleLock::acquire(&state_dir, timeout, false)?; + start_locked(&state_dir, artifacts, timeout) +} + +/// The control features that the layered sandbox root must provide for the requests of +/// `nvx-rs sandbox`. +pub(crate) const REQUIRED_FEATURES: GuestFeatures = GuestFeatures::LAYERED_SANDBOX; + +/// Starts the sandbox of a locked state directory, as `_start` does. +fn start_locked(state_dir: &PurePath, artifacts: &Artifacts<'_>, timeout: f64) -> Result { + let config_path = state_dir.join(CONFIG_NAME); + require_file(config_path.as_path(), "sandbox configuration")?; + let config = read_json( + &config_path, + "sandbox configuration", + "format", + &CONFIG_FORMATS, + )?; + // Any runtime file belongs to an OpenVMM process that may still run, even without a runtime + // record, as after a start that was killed. Without them, every runtime file that appears + // while this start holds the lifecycle lock is its own. + if RUNTIME_FILE_NAMES + .iter() + .any(|name| fs::lexists(state_dir.join(name).as_path())) + { + return Err(Exit::error( + "sandbox is already running or has stale runtime state", + )); + } + let outcome_path = state_dir.join(OUTCOME_NAME); + unlink_missing_ok(&outcome_path)?; + let launch = deserialize_launch(&config)?; + let [executable, kernel, initrd] = artifacts.require()?; + let capability = random_bytes::()?; + if capability == [0; CAPABILITY_LEN] { + return Err(Exit::error( + "the system returned an all-zero control capability", + )); + } + let endpoint = if cfg!(windows) { + format!("//./pipe/openvmm-microvm-{}", uuid4_hex()?) + } else { + state_dir + .join(super::lifecycle::CONTROL_SOCKET_NAME) + .as_str() + .to_owned() + }; + let mut command = vec![executable]; + command.extend(launch.openvmm_arguments()?); + command.extend( + [ + "--microvm-lifecycle", + "managed", + "--single-process", + "--hypervisor", + ] + .map(str::to_owned), + ); + command.push(config_text(&config, "hypervisor")?); + command.push("--memory".to_owned()); + command.push(format!("{}M", config_int(&config, "memory_mib")?)); + command.extend(["--kernel".to_owned(), kernel, "--initrd".to_owned(), initrd]); + command.push("--cmdline".to_owned()); + command.push(launch.kernel_command_line(&config_text(&config, "cmdline")?)?); + command.extend(["--virtio-console", "none", "--microvm-control-console"].map(str::to_owned)); + command.push(format!("listen={endpoint}")); + command.push("--microvm-control-auth-stdin".to_owned()); + command.push("--microvm-report".to_owned()); + command.push(outcome_path.as_str().to_owned()); + extend_recorded_network_arguments(&mut command, &config)?; + + let capability_path = state_dir.join(CAPABILITY_NAME); + create_capability(&capability_path, &capability)?; + let log_path = state_dir.join(LOG_NAME); + let log = match OpenOptions::new() + .append(true) + .create(true) + .open(log_path.as_path()) + { + Ok(log) => log, + Err(error) => { + let _ = unlink_missing_ok(&capability_path); + return Err(os_error(&error, &log_path)); + } + }; + let mut launched: Option = None; + let started = launch_and_connect( + &command, + &capability, + log, + &endpoint, + state_dir, + timeout, + &mut launched, + ); + let Err(error) = started else { + if let Some(launched) = launched { + process::detach_child(launched); + } + return Ok(()); + }; + let mut status = None; + let mut ended = launched.is_none(); + if let Some(launched) = launched.as_mut() { + // OpenVMM closes the control endpoint as it tears the VM down, before it publishes its + // outcome report and exits, so only a closed endpoint warrants waiting for it. + let grace = if matches!(error, Exit::EndpointClosed) { + timeout + } else { + 0.0 + }; + match end_failed_start(launched, grace, &command) { + Ok(exited) => { + status = exited; + ended = true; + } + Err(exit) => return Err(exit), + } + } + if ended { + // This start found none of these files and has held the lifecycle lock since, so they are + // its own. The runtime record goes last, so that an interrupted cleanup leaves a record + // that shows OpenVMM gone rather than runtime files that deprovision cannot vouch for. + unlink_missing_ok(&state_dir.join(CONTROL_SOCKET_NAME))?; + unlink_missing_ok(&capability_path)?; + unlink_missing_ok(&state_dir.join(RUNTIME_NAME))?; + } + match status { + Some(status) if !matches!(error, Exit::Interrupted) => { + Err(Exit::error(startup_exit_message(&outcome_path, status)?)) + } + _ => Err(error), + } +} + +/// Launches OpenVMM, records it, and waits until its guest is ready and provides the features of +/// [`REQUIRED_FEATURES`], leaving the launched process in `launched` for the caller to end if +/// that fails. +fn launch_and_connect( + command: &[String], + capability: &[u8; CAPABILITY_LEN], + log: File, + endpoint: &str, + state_dir: &PurePath, + timeout: f64, + launched: &mut Option, +) -> Result { + let arguments: Vec = command[1..].iter().map(Into::into).collect(); + let directory = + std::env::current_dir().map_err(|error| Exit::error(os_error_message(&error, None)))?; + let child = process::spawn( + std::path::Path::new(&command[0]), + false, + &arguments, + capability, + log, + &directory, + ) + .map_err(|error| Exit::error(super::spawn_error(&error, &command[0])))?; + let child = launched.insert(child); + // Until this process reaps the child or closes its handle, no other process can reuse its ID, + // so this identifies OpenVMM itself. + let pid = child.id(); + let start_time = process_start_time(&Int::from(pid)) + .map_err(|error| Exit::error(format!("cannot identify the OpenVMM process: {error}")))?; + let Some(start_time) = start_time else { + let exited = child + .wait_timeout(Duration::ZERO) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + return Err(Exit::error(if exited.is_some() { + "OpenVMM exited during startup" + } else { + "cannot identify the OpenVMM process" + })); + }; + write_json( + &state_dir.join(RUNTIME_NAME), + &dict([ + ("format", Value::from(STATE_FORMAT)), + ("pid", Value::from(pid)), + ("start_time", Value::from(start_time)), + ("control_endpoint", Value::from(endpoint)), + ]), + )?; + let mut session = Session::connect(endpoint, pid, capability, timeout)?; + session.ping(timeout)?; + let features = session.features(timeout)?; + let missing = features.missing(REQUIRED_FEATURES); + if !missing.is_empty() { + return Err(Exit::error(format!( + "the guest image lacks control features that layered sandboxes need ({}); use an \ + NVX release or build that includes them", + missing.join(", ") + ))); + } + Ok(()) +} + +/// Appends the network options that a configuration records, in the order of `_start`. +fn extend_recorded_network_arguments(command: &mut Vec, config: &Document) -> Result { + let root = config.root(); + let text = + |id| py_str(config, id).ok_or_else(|| Exit::error("sandbox configuration is malformed")); + let present = + |name| member(config, root, name).filter(|&id| !matches!(config.node(id), Node::Null)); + if let Some(net) = present("net") { + command.push("--net".to_owned()); + command.push(text(net)?); + command.push("--network-profile".to_owned()); + command.push(match member(config, root, "network_profile") { + Some(profile) => text(profile)?, + None => "None".to_owned(), + }); + } + for name in ["network_egress", "network_ingress", "host_loopback"] { + if let Some(value) = present(name) { + command.push(format!("--{}", name.replace('_', "-"))); + command.push(text(value)?); + } + } + for name in [ + "network_egress_allow", + "network_egress_deny", + "host_loopback_forward", + ] { + let Some(values) = member(config, root, name) else { + continue; + }; + let Node::Array(items) = config.node(values) else { + return Err(Exit::error("sandbox configuration is malformed")); + }; + for &item in items { + command.push(format!("--{}", name.replace('_', "-"))); + command.push(text(item)?); + } + } + if let Some(proxy) = present("network_proxy") { + command.push("--network-proxy".to_owned()); + command.push(text(proxy)?); + } + Ok(()) +} + +/// Creates the control capability file of a start, which only it may hold, as +/// `_create_capability` does. +fn create_capability(path: &PurePath, capability: &[u8; CAPABILITY_LEN]) -> Result { + let mut options = OpenOptions::new(); + options.write(true).create_new(true); + #[cfg(unix)] + std::os::unix::fs::OpenOptionsExt::mode(&mut options, 0o600); + let mut file = match options.open(path.as_path()) { + Ok(file) => file, + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => { + return Err(Exit::error( + "sandbox is already running or has stale runtime state", + )); + } + Err(error) => return Err(os_error(&error, path)), + }; + if let Err(error) = file.write_all(capability) { + drop(file); + let _ = unlink_missing_ok(path); + return Err(os_error(&error, path)); + } + Ok(()) +} + +/// Ends the OpenVMM process of a failed start, as `_end_failed_start` does: returns OpenVMM's +/// status if it exits on its own within `grace` seconds, or terminates it and returns `None`. +/// Terminating OpenVMM while it publishes its outcome report would leave the report's staging +/// file in the state directory, which deprovision refuses to remove. +fn end_failed_start( + launched: &mut Launched, + grace: f64, + command: &[String], +) -> Result> { + let waited = if grace > 0.0 { + wait_interruptibly(launched, Deadline::after(grace)) + } else { + Ok(()) + }; + let status = launched + .wait_timeout(Duration::ZERO) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + if status.is_none() { + launched + .terminate() + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + let exited = launched + .wait_timeout(Duration::from_secs(5)) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + if exited.is_none() { + launched + .kill() + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + let killed = launched + .wait_timeout(Duration::from_secs(5)) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + if killed.is_none() { + let arguments: Vec = + command.iter().map(|argument| repr(argument)).collect(); + return Err(Exit::error(format!( + "Command '[{}]' timed out after 5 seconds", + arguments.join(", ") + ))); + } + } + } + // Ctrl-C during the grace period ends the start as interrupted, after OpenVMM. + waited?; + Ok(status) +} + +/// Waits until `launched` exits or `deadline` passes, failing if Ctrl-C arrives. +fn wait_interruptibly(launched: &mut Launched, deadline: Deadline) -> Result { + loop { + let slice = deadline + .remaining() + .map_or(RETRY_INTERVAL, |left| left.min(RETRY_INTERVAL)); + crate::host::cli::check_interrupt()?; + if launched + .wait_timeout(slice) + .map_err(|error| Exit::error(os_error_message(&error, None)))? + .is_some() + || deadline.passed() + { + return Ok(()); + } + } +} + +/// Describes how OpenVMM ended a start, preferring its outcome report, which keeps the guest's +/// own status, as `_startup_exit_message` does. +fn startup_exit_message(outcome_path: &PurePath, status: i64) -> Result { + let report = match read_openvmm_outcome(outcome_path) { + Ok(report) => Some(report), + // A `ValueError` of `json.loads` escapes the `except ScriptError`. + Err(exit) + if exit + .message() + .is_some_and(|message| message.starts_with("Exceeds the limit")) => + { + return Err(exit); + } + Err(_) => None, + }; + if let Some(report) = &report + && let Some(outcome) = member(report, report.root(), "outcome") + && let Some(category) = member(report, outcome, "category") + && let Some(status_code) = member(report, outcome, "status_code") + && let Node::String(category) = report.node(category) + && let Node::Integer(status_code) = report.node(status_code) + { + let status_code = Int::parse(status_code).map_err(|_| Exit::error("invalid status"))?; + return Ok(format!( + "OpenVMM exited during startup: {} status {status_code}", + category.render() + )); + } + Ok(format!( + "OpenVMM exited during startup with status {status}" + )) +} + +/// Runs a workload in a running sandbox, as `exec_workload` does. +pub(crate) fn exec_workload( + state_path: &str, + request: &ExecRequest, + response_timeout: f64, +) -> Result { + let state_dir = prepare_state_directory(state_path, false)?; + let running = load_running(&state_dir)?; + let mut session = connect(&running, response_timeout)?; + session.exec(request, response_timeout) +} + +/// Stops a running sandbox and returns OpenVMM's outcome report, as `stop` does. +pub(crate) fn stop(state_path: &str, timeout: f64) -> Result { + let state_dir = prepare_state_directory(state_path, false)?; + let _lock = LifecycleLock::acquire(&state_dir, timeout, false)?; + let running = load_running(&state_dir)?; + let process = runtime_process(&running.runtime)?; + { + let mut session = connect(&running, timeout)?; + session.stop(timeout)?; + } + let deadline = Deadline::after(timeout); + while process_running(&process.pid, process.start_time.as_ref())? { + if deadline.passed() { + return Err(Exit::error("OpenVMM did not terminate after managed stop")); + } + sleep(RETRY_INTERVAL)?; + } + let outcome = read_openvmm_outcome(&state_dir.join(OUTCOME_NAME)); + // As in a failed start, the runtime record goes last. + unlink_missing_ok(&state_dir.join(CONTROL_SOCKET_NAME))?; + unlink_missing_ok(&state_dir.join(CAPABILITY_NAME))?; + unlink_missing_ok(&state_dir.join(RUNTIME_NAME))?; + outcome +} + +/// Removes a stopped sandbox and its state directory, as `deprovision` does. +pub(crate) fn deprovision(state_path: &str, timeout: f64) -> Result { + let state_dir = prepare_state_directory(state_path, false)?; + let mut lock = LifecycleLock::acquire(&state_dir, timeout, false)?; + let runtime_path = state_dir.join(RUNTIME_NAME); + if fs::is_file(runtime_path.as_path()) { + let runtime = read_json( + &runtime_path, + "sandbox runtime state", + "format", + &[STATE_FORMAT], + )?; + let process = runtime_process(&runtime)?; + if process_running(&process.pid, process.start_time.as_ref())? { + return Err(Exit::error("sandbox must be stopped before deprovision")); + } + } else if RUNTIME_FILE_NAMES + .iter() + .any(|name| fs::lexists(state_dir.join(name).as_path())) + { + // As start does, refuse runtime files that no record vouches for. + return Err(Exit::error(format!( + "sandbox has runtime files but no runtime record, so its OpenVMM process may still \ + run: end any OpenVMM process whose arguments name the state directory, then remove \ + {CAPABILITY_NAME} and {CONTROL_SOCKET_NAME}" + ))); + } + for name in [ + RUNTIME_NAME, + CAPABILITY_NAME, + CONTROL_SOCKET_NAME, + OUTCOME_NAME, + LOG_NAME, + CONFIG_NAME, + ] { + unlink_missing_ok(&state_dir.join(name))?; + } + let unknown: Vec = entries(&state_dir) + .map_err(|error| os_error(&error, &state_dir))? + .into_iter() + .filter(|name| name != LOCK_NAME) + .collect(); + if !unknown.is_empty() { + // `iterdir` lists the names in the order of the directory, which the message keeps. + return Err(Exit::error(format!( + "sandbox state directory contains files not owned by NVX: {}", + unknown.join(", ") + ))); + } + // Holding the lock until the directory is gone keeps a waiting transition from creating a new + // lock file in it. + lock.remove()?; + let lingering = match std::fs::remove_dir(state_dir.as_path()) { + Ok(()) => false, + Err(error) if directory_not_empty(&error) => true, + Err(error) => { + lock.retract(); + return Err(os_error(&error, &state_dir)); + } + }; + if !lingering { + lock.retract(); + return Ok(()); + } + // A removed lock file that a handle still keeps stays in the directory, as do the lock files of + // transitions that start meanwhile. Each waiter closes its handle once it finds the sandbox + // gone, so the directory empties after this call releases the lock. + let (removing, held) = hold_directory(&state_dir)?; + lock.release(); + let removed = remove_emptied_state_directory(&state_dir, removing, timeout); + drop(held); + removed +} + +/// Returns the identity of a directory, and a handle that keeps it, as `_hold_directory` does: on +/// POSIX, the open directory keeps a directory that replaces it from reusing its inode number; +/// NTFS and ReFS do not reuse file IDs. +fn hold_directory(path: &PurePath) -> Result<((u64, u128), Option)> { + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + + let file = OpenOptions::new() + .read(true) + .custom_flags((nix::fcntl::OFlag::O_DIRECTORY | nix::fcntl::OFlag::O_NOFOLLOW).bits()) + .open(path.as_path()) + .map_err(|error| os_error(&error, path))?; + let status = + fs::fstat(&file).map_err(|error| Exit::error(os_error_message(&error, None)))?; + Ok((status.identity, Some(file))) + } + #[cfg(not(unix))] + { + let status = fs::lstat(path.as_path()).map_err(|error| os_error(&error, path))?; + Ok((status.identity(), None)) + } +} + +/// Removes a state directory once no lingering lock file keeps it, as +/// `_remove_emptied_state_directory` does. A provision that waits for the removal may complete it +/// and provision the path again, which ends this attempt too. +fn remove_emptied_state_directory( + state_dir: &PurePath, + removing: (u64, u128), + timeout: f64, +) -> Result { + let deadline = Deadline::after(timeout); + let mut retried_at_once = false; + loop { + let error = match std::fs::remove_dir(state_dir.as_path()) { + Ok(()) => return Ok(()), + Err(error) if os_error_class(&error) == OsErrorClass::NotFound => return Ok(()), + Err(error) => error, + }; + if directory_status(state_dir) != Some(removing) { + return Ok(()); + } + let remaining = match entries(state_dir) { + Ok(remaining) => remaining, + Err(error) if os_error_class(&error) == OsErrorClass::NotFound => return Ok(()), + Err(error) => return Err(os_error(&error, state_dir)), + }; + let mut unexpected: Vec = remaining + .iter() + .filter(|name| !lingers(name)) + .cloned() + .collect(); + unexpected.sort(); + // A provision that starts after this deprovision removed the lock file, and before it + // removed the directory, provisions it again. + if unexpected.iter().any(|name| name == CONFIG_NAME) { + return Err(Exit::error( + "sandbox was provisioned again while deprovision removed it", + )); + } + if !unexpected.is_empty() { + return Err(Exit::error(format!( + "sandbox state directory contains files not owned by NVX: {}", + unexpected.join(", ") + ))); + } + if !directory_not_empty(&error) { + return Err(os_error(&error, state_dir)); + } + // Without remaining entries, the last lingering lock file went away after rmdir failed, + // so the next attempt follows at once. Another such failure right after it pauses first, + // so that a file system that does not report emptiness right away cannot spin. + if remaining.is_empty() && !retried_at_once { + retried_at_once = true; + if deadline.passed() { + return Err(os_error(&error, state_dir)); + } + continue; + } + retried_at_once = false; + if !deadline.pause(LOCK_RETRY_INTERVAL)? { + return Err(os_error(&error, state_dir)); + } + } +} diff --git a/nvx-rs/src/host/cli/sandbox/lock.rs b/nvx-rs/src/host/cli/sandbox/lock.rs new file mode 100644 index 000000000..c72b2b385 --- /dev/null +++ b/nvx-rs/src/host/cli/sandbox/lock.rs @@ -0,0 +1,505 @@ +//! The lifecycle lock of a state directory, `_LifecycleLock` of `sandbox_lifecycle.py`, with the +//! file primitives that it shares with Python, so that both implementations exclude each other: +//! +//! - `lifecycle.lock` is opened or created without following a link that it names: with +//! `O_NOFOLLOW` on POSIX, and on Windows with `FILE_FLAG_OPEN_REPARSE_POINT` and every sharing +//! mode, so that deprovision can delete it while others wait for it. It must be a regular file +//! with one link, and not a reparse point. +//! - The lock is `flock(LOCK_EX | LOCK_NB)` on POSIX, and on Windows the lock of the file's first +//! byte, as `msvcrt.locking(fd, LK_NBLCK, 1)` takes it at offset 0. A caller that cannot take +//! it retries every 25 ms within its timeout. +//! - Deprovision writes `deprovisioned\n` into the lock file before it removes the file, so that +//! a waiter that then takes the lock of the removed file knows that the directory goes away. +//! +//! [`LifecycleLock::acquire`] and [`LifecycleLock::release`] follow `__enter__` and `release` +//! step by step; see the comments of `_LifecycleLock` for the protocol. + +use std::fs::{File, OpenOptions}; +use std::io::{self, Read, Seek, SeekFrom, Write}; + +use super::lifecycle::{ + CONFIG_NAME, LOCK_NAME, LOCK_RETRY_INTERVAL, LOCK_TOMBSTONE, prepare_state_directory, +}; +use crate::host::cli::deadline::Deadline; +use crate::host::cli::{Exit, Result}; +use crate::host::python::fs::{self, os_error_message}; +use crate::host::python::path::PurePath; + +/// How Python classifies an `OSError`, as far as the lifecycle tells the classes apart. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum OsErrorClass { + /// `FileNotFoundError`: `ENOENT`. + NotFound, + /// `PermissionError`: `EACCES` or `EPERM`. + PermissionDenied, + /// Any other `OSError`. + Other, +} + +/// Returns the class of the `OSError` that Python raises for `error`. On Windows, Python maps the +/// Win32 error to an `errno` as `winerror_to_errno` does. +pub(crate) fn os_error_class(error: &io::Error) -> OsErrorClass { + #[cfg(windows)] + if let Some(code) = error.raw_os_error() { + return match code { + 2 | 3 | 15 | 18 | 53 | 67 | 161 | 206 => OsErrorClass::NotFound, + 5 | 16 | 19..=36 | 65 | 82 | 83 | 108 | 132 | 158 | 167 => { + OsErrorClass::PermissionDenied + } + _ => OsErrorClass::Other, + }; + } + match error.kind() { + io::ErrorKind::NotFound => OsErrorClass::NotFound, + io::ErrorKind::PermissionDenied => OsErrorClass::PermissionDenied, + _ => OsErrorClass::Other, + } +} + +/// Returns whether `error` is `ELOOP`, with which `O_NOFOLLOW` refuses a symbolic link. +fn is_loop(error: &io::Error) -> bool { + #[cfg(unix)] + return error.raw_os_error() == Some(nix::errno::Errno::ELOOP as i32); + #[cfg(not(unix))] + { + let _ = error; + false + } +} + +/// Returns the error of a lock file that is not a regular file with one name. +fn not_standalone(path: &PurePath) -> Exit { + Exit::error(format!( + "sandbox lifecycle lock is not a standalone regular file: {path}" + )) +} + +/// Returns the message of an `OSError` that opening the lock file at `path` raised: with the +/// file's name from `os.open` on POSIX, and without it from `ctypes.WinError` on Windows. +fn open_error(error: &io::Error, path: &PurePath) -> Exit { + let path = if cfg!(windows) { + None + } else { + Some(path.as_str()) + }; + Exit::error(os_error_message(error, path)) +} + +/// Opens or creates `path` without following a link that it names, as `_open_lock_path` does. +fn open_lock_path(path: &PurePath) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true).write(true).create(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + + options + .mode(0o600) + .custom_flags(nix::fcntl::OFlag::O_NOFOLLOW.bits()); + } + #[cfg(windows)] + { + use std::os::windows::fs::OpenOptionsExt; + + /// `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE`. + const SHARE_ALL: u32 = 0x7; + /// `FILE_ATTRIBUTE_NORMAL`. + const NORMAL: u32 = 0x80; + /// `FILE_FLAG_OPEN_REPARSE_POINT`. + const OPEN_REPARSE_POINT: u32 = 0x0020_0000; + options + .share_mode(SHARE_ALL) + .attributes(NORMAL) + .custom_flags(OPEN_REPARSE_POINT); + } + options.open(path.as_path()) +} + +/// Why opening the lock file failed. +enum OpenFailure { + /// An `OSError` that the caller may handle. + Os(io::Error), + /// Any other failure. + Exit(Exit), +} + +/// Opens the lock file of a state directory, creating it if needed, as `_open_lock_file` does: +/// it must be a regular file that no other name links, and not a reparse point. +fn open_lock_file(path: &PurePath) -> std::result::Result { + let file = match open_lock_path(path) { + Ok(file) => file, + Err(error) if is_loop(&error) => return Err(OpenFailure::Exit(not_standalone(path))), + Err(error) => return Err(OpenFailure::Os(error)), + }; + let status = fs::fstat(&file) + .map_err(|error| OpenFailure::Exit(Exit::error(os_error_message(&error, None))))?; + if !status.regular || status.links != 1 || status.reparse_point { + return Err(OpenFailure::Exit(not_standalone(path))); + } + Ok(file) +} + +/// Takes the exclusive lock of an open lock file unless another caller holds it, and returns +/// whether it took it, as `_try_lock_file` does. +fn try_lock_file(file: &File) -> Result { + #[cfg(windows)] + return Ok(crate::host::platform::try_lock_first_byte(file)); + #[cfg(not(windows))] + match file.try_lock() { + Ok(()) => Ok(true), + Err(std::fs::TryLockError::WouldBlock) => Ok(false), + Err(std::fs::TryLockError::Error(error)) => { + Err(Exit::error(os_error_message(&error, None))) + } + } +} + +/// Releases the lock of an open lock file, as `_unlock_file` does. +fn unlock_file(file: &File) -> io::Result<()> { + #[cfg(windows)] + return crate::host::platform::unlock_first_byte(file); + #[cfg(not(windows))] + file.unlock() +} + +/// Returns whether `path` still names the open `file`, as `_names_open_file` does. Windows keeps +/// a removed file in its directory until its last handle closes, and denies access to it +/// meanwhile, which counts as another file. +fn names_open_file(path: &PurePath, file: &File) -> io::Result { + let current = match fs::stat(path.as_path()) { + Ok(current) => current, + Err(error) => { + return match os_error_class(&error) { + OsErrorClass::NotFound => Ok(false), + OsErrorClass::PermissionDenied if cfg!(windows) => Ok(false), + _ => Err(error), + }; + } + }; + Ok(current.identity() == fs::fstat(file)?.identity) +} + +/// Reads up to `size` bytes from the start of the lock file, as `_read_lock_file` does. +fn read_lock_file(mut file: &File, size: usize) -> io::Result> { + file.seek(SeekFrom::Start(0))?; + let mut data = Vec::with_capacity(size); + let read = (&mut file).take(size as u64).read_to_end(&mut data); + file.seek(SeekFrom::Start(0))?; + read.map(|_| data) +} + +/// Writes `data` over the start of the lock file, as `_write_lock_file` does. +fn write_lock_file(mut file: &File, data: &[u8]) -> io::Result<()> { + file.seek(SeekFrom::Start(0))?; + let mut remaining = data; + let written = (|| { + while !remaining.is_empty() { + let count = file.write(remaining)?; + if count == 0 { + return Err(io::Error::other( + "sandbox lifecycle lock write made no progress", + )); + } + remaining = &remaining[count..]; + } + Ok(()) + })(); + // `msvcrt.locking()` unlocks from the file position, as it locked. + file.seek(SeekFrom::Start(0))?; + written +} + +/// Returns the error of a transition that still waits for another: Python's `TimeoutError`. +pub(crate) fn transition_in_progress() -> Exit { + Exit::error("another lifecycle transition of the sandbox is still in progress") +} + +/// Returns `os.lstat(path)`'s identity, or `None` if the path cannot be examined, as +/// `_directory_status` does. +pub(crate) fn directory_status(path: &PurePath) -> Option<(u64, u128)> { + fs::lstat(path.as_path()) + .ok() + .map(|status| status.identity()) +} + +/// Returns whether `name` is a removed lock file that a handle still keeps, as `_lingers` does: +/// NFS renames a removed file that is still open to `.nfs*`, and Windows file systems without +/// POSIX deletion keep it under its own name. +pub(crate) fn lingers(name: &str) -> bool { + name == LOCK_NAME || name.starts_with(".nfs") +} + +/// Returns whether `error` is how `rmdir` reports a directory that is not empty. +pub(crate) fn directory_not_empty(error: &io::Error) -> bool { + #[cfg(windows)] + let not_empty = matches!(error.raw_os_error(), Some(145 | 80 | 183)); + #[cfg(unix)] + let not_empty = matches!( + error.raw_os_error(), + Some(code) if code == nix::errno::Errno::ENOTEMPTY as i32 + || code == nix::errno::Errno::EEXIST as i32 + ); + #[cfg(not(any(windows, unix)))] + let not_empty = error.kind() == io::ErrorKind::DirectoryNotEmpty; + not_empty +} + +/// Returns the names of the entries of `directory`. +pub(crate) fn entries(directory: &PurePath) -> io::Result> { + std::fs::read_dir(directory.as_path())? + .map(|entry| { + let name = entry?.file_name(); + name.into_string() + .map_err(|_| crate::host::python::not_unicode("a file name")) + }) + .collect() +} + +/// Waits while lingering lock files keep the directory that deprovision removes, as +/// `_wait_for_handoff` does. The wait ends once the path names no directory or another one, or +/// the directory holds anything else; a directory that nothing keeps any longer is removed here, +/// in case deprovision has given up. +fn wait_for_handoff(state_dir: &PurePath, removing: (u64, u128), deadline: Deadline) -> Result { + loop { + if directory_status(state_dir) != Some(removing) { + return Ok(()); + } + let Ok(names) = entries(state_dir) else { + return Ok(()); + }; + if !names.iter().all(|name| lingers(name)) { + return Ok(()); + } + if names.is_empty() { + match std::fs::remove_dir(state_dir.as_path()) { + Ok(()) => return Ok(()), + Err(error) if !directory_not_empty(&error) => return Ok(()), + Err(_) => {} + } + } + if !deadline.pause(LOCK_RETRY_INTERVAL)? { + return Err(transition_in_progress()); + } + } +} + +/// The exclusive lock that serializes the lifecycle transitions of a state directory. +pub(crate) struct LifecycleLock { + /// The state directory. + state_dir: PurePath, + /// Its lock file. + path: PurePath, + /// The locked file, until the lock is released. + file: Option, +} + +impl LifecycleLock { + /// Takes the lock of `state_dir` within `timeout` seconds, as `_LifecycleLock(state_dir, + /// timeout, provision=provision).__enter__()` does. + /// + /// A caller that takes the lock of a lock file that its holder removed meanwhile holds no + /// lock. Unless it provisions, it retries only while the sandbox is still provisioned, and a + /// provision that finds the lock file or the directory removed waits until the old directory + /// is gone, then provisions a new one, unless another provision has done so first. + pub(crate) fn acquire(state_dir: &PurePath, timeout: f64, provision: bool) -> Result { + if !(timeout > 0.0 && timeout < f64::INFINITY) { + return Err(Exit::error( + "lifecycle timeout must be finite and greater than 0", + )); + } + let path = state_dir.join(LOCK_NAME); + let deadline = Deadline::after(timeout); + loop { + let file = match open_lock_file(&path) { + Ok(file) => file, + Err(OpenFailure::Exit(exit)) => return Err(exit), + Err(OpenFailure::Os(error)) => match os_error_class(&error) { + // Deprovision removed the state directory meanwhile, which only a provision + // creates again. + OsErrorClass::NotFound => { + if !provision { + return Err(Exit::error(format!( + "sandbox state path is not a plain directory: {state_dir}" + ))); + } + if deadline.passed() { + return Err(transition_in_progress()); + } + prepare_state_directory(state_dir.as_str(), true)?; + continue; + } + // Without POSIX deletion, Windows denies opening a removed lock file until + // its last handle closes. + OsErrorClass::PermissionDenied + if provision && cfg!(windows) && deadline.pause(LOCK_RETRY_INTERVAL)? => + { + continue; + } + _ => return Err(open_error(&error, &path)), + }, + }; + while !try_lock_file(&file)? { + if !deadline.pause(LOCK_RETRY_INTERVAL)? { + return Err(transition_in_progress()); + } + } + if names_open_file(&path, &file) + .map_err(|error| Exit::error(os_error_message(&error, Some(path.as_str()))))? + { + return Ok(Self { + state_dir: state_dir.clone(), + path, + file: Some(file), + }); + } + let tombstone = read_lock_file(&file, LOCK_TOMBSTONE.len()) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + // Deprovision leaves the mark only if lingering lock files, such as the one that this + // handle may keep, kept it from removing the directory, which the path then still + // names. + let removing = if provision && tombstone == LOCK_TOMBSTONE { + directory_status(state_dir) + } else { + None + }; + drop(file); + if !provision { + if !fs::lexists(state_dir.join(CONFIG_NAME).as_path()) { + return Err(Exit::error("sandbox is not provisioned")); + } + } else if let Some(removing) = removing { + wait_for_handoff(state_dir, removing, deadline)?; + } + if deadline.passed() { + return Err(transition_in_progress()); + } + } + } + + /// Removes the held lock file, marking it so that waiters know why, as `remove` does. If the + /// removal fails, the mark goes again, as deprovision then keeps the directory. + pub(crate) fn remove(&mut self) -> Result { + let file = self.file.as_ref().expect("the lock is held"); + let removed = write_lock_file(file, LOCK_TOMBSTONE) + .map_err(|error| Exit::error(os_error_message(&error, None))) + .and_then(|()| { + std::fs::remove_file(self.path.as_path()).map_err(|error| { + Exit::error(os_error_message(&error, Some(self.path.as_str()))) + }) + }); + if removed.is_err() { + self.retract(); + } + removed + } + + /// Clears the mark of the removed lock file, so that waiters retry at once, as `retract` + /// does. + pub(crate) fn retract(&mut self) { + if let Some(file) = &self.file { + let _ = write_lock_file(file, &[0; LOCK_TOMBSTONE.len()]); + } + } + + /// Releases the lock, as `release` does: the lock file of a directory that holds no + /// provisioned sandbox goes with it. + pub(crate) fn release(&mut self) { + let Some(file) = self.file.take() else { + return; + }; + if !fs::lexists(self.state_dir.join(CONFIG_NAME).as_path()) + && names_open_file(&self.path, &file).unwrap_or(false) + { + let _ = std::fs::remove_file(self.path.as_path()); + } + // Closing the file releases the lock even if the unlock fails. + let _ = unlock_file(&file); + } +} + +impl Drop for LifecycleLock { + fn drop(&mut self) { + self.release(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn state_dir(directory: &tempfile::TempDir) -> PurePath { + PurePath::native(directory.path().to_str().unwrap()) + } + + #[test] + fn the_lock_excludes_other_holders_until_released() { + let directory = tempfile::tempdir().unwrap(); + let state = state_dir(&directory); + std::fs::write(state.join(CONFIG_NAME).as_path(), "{}").unwrap(); + let mut held = LifecycleLock::acquire(&state, 10.0, false).unwrap(); + let started = std::time::Instant::now(); + let error = LifecycleLock::acquire(&state, 0.1, false).err().unwrap(); + assert!(started.elapsed() >= std::time::Duration::from_millis(100)); + assert_eq!( + error.message(), + Some("another lifecycle transition of the sandbox is still in progress") + ); + held.release(); + drop(LifecycleLock::acquire(&state, 10.0, false).unwrap()); + // A provisioned directory keeps its lock file. + assert!(state.join(LOCK_NAME).as_path().is_file()); + } + + #[test] + fn released_locks_leave_no_file_in_unprovisioned_directories() { + let directory = tempfile::tempdir().unwrap(); + let state = state_dir(&directory); + drop(LifecycleLock::acquire(&state, 10.0, true).unwrap()); + assert!(!state.join(LOCK_NAME).as_path().exists()); + let error = LifecycleLock::acquire(&state.join("missing"), 1.0, false) + .err() + .unwrap(); + assert_eq!( + error.message().unwrap(), + format!( + "sandbox state path is not a plain directory: {}", + state.join("missing") + ) + ); + } + + #[test] + fn linked_lock_files_are_refused() { + let directory = tempfile::tempdir().unwrap(); + let state = state_dir(&directory); + let other = state.join("other"); + std::fs::write(other.as_path(), "").unwrap(); + if std::fs::hard_link(other.as_path(), state.join(LOCK_NAME).as_path()).is_err() { + return; + } + let error = LifecycleLock::acquire(&state, 1.0, true).err().unwrap(); + assert_eq!( + error.message().unwrap(), + format!( + "sandbox lifecycle lock is not a standalone regular file: {}", + state.join(LOCK_NAME) + ) + ); + } + + #[test] + fn deprovision_marks_the_removed_lock_file() { + let directory = tempfile::tempdir().unwrap(); + let state = state_dir(&directory); + let mut lock = LifecycleLock::acquire(&state, 10.0, true).unwrap(); + let file = lock.file.as_ref().unwrap().try_clone().unwrap(); + lock.remove().unwrap(); + assert_eq!(read_lock_file(&file, 64).unwrap(), LOCK_TOMBSTONE); + lock.retract(); + assert_eq!( + read_lock_file(&file, 64).unwrap(), + vec![0; LOCK_TOMBSTONE.len()] + ); + assert!(!state.join(LOCK_NAME).as_path().exists()); + } +} diff --git a/nvx-rs/src/host/cli/sandbox/mod.rs b/nvx-rs/src/host/cli/sandbox/mod.rs new file mode 100644 index 000000000..09bfa2080 --- /dev/null +++ b/nvx-rs/src/host/cli/sandbox/mod.rs @@ -0,0 +1,811 @@ +//! `nvx-rs sandbox`: `nvx.py sandbox`, ported from `command_sandbox` and the `sandbox` parser of +//! `scripts/nvx.py`, with its options, defaults, checks, outputs, and exit statuses. +//! +//! - `run`, the default, boots a one-shot sandbox: it prints the OpenVMM command as `>> COMMAND`, +//! then runs it on the terminal, unless `--dry-run`, and exits with OpenVMM's status. +//! - `provision`, `start`, `exec`, `stop`, and `deprovision` manage a persistent sandbox in its +//! `--state-dir`; see [`lifecycle`]. +//! +//! The parser reads the options of `nvx.py sandbox` with their names, defaults, types, and +//! choices, and fails as `argparse` fails, with status 2. The `--mount-deny`, `--mount-allow`, +//! and `--mount-write` options name paths of the `--mount` before them. + +mod identity; +mod lifecycle; +mod lock; +mod state; + +use std::process::{Child, Command}; +use std::thread; +use std::time::Duration; + +use super::args::{Parser, Receiver, Spec, invalid_choice, invalid_value}; +use super::artifacts::{Located, locate}; +use super::{Exit, Result, output}; +use crate::host::artifacts::{Guest, KERNEL_NAME}; +use crate::host::network::policy::compile_policy_file; +use crate::host::platform; +use crate::host::python::builtins::{Int, parse_float}; +use crate::host::python::path::PurePath; +use crate::host::python::shell::format_command; +use crate::host::sandbox::mount::{MountOwner, PolicyKind}; +use crate::host::sandbox::options::{ + PolicyPathOption, SandboxOperation, SandboxOptions, sandbox_mounts, + validate_sandbox_systemd_policy, +}; +use crate::host::sandbox::{ + DEFAULT_ENTRYPOINT, DEFAULT_HOSTNAME, DEFAULT_WORKLOAD_IDENTITY, SandboxLaunch, SandboxLayer, + SandboxSettings, parse_workload_identity, require_file, +}; + +pub(crate) use self::lifecycle::ProvisionConfig; + +/// The name of the parser in usage messages. +const PROG: &str = "nvx-rs sandbox"; +/// The usage of the parser. +const USAGE: &str = "\ +usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH] + [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT] + [--arg SANDBOX_ARG] [--hostname HOSTNAME] + [--workload-user UID:GID] [--memory-max MEMORY_MAX] + [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB] + [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS] + [--cwd CWD] [--environment KEY=VALUE] + [--environment-file PATH] + [--inherit-default-environment] + [--hypervisor {auto,whp,kvm,mshv}] + [--mount GUEST_TARGET,HOST_PATH[,ro|rw]] + [--mount-deny HOST_PATH] [--mount-allow HOST_PATH] + [--mount-write HOST_PATH] [--mount-owner {vmm,caller}] + [--net IPV4/PREFIX] [--network-profile {portable}] + [--network-egress {allow,deny}] + [--network-ingress {allow,deny}] + [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]] + [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]] + [--network-egress-policy-file PATH] + [--host-loopback {allow,deny}] + [--network-proxy IPV4:TCP-PORT] + [--host-loopback-forward HOST_LOOPBACK_FORWARD] + [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE] + [--dry-run] + [{run,provision,start,exec,stop,deprovision}]"; +/// The help of the parser, after its usage. +const HELP: &str = concat!( + "\ +usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH] + [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT] + [--arg SANDBOX_ARG] [--hostname HOSTNAME] + [--workload-user UID:GID] [--memory-max MEMORY_MAX] + [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB] + [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS] + [--cwd CWD] [--environment KEY=VALUE] + [--environment-file PATH] + [--inherit-default-environment] + [--hypervisor {auto,whp,kvm,mshv}] + [--mount GUEST_TARGET,HOST_PATH[,ro|rw]] + [--mount-deny HOST_PATH] [--mount-allow HOST_PATH] + [--mount-write HOST_PATH] [--mount-owner {vmm,caller}] + [--net IPV4/PREFIX] [--network-profile {portable}] + [--network-egress {allow,deny}] + [--network-ingress {allow,deny}] + [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]] + [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]] + [--network-egress-policy-file PATH] + [--host-loopback {allow,deny}] + [--network-proxy IPV4:TCP-PORT] + [--host-loopback-forward HOST_LOOPBACK_FORWARD] + [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE] + [--dry-run] + [{run,provision,start,exec,stop,deprovision}] +", + " +positional arguments: + {run,provision,start,exec,stop,deprovision} + +options: + -h, --help show this help message and exit + --layer ROLE,PATH,EROFS_UUID + --scratch SCRATCH + --state-dir STATE_DIR + --entrypoint ENTRYPOINT + --arg SANDBOX_ARG + --hostname HOSTNAME + --workload-user UID:GID + fixed non-root workload identity (default: + 65534:65534) + --memory-max MEMORY_MAX + --pids-max PIDS_MAX + --memory-mib MEMORY_MIB + --timeout TIMEOUT control operation timeout, and the longest wait for + another lifecycle transition of the sandbox, in + seconds (default: 60) + --exec-timeout-ms EXEC_TIMEOUT_MS + guest workload timeout in milliseconds; zero disables + it + --cwd CWD absolute guest working directory for managed exec + (default: /) + --environment KEY=VALUE + set the exact managed exec environment; repeat for + multiple entries + --environment-file PATH + read the exact managed exec environment from a JSON + string array + --inherit-default-environment + layer the managed exec environment over the guest + default environment instead of replacing it + --hypervisor {auto,whp,kvm,mshv} + --mount GUEST_TARGET,HOST_PATH[,ro|rw] + live-share a host directory, or a single regular host + file without its directory, inside the container + rootfs; the mode follows the last comma, so HOST_PATH + may contain commas when the mode is given; repeat to + attach more shares, each with its own target and mode, + as children of one virtio-fs device + --mount-deny HOST_PATH + hide one existing path inside a --mount host + directory; with several --mount options, it applies to + the --mount before it + --mount-allow HOST_PATH + expose one existing path inside a --mount-deny path of + the same share again; the hidden directories on the + way list only it + --mount-write HOST_PATH + make one existing path one of the only writable parts + of a read-write --mount; the rest of the share is + read-only + --mount-owner {vmm,caller} + host identity of the share's file operations: vmm + (default) or caller, the workload identity with guest + root squashed to the owner of the shared directory or + file (Linux only) + --net IPV4/PREFIX + --network-profile {portable} + --network-egress {allow,deny} + --network-ingress {allow,deny} + --network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]] + allow guest egress to an IPv4 or IPv6 CIDR, optionally + only for PROTOCOL tcp, udp, or icmp, and for tcp or + udp only on PORT or on the inclusive range PORT-PORT; + repeat to add rules + --network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]] + deny guest egress to an IPv4 or IPv6 CIDR, optionally + only for PROTOCOL tcp, udp, or icmp, and for tcp or + udp only on PORT or on the inclusive range PORT-PORT; + repeat to add rules + --network-egress-policy-file PATH + load bounded IPv4 and IPv6 ranges and rule-local + exclusions for run/provision + --host-loopback {allow,deny} + --network-proxy IPV4:TCP-PORT + --host-loopback-forward HOST_LOOPBACK_FORWARD + --outcome-report OUTCOME_REPORT + write a bounded local JSON outcome report for run or + exec + --cmdline CMDLINE + --dry-run print the one-shot run command without launching it" +); + +/// The operations, in the order in which usage messages list them. +const OPERATIONS: [&str; 6] = ["run", "provision", "start", "exec", "stop", "deprovision"]; +/// The choices of `--hypervisor`. +const HYPERVISORS: [&str; 4] = ["auto", "whp", "kvm", "mshv"]; +/// The choices of `--mount-owner`. +const MOUNT_OWNERS: [&str; 2] = ["vmm", "caller"]; +/// The choices of `--network-profile`. +const NETWORK_PROFILES: [&str; 1] = ["portable"]; +/// The choices of `--network-egress`, `--network-ingress`, and `--host-loopback`. +const ACCESS: [&str; 2] = ["allow", "deny"]; + +/// The options of `nvx-rs sandbox`, which parse with the defaults of `nvx.py sandbox`. +const OPTIONS: [Spec; 35] = [ + Spec::one("--layer"), + Spec::one("--scratch"), + Spec::one("--state-dir"), + Spec::one("--entrypoint"), + Spec::one("--arg"), + Spec::one("--hostname"), + Spec::one("--workload-user"), + Spec::one("--memory-max"), + Spec::one("--pids-max"), + Spec::one("--memory-mib"), + Spec::one("--timeout"), + Spec::one("--exec-timeout-ms"), + Spec::one("--cwd"), + Spec::one("--environment"), + Spec::one("--environment-file"), + Spec::flag("--inherit-default-environment"), + Spec::one("--hypervisor"), + Spec::one("--mount"), + Spec::one("--mount-deny"), + Spec::one("--mount-allow"), + Spec::one("--mount-write"), + Spec::one("--mount-owner"), + Spec::one("--net"), + Spec::one("--network-profile"), + Spec::one("--network-egress"), + Spec::one("--network-ingress"), + Spec::one("--network-egress-allow"), + Spec::one("--network-egress-deny"), + Spec::one("--network-egress-policy-file"), + Spec::one("--host-loopback"), + Spec::one("--network-proxy"), + Spec::one("--host-loopback-forward"), + Spec::one("--outcome-report"), + Spec::one("--cmdline"), + Spec::flag("--dry-run"), +]; + +/// The parsed options of `nvx-rs sandbox`: the namespace of `nvx.py sandbox`. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct Options { + /// The operation, `run` by default. + pub(crate) operation: SandboxOperation, + /// `--layer`, parsed as `SandboxLayer.parse` parses it. + pub(crate) layers: Vec, + /// `--scratch`, as given. + pub(crate) scratch: Option, + /// `--state-dir`, as given. + pub(crate) state_dir: Option, + /// `--entrypoint`. + pub(crate) entrypoint: String, + /// The `--arg` options. + pub(crate) args: Vec, + /// `--hostname`. + pub(crate) hostname: String, + /// `--workload-user`. + pub(crate) workload_user: (u32, u32), + /// `--memory-max`. + pub(crate) memory_max: Option, + /// `--pids-max`. + pub(crate) pids_max: Option, + /// `--memory-mib`, 256 by default. + pub(crate) memory_mib: Int, + /// `--timeout` in seconds, 60 by default. + pub(crate) timeout: f64, + /// `--exec-timeout-ms`, 0 by default. + pub(crate) exec_timeout_ms: Int, + /// `--cwd`. + pub(crate) cwd: Option, + /// The `--environment` options. + pub(crate) environment: Vec, + /// `--environment-file`, as given. + pub(crate) environment_file: Option, + /// `--inherit-default-environment`. + pub(crate) inherit_default_environment: bool, + /// `--hypervisor`, `auto` by default. + pub(crate) hypervisor: &'static str, + /// The `--mount` options. + pub(crate) mounts: Vec, + /// The `--mount-deny`, `--mount-allow`, and `--mount-write` options, with the `--mount` + /// before each. + pub(crate) policy_paths: Vec, + /// `--mount-owner`. + pub(crate) mount_owner: Option, + /// `--net`. + pub(crate) net: Option, + /// `--network-profile`. + pub(crate) network_profile: Option<&'static str>, + /// `--network-egress`. + pub(crate) network_egress: Option<&'static str>, + /// `--network-ingress`. + pub(crate) network_ingress: Option<&'static str>, + /// The `--network-egress-allow` options. + pub(crate) network_egress_allow: Vec, + /// The `--network-egress-deny` options. + pub(crate) network_egress_deny: Vec, + /// `--network-egress-policy-file`, as given. + pub(crate) network_egress_policy_file: Option, + /// `--host-loopback`. + pub(crate) host_loopback: Option<&'static str>, + /// `--network-proxy`. + pub(crate) network_proxy: Option, + /// The `--host-loopback-forward` options. + pub(crate) host_loopback_forward: Vec, + /// `--outcome-report`, as given. + pub(crate) outcome_report: Option, + /// `--cmdline`, empty by default. + pub(crate) cmdline: String, + /// `--dry-run`. + pub(crate) dry_run: bool, +} + +impl Default for Options { + fn default() -> Self { + Self { + operation: SandboxOperation::Run, + layers: Vec::new(), + scratch: None, + state_dir: None, + entrypoint: DEFAULT_ENTRYPOINT.to_owned(), + args: Vec::new(), + hostname: DEFAULT_HOSTNAME.to_owned(), + workload_user: DEFAULT_WORKLOAD_IDENTITY, + memory_max: None, + pids_max: None, + memory_mib: Int::from(256u32), + timeout: 60.0, + exec_timeout_ms: Int::zero(), + cwd: None, + environment: Vec::new(), + environment_file: None, + inherit_default_environment: false, + hypervisor: "auto", + mounts: Vec::new(), + policy_paths: Vec::new(), + mount_owner: None, + net: None, + network_profile: None, + network_egress: None, + network_ingress: None, + network_egress_allow: Vec::new(), + network_egress_deny: Vec::new(), + network_egress_policy_file: None, + host_loopback: None, + network_proxy: None, + host_loopback_forward: Vec::new(), + outcome_report: None, + cmdline: String::new(), + dry_run: false, + } + } +} + +/// Returns the choice that `value` names among `choices`, as `argparse` checks `choices`. +fn choice(value: &str, choices: &[&'static str]) -> std::result::Result<&'static str, String> { + choices + .iter() + .find(|choice| **choice == value) + .copied() + .ok_or_else(|| invalid_choice(value, choices)) +} + +/// Returns `int(value)` for an option of `type=int`. +fn integer(value: &str) -> std::result::Result { + Int::parse(value).map_err(|_| invalid_value("int", value)) +} + +impl Receiver for Options { + fn take(&mut self, name: &'static str, value: Option<&str>) -> std::result::Result<(), String> { + let text = || value.unwrap_or_default().to_owned(); + match name { + "sandbox_operation" => { + let operation = choice(value.unwrap_or("run"), &OPERATIONS)?; + self.operation = match operation { + "provision" => SandboxOperation::Provision, + "start" => SandboxOperation::Start, + "exec" => SandboxOperation::Exec, + "stop" => SandboxOperation::Stop, + "deprovision" => SandboxOperation::Deprovision, + _ => SandboxOperation::Run, + }; + } + "--layer" => self + .layers + .push(SandboxLayer::parse(&text()).map_err(|error| error.message().to_owned())?), + "--scratch" => self.scratch = Some(text()), + "--state-dir" => self.state_dir = Some(text()), + "--entrypoint" => self.entrypoint = text(), + "--arg" => self.args.push(text()), + "--hostname" => self.hostname = text(), + "--workload-user" => { + self.workload_user = + parse_workload_identity(&text()).map_err(|error| error.message().to_owned())?; + } + "--memory-max" => self.memory_max = Some(integer(&text())?), + "--pids-max" => self.pids_max = Some(integer(&text())?), + "--memory-mib" => self.memory_mib = integer(&text())?, + "--timeout" => { + let timeout = + parse_float(&text()).ok_or_else(|| invalid_value("positive_float", &text()))?; + if !timeout.is_finite() || timeout <= 0.0 { + return Err("must be finite and greater than 0".to_owned()); + } + self.timeout = timeout; + } + "--exec-timeout-ms" => self.exec_timeout_ms = integer(&text())?, + "--cwd" => self.cwd = Some(text()), + "--environment" => self.environment.push(text()), + "--environment-file" => self.environment_file = Some(text()), + "--inherit-default-environment" => self.inherit_default_environment = true, + "--hypervisor" => self.hypervisor = choice(&text(), &HYPERVISORS)?, + "--mount" => self.mounts.push(text()), + "--mount-deny" | "--mount-allow" | "--mount-write" => { + let kind = match name { + "--mount-deny" => PolicyKind::Denied, + "--mount-allow" => PolicyKind::Allowed, + _ => PolicyKind::Writable, + }; + self.policy_paths.push(PolicyPathOption { + kind, + preceding_mount: self.mounts.len().checked_sub(1), + path: text(), + }); + } + "--mount-owner" => { + self.mount_owner = Some(match choice(&text(), &MOUNT_OWNERS)? { + "caller" => MountOwner::Caller, + _ => MountOwner::Vmm, + }); + } + "--net" => self.net = Some(text()), + "--network-profile" => self.network_profile = Some(choice(&text(), &NETWORK_PROFILES)?), + "--network-egress" => self.network_egress = Some(choice(&text(), &ACCESS)?), + "--network-ingress" => self.network_ingress = Some(choice(&text(), &ACCESS)?), + "--network-egress-allow" => self.network_egress_allow.push(text()), + "--network-egress-deny" => self.network_egress_deny.push(text()), + "--network-egress-policy-file" => self.network_egress_policy_file = Some(text()), + "--host-loopback" => self.host_loopback = Some(choice(&text(), &ACCESS)?), + "--network-proxy" => self.network_proxy = Some(text()), + "--host-loopback-forward" => self.host_loopback_forward.push(text()), + "--outcome-report" => self.outcome_report = Some(text()), + "--cmdline" => self.cmdline = text(), + "--dry-run" => self.dry_run = true, + _ => unreachable!("the parser knows only these options"), + } + Ok(()) + } +} + +/// Parses the arguments of `sandbox`, and returns the options and the arguments that the +/// parser does not recognize. +pub(super) fn parse(arguments: &[String]) -> Result<(Options, Vec)> { + let mut options = Options::default(); + let unrecognized = Parser::new(&OPTIONS, &[Spec::optional("sandbox_operation")]) + .parse(arguments, &mut options) + .map_err(|stop| Exit::from_stop(stop, PROG, USAGE, HELP))?; + Ok((options, unrecognized)) +} + +/// Returns `_hypervisor(selected)`: `auto` selects `whp` on Windows and `kvm` elsewhere. +fn hypervisor(selected: &'static str) -> &'static str { + match selected { + "auto" if cfg!(windows) => "whp", + "auto" => "kvm", + other => other, + } +} + +/// Returns `str(Path(value))`. +fn path_text(value: &str) -> String { + PurePath::native(value).as_str().to_owned() +} + +/// Returns `path` as text, which the commands of `nvx.py` hold as `str`. +pub(crate) fn path_string(path: &std::path::Path) -> Result { + path.to_str() + .map(str::to_owned) + .ok_or_else(|| Exit::error(format!("path is not valid Unicode: {}", path.display()))) +} + +/// The rules of the egress policy of a launch. +struct EgressRules { + /// The `--network-egress-allow` rules. + allow: Vec, + /// The `--network-egress-deny` rules. + deny: Vec, +} + +/// Returns `_resolve_network_egress_rules(args)`: the explicit rules, or those that the +/// `--network-egress-policy-file` compiles to, which neither rule option nor a missing +/// `--network-egress` may accompany. +fn resolve_network_egress_rules(options: &Options) -> Result { + let Some(policy) = &options.network_egress_policy_file else { + return Ok(EgressRules { + allow: options.network_egress_allow.clone(), + deny: options.network_egress_deny.clone(), + }); + }; + if !options.network_egress_allow.is_empty() || !options.network_egress_deny.is_empty() { + return Err(Exit::error( + "--network-egress-policy-file cannot be combined with --network-egress-allow or \ + --network-egress-deny", + )); + } + if options.network_egress.is_none() { + return Err(Exit::error( + "--network-egress is required with --network-egress-policy-file", + )); + } + let compiled = compile_policy_file(std::path::Path::new(policy))?; + Ok(EgressRules { + allow: compiled.allow, + deny: compiled.deny, + }) +} + +/// Appends the network options of `options` to `command`, as `_extend_network_arguments` does. +fn extend_network_arguments(command: &mut Vec, options: &Options, rules: &EgressRules) { + let mut push = |name: &str, value: &str| { + command.push(name.to_owned()); + command.push(value.to_owned()); + }; + if let Some(net) = &options.net { + push("--net", net); + push( + "--network-profile", + options.network_profile.unwrap_or_default(), + ); + } + if let Some(egress) = options.network_egress { + push("--network-egress", egress); + } + if let Some(ingress) = options.network_ingress { + push("--network-ingress", ingress); + } + for rule in &rules.allow { + push("--network-egress-allow", rule); + } + for rule in &rules.deny { + push("--network-egress-deny", rule); + } + if let Some(host_loopback) = options.host_loopback { + push("--host-loopback", host_loopback); + } + if let Some(proxy) = &options.network_proxy { + push("--network-proxy", proxy); + } + for forward in &options.host_loopback_forward { + push("--host-loopback-forward", forward); + } +} + +/// The artifacts of a sandbox, found when an operation first needs them. +pub(crate) struct Artifacts<'a> { + /// `--root`, if given. + root: Option<&'a str>, +} + +impl Artifacts<'_> { + /// Returns the OpenVMM executable, kernel, and Alpine initramfs, checked in that order as + /// `nvx.py sandbox run` and `start` check them. + pub(crate) fn require(&self) -> Result<[String; 3]> { + let Located { dir, openvmm } = locate(self.root)?; + require_file(&openvmm, "OpenVMM release binary")?; + let kernel = dir.artifact(KERNEL_NAME); + require_file(&kernel, "Linux direct kernel")?; + let initrd = dir.initramfs(Guest::Alpine); + require_file(&initrd, "initramfs")?; + Ok([ + path_string(&openvmm)?, + path_string(&kernel)?, + path_string(&initrd)?, + ]) + } +} + +/// Runs the operation of `options`, as `command_sandbox` does. +pub(super) fn run(root: Option<&str>, options: Options) -> Result { + let operation = options.operation; + let checks = SandboxOptions { + operation, + dry_run: options.dry_run, + cwd: options.cwd.clone(), + environment: options.environment.clone(), + environment_file: options.environment_file.clone(), + inherit_default_environment: options.inherit_default_environment, + network_options: options.net.is_some() + || options.network_profile.is_some() + || options.network_egress.is_some() + || options.network_ingress.is_some() + || options.network_egress_policy_file.is_some() + || options.host_loopback.is_some() + || options.network_proxy.is_some() + || !options.network_egress_allow.is_empty() + || !options.network_egress_deny.is_empty() + || !options.host_loopback_forward.is_empty(), + net: options.net.is_some(), + network_profile: options.network_profile.is_some(), + entrypoint: options.entrypoint.clone(), + outcome_report: options.outcome_report.is_some(), + mounts: options.mounts.clone(), + policy_paths: options.policy_paths.clone(), + mount_owner: options.mount_owner, + layers: !options.layers.is_empty(), + scratch: options.scratch.is_some(), + }; + let environment = checks.check()?; + let launch = if matches!( + operation, + SandboxOperation::Run | SandboxOperation::Provision + ) { + let rules = resolve_network_egress_rules(&options)?; + let mounts = sandbox_mounts(&options.mounts, &options.policy_paths, options.mount_owner)?; + let settings = SandboxSettings { + entrypoint: options.entrypoint.clone(), + args: options.args.clone(), + hostname: options.hostname.clone(), + workload_identity: options.workload_user, + memory_max: options.memory_max.clone(), + pids_max: options.pids_max.clone(), + mounts, + }; + let launch = SandboxLaunch::new( + options.layers.clone(), + options.scratch.as_deref().unwrap_or_default(), + settings, + )?; + launch.validated()?; + validate_sandbox_systemd_policy(&launch)?; + Some((launch, rules)) + } else { + None + }; + let artifacts = Artifacts { root }; + let state_dir = |operation: &str| { + options + .state_dir + .as_deref() + .ok_or_else(|| Exit::error(format!("sandbox {operation} requires --state-dir"))) + }; + match (operation, launch) { + (SandboxOperation::Provision, Some((launch, rules))) => { + let state_dir = state_dir("provision")?; + lifecycle::provision( + state_dir, + &launch, + &ProvisionConfig { + hypervisor: hypervisor(options.hypervisor), + memory_mib: &options.memory_mib, + net: options.net.as_deref(), + network_profile: options.network_profile, + network_egress: options.network_egress, + network_ingress: options.network_ingress, + network_egress_allow: &rules.allow, + network_egress_deny: &rules.deny, + host_loopback: options.host_loopback, + network_proxy: options.network_proxy.as_deref(), + host_loopback_forward: &options.host_loopback_forward, + cmdline: &options.cmdline, + }, + options.timeout, + ) + } + (SandboxOperation::Start, _) => { + lifecycle::start(state_dir("start")?, &artifacts, options.timeout) + } + (SandboxOperation::Exec, _) => { + let state_dir = state_dir("exec")?; + if let Some(report) = &options.outcome_report { + lifecycle::validate_outcome_destination(report)?; + } + let mut arguments = vec![options.entrypoint.clone()]; + arguments.extend(options.args.iter().cloned()); + let result = lifecycle::exec_workload( + state_dir, + &super::session::ExecRequest { + arguments, + timeout_ms: options.exec_timeout_ms.clone(), + cwd: options.cwd.clone(), + environment, + inherit_default_environment: options.inherit_default_environment, + }, + options.timeout, + )?; + output::write_stdout(&result.stdout) + .and_then(|()| output::write_stderr(&result.stderr)) + .map_err(|error| Exit::error(output::stream_error(&error)))?; + if let Some(report) = &options.outcome_report { + lifecycle::write_exec_outcome(report, &result)?; + } + Err(Exit::Status(result.returncode)) + } + (SandboxOperation::Stop, _) => { + lifecycle::stop(state_dir("stop")?, options.timeout).map(drop) + } + (SandboxOperation::Deprovision, _) => { + lifecycle::deprovision(state_dir("deprovision")?, options.timeout) + } + (SandboxOperation::Run, Some((launch, rules))) => { + if options.state_dir.is_some() { + return Err(Exit::error( + "one-shot sandbox execution rejects persistent --state-dir settings", + )); + } + let [openvmm, kernel, initrd] = artifacts.require()?; + let mut command = vec![openvmm]; + command.extend(launch.openvmm_arguments()?); + command.extend( + [ + "--microvm-lifecycle", + "one-shot", + "--single-process", + "--hypervisor", + hypervisor(options.hypervisor), + ] + .map(str::to_owned), + ); + command.extend([ + "--memory".to_owned(), + format!("{}M", options.memory_mib), + "--kernel".to_owned(), + kernel, + "--initrd".to_owned(), + initrd, + "--cmdline".to_owned(), + launch.kernel_command_line(&options.cmdline)?, + ]); + extend_network_arguments(&mut command, &options, &rules); + if let Some(report) = &options.outcome_report { + command.extend(["--microvm-report".to_owned(), path_text(report)]); + } + output::print_stdout(&format!(">> {}", format_command(&command))) + .map_err(|error| Exit::error(output::stream_error(&error)))?; + if options.dry_run { + return Ok(()); + } + Err(Exit::Status(run_openvmm(&command)?)) + } + (SandboxOperation::Run | SandboxOperation::Provision, None) => { + unreachable!("run and provision build their launch") + } + } +} + +/// Runs OpenVMM on the terminal, as `subprocess.run(command)` does, and returns its status. +/// +/// OpenVMM shares the terminal and its process group, so Ctrl-C reaches it too. As +/// `subprocess.run` does, the CLI then gives it 0.25 s to exit on Unix before it kills it, and on +/// Windows, where Python cannot interrupt its wait, waits for it to exit; either way the command +/// ends as interrupted, also when OpenVMM exited first, since Python's wait raises +/// `KeyboardInterrupt` once Ctrl-C arrives. +fn run_openvmm(command: &[String]) -> Result { + let mut child = Command::new(&command[0]) + .args(&command[1..]) + .spawn() + .map_err(|error| Exit::error(spawn_error(&error, &command[0])))?; + loop { + let status = poll(&mut child)?; + if platform::interrupted() { + if status.is_none() { + end_interrupted(&mut child)?; + } + return Err(Exit::Interrupted); + } + if let Some(status) = status { + return Ok(status); + } + thread::sleep(Duration::from_millis(10)); + } +} + +/// Ends the OpenVMM of an interrupted run as `subprocess.run` does: on Unix, it waits 0.25 s for +/// OpenVMM to exit and then kills it, and on Windows, it waits for OpenVMM to exit. +fn end_interrupted(child: &mut Child) -> Result { + let deadline = std::time::Instant::now() + Duration::from_millis(250); + while poll(child)?.is_none() { + if cfg!(unix) && std::time::Instant::now() >= deadline { + let _ = child.kill(); + let _ = child.wait(); + break; + } + thread::sleep(Duration::from_millis(10)); + } + Ok(()) +} + +/// Returns the status of `child` if it has exited: `Popen.returncode`, which on Unix is the +/// negated signal of a process that a signal ended. +fn poll(child: &mut Child) -> Result> { + let status = child + .try_wait() + .map_err(|error| Exit::error(crate::host::python::fs::os_error_message(&error, None)))?; + Ok(status.map(|status| { + #[cfg(unix)] + { + use std::os::unix::process::ExitStatusExt; + + status + .code() + .unwrap_or_else(|| -status.signal().unwrap_or_default()) + } + #[cfg(not(unix))] + status.code().unwrap_or_default() + })) +} + +/// Returns the message of the `OSError` with which `subprocess` fails to start `program`: with +/// the program's name on POSIX, and without on Windows. +pub(crate) fn spawn_error(error: &std::io::Error, program: &str) -> String { + let program = if cfg!(windows) { None } else { Some(program) }; + crate::host::python::fs::os_error_message(error, program) +} diff --git a/nvx-rs/src/host/cli/sandbox/state.rs b/nvx-rs/src/host/cli/sandbox/state.rs new file mode 100644 index 000000000..9de2687dc --- /dev/null +++ b/nvx-rs/src/host/cli/sandbox/state.rs @@ -0,0 +1,627 @@ +//! The files of a state directory: `config.json` in formats 1 to 6, `runtime.json`, and the JSON +//! reading and writing of `sandbox_lifecycle.py`. +//! +//! - [`write_json`] writes `json.dumps(value, indent=2, sort_keys=True) + "\n"` as +//! `Path.write_text` does, with the `\n`s turned into `os.linesep`, into a temporary file of +//! mode 0600 that then replaces the file. +//! - [`read_json`] reads a file as `json.loads(path.read_text(encoding="utf-8"))` does, and +//! accepts only an object whose version field equals, as `==` compares, one of the given +//! versions. +//! - [`serialize_launch`] writes a provisioned launch in the lowest format that holds it, and +//! [`deserialize_launch`] reads it back with the conversions, defaults, and checks of +//! `_deserialize_launch`. + +use std::fs::OpenOptions; +use std::io::Write; + +use super::lifecycle::ProvisionConfig; +use crate::host::cli::output::LINE_SEPARATOR; +use crate::host::cli::{Exit, Result}; +use crate::host::python::builtins::{Int, repr}; +use crate::host::python::dumps::{self, Value, dict}; +use crate::host::python::fs::{self, os_error_message}; +use crate::host::python::json::{self, Document, Loads, Node, NodeId, ReadError}; +use crate::host::python::path::PurePath; +use crate::host::python::text::decode_utf8; +use crate::host::python::value::{self, IntError, equals_int, py_int, py_str}; +use crate::host::sandbox::layer::{LayerRole, SandboxLayer}; +use crate::host::sandbox::mount::{ + MountAccess, MountKind, MountOwner, MountPolicy, SandboxMount, validate_mount_target, +}; +use crate::host::sandbox::{SandboxLaunch, SandboxSettings}; + +/// The format of `runtime.json`, and the version of the files that `_read_json` reads by default. +pub(crate) const STATE_FORMAT: u64 = 1; +/// The configuration of a sandbox without shares. +pub(crate) const CONFIG_FORMAT: u64 = 1; +/// The configuration of a sandbox with one share, which format-1 readers would start without it. +pub(crate) const MOUNT_CONFIG_FORMAT: u64 = 2; +/// The configuration of a sandbox with one share that the caller owns, which format-2 readers +/// would start as the VMM. +pub(crate) const OWNER_CONFIG_FORMAT: u64 = 3; +/// The configuration of a sandbox with several shares, listed under `mounts`. +pub(crate) const MULTI_MOUNT_CONFIG_FORMAT: u64 = 4; +/// The configuration of a sandbox whose shares have allowed or writable paths. +pub(crate) const POLICY_CONFIG_FORMAT: u64 = 5; +/// The configuration of a sandbox that shares a file, with the kind of each share. +pub(crate) const FILE_MOUNT_CONFIG_FORMAT: u64 = 6; +/// Every configuration format that `start` reads. +pub(crate) const CONFIG_FORMATS: [u64; 6] = [ + CONFIG_FORMAT, + MOUNT_CONFIG_FORMAT, + OWNER_CONFIG_FORMAT, + MULTI_MOUNT_CONFIG_FORMAT, + POLICY_CONFIG_FORMAT, + FILE_MOUNT_CONFIG_FORMAT, +]; + +/// Returns the name of a temporary file next to `path`: `.NAME.UUID.tmp`, with a fresh UUID4 in +/// hexadecimal. +pub(crate) fn temporary_path(path: &PurePath) -> Result { + let name = format!(".{}.{}.tmp", path.name(), uuid4_hex()?); + path.with_name(&name) + .ok_or_else(|| Exit::error(format!("{path} has an empty name"))) +} + +/// Returns `uuid.uuid4().hex`: 32 lowercase hexadecimal digits of 122 random bits, with the +/// version and variant bits of a version-4 UUID. +pub(crate) fn uuid4_hex() -> Result { + let mut bytes = random_bytes::<16>()?; + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + Ok(hex(&bytes)) +} + +/// Returns `N` random bytes from the operating system, as `secrets` and `uuid` take them. +pub(crate) fn random_bytes() -> Result<[u8; N]> { + let mut bytes = [0u8; N]; + getrandom::fill(&mut bytes) + .map_err(|error| Exit::error(format!("cannot read random bytes: {error}")))?; + Ok(bytes) +} + +/// Returns `bytes` in lowercase hexadecimal. +pub(crate) fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|byte| format!("{byte:02x}")).collect() +} + +/// Returns `str(error)` for an `OSError` that names two files, as `os.replace` raises it. +fn os_error_with_two_paths(error: &std::io::Error, source: &str, destination: &str) -> String { + let message = os_error_message(error, Some(source)); + match error.raw_os_error() { + Some(_) => format!("{message} -> {}", repr(destination)), + None => message, + } +} + +/// Atomically replaces `path` with `value` as `_write_json` writes it: the JSON text of +/// `json.dumps(value, indent=2, sort_keys=True)` and a newline, with `os.linesep` for each +/// newline, in a temporary file of mode 0600 that then replaces `path`. +pub(crate) fn write_json(path: &PurePath, value: &Value) -> Result { + let temporary = temporary_path(path)?; + let text = format!("{}\n", dumps::dumps(value)).replace('\n', LINE_SEPARATOR); + let written = (|| -> Result { + let mut options = OpenOptions::new(); + options.write(true).create(true).truncate(true); + let mut file = options + .open(temporary.as_path()) + .and_then(|mut file| file.write_all(text.as_bytes()).map(|()| file)) + .map_err(|error| Exit::error(os_error_message(&error, Some(temporary.as_str()))))?; + file.flush() + .map_err(|error| Exit::error(os_error_message(&error, Some(temporary.as_str()))))?; + drop(file); + set_private(&temporary)?; + std::fs::rename(temporary.as_path(), path.as_path()).map_err(|error| { + Exit::error(os_error_with_two_paths( + &error, + temporary.as_str(), + path.as_str(), + )) + }) + })(); + let _ = std::fs::remove_file(temporary.as_path()); + written +} + +/// Gives `path` mode 0600, as `os.chmod(path, 0o600)` does: on Windows, that only clears the +/// read-only attribute. +fn set_private(path: &PurePath) -> Result { + let error = |error: std::io::Error| Exit::error(os_error_message(&error, Some(path.as_str()))); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + std::fs::set_permissions(path.as_path(), std::fs::Permissions::from_mode(0o600)) + .map_err(error) + } + #[cfg(not(unix))] + { + let mut permissions = std::fs::metadata(path.as_path()) + .map_err(error)? + .permissions(); + if permissions.readonly() { + #[allow(clippy::permissions_set_readonly_false)] + permissions.set_readonly(false); + std::fs::set_permissions(path.as_path(), permissions).map_err(error)?; + } + Ok(()) + } +} + +/// Returns the member `name` of the object at `id`, as `value.get(name)` finds it in the dict +/// that Python builds, in which the last of repeated names wins. +pub(crate) fn member(document: &Document, id: NodeId, name: &str) -> Option { + let Node::Object(members) = document.node(id) else { + return None; + }; + members + .iter() + .rev() + .find(|(member, _)| member.as_bytes() == name.as_bytes()) + .map(|&(_, value)| value) +} + +/// Reads the JSON object at `path`, as `_read_json` does: it must hold `version_field` equal, as +/// `==` compares, to one of `versions`. +/// +/// A file that cannot be read, is not UTF-8, or is not JSON fails as `failed to read +/// DESCRIPTION: PATH`, and another value or version as `DESCRIPTION has an unsupported format: +/// PATH`. An integer of more than 4300 digits fails with the `ValueError` of `int`. +pub(crate) fn read_json( + path: &PurePath, + description: &str, + version_field: &str, + versions: &[u64], +) -> Result { + let unreadable = || Exit::error(format!("failed to read {description}: {path}")); + let bytes = std::fs::read(path.as_path()).map_err(|_| unreadable())?; + // `read_text` decodes strictly and translates universal newlines. + let text = decode_utf8(&bytes) + .map_err(|_| unreadable())? + .replace("\r\n", "\n") + .replace('\r', "\n"); + let document = match json::read(&text, &mut Loads) { + Ok(document) => document, + Err(ReadError::Syntax { .. }) => return Err(unreadable()), + Err(ReadError::Hook(error)) => return Err(Exit::error(error.message(&text))), + }; + let unsupported = || Exit::error(format!("{description} has an unsupported format: {path}")); + let root = document.root(); + if !matches!(document.node(root), Node::Object(_)) { + return Err(unsupported()); + } + let version = member(&document, root, version_field); + if !version.is_some_and(|version| { + versions + .iter() + .any(|&expected| equals_int(&document, version, expected)) + }) { + return Err(unsupported()); + } + Ok(document) +} + +/// Returns the format of the configuration of a sandbox with `mounts`: the lowest that holds +/// them, as `_config_format` chooses it. +pub(crate) fn config_format(mounts: &[SandboxMount]) -> u64 { + if mounts.iter().any(|mount| mount.kind() == MountKind::File) { + FILE_MOUNT_CONFIG_FORMAT + } else if mounts + .iter() + .any(|mount| !mount.policy().allowed.is_empty() || !mount.policy().writable.is_empty()) + { + POLICY_CONFIG_FORMAT + } else if mounts.len() > 1 { + MULTI_MOUNT_CONFIG_FORMAT + } else if mounts.len() == 1 && mounts[0].owner() == MountOwner::Caller { + OWNER_CONFIG_FORMAT + } else if mounts.len() == 1 { + MOUNT_CONFIG_FORMAT + } else { + CONFIG_FORMAT + } +} + +/// Returns whether a format lists every share under `mounts`. +fn lists_mounts(format: u64) -> bool { + matches!( + format, + MULTI_MOUNT_CONFIG_FORMAT | POLICY_CONFIG_FORMAT | FILE_MOUNT_CONFIG_FORMAT + ) +} + +/// Returns `path.resolve()` as text. +fn resolved(path: &std::path::Path) -> Result { + let text = crate::host::cli::sandbox::path_string(path)?; + let resolved = fs::resolve(&PurePath::native(&text)) + .map_err(|error| Exit::error(os_error_message(&error, None)))?; + Ok(resolved.as_str().to_owned()) +} + +/// Returns a list of strings. +fn strings(values: &[String]) -> Value { + Value::List( + values + .iter() + .map(|value| Value::from(value.as_str())) + .collect(), + ) +} + +/// Returns the configuration that `provision` records for `launch` and the other options, as +/// `_serialize_launch` builds it. +pub(crate) fn serialize_launch( + launch: &SandboxLaunch, + config: &ProvisionConfig<'_>, +) -> Result { + let format = config_format(launch.mounts()); + let mut layers = Vec::new(); + for layer in launch.ordered_layers() { + layers.push(dict([ + ("role", Value::from(layer.role().as_str())), + ("path", Value::from(resolved(layer.path())?)), + ("uuid", Value::from(layer.uuid())), + ])); + } + let settings = launch.settings(); + let (uid, gid) = settings.workload_identity; + let mut value = dict([ + ("format", Value::from(format)), + ("layers", Value::List(layers)), + ("scratch", Value::from(resolved(launch.scratch())?)), + ("hostname", Value::from(settings.hostname.as_str())), + ("workload_uid", Value::from(uid)), + ("workload_gid", Value::from(gid)), + ("memory_max", Value::from(settings.memory_max.clone())), + ("pids_max", Value::from(settings.pids_max.clone())), + ("hypervisor", Value::from(config.hypervisor)), + ("memory_mib", Value::from(config.memory_mib.clone())), + ("net", Value::from(config.net)), + ("network_profile", Value::from(config.network_profile)), + ("network_egress", Value::from(config.network_egress)), + ("network_ingress", Value::from(config.network_ingress)), + ("network_egress_allow", strings(config.network_egress_allow)), + ("network_egress_deny", strings(config.network_egress_deny)), + ("host_loopback", Value::from(config.host_loopback)), + ("network_proxy", Value::from(config.network_proxy)), + ( + "host_loopback_forward", + strings(config.host_loopback_forward), + ), + ("cmdline", Value::from(config.cmdline)), + ]); + let Value::Dict(members) = &mut value else { + unreachable!("dict builds a dict") + }; + if lists_mounts(format) { + let mut mounts = Vec::new(); + for mount in launch.mounts() { + mounts.push(serialize_mount( + mount, + format != MULTI_MOUNT_CONFIG_FORMAT, + format == FILE_MOUNT_CONFIG_FORMAT, + )?); + } + members.insert("mounts".to_owned(), Value::List(mounts)); + } else { + let mount = match launch.mounts().first() { + Some(mount) => serialize_mount(mount, false, false)?, + None => Value::Null, + }; + members.insert("mount".to_owned(), mount); + } + Ok(value) +} + +/// Returns a share as `_serialize_mount` records it, with its host path made absolute, and with +/// its allowed and writable paths and its kind if the format holds them. +fn serialize_mount(mount: &SandboxMount, policy: bool, kind: bool) -> Result { + let absolute = mount.absolute()?; + let mut value = dict([ + ("guest_target", Value::from(absolute.guest_target())), + ( + "host_path", + Value::from(crate::host::cli::sandbox::path_string( + absolute.host_path(), + )?), + ), + ("access", Value::from(absolute.access().as_str())), + ("denied_paths", strings(&absolute.policy().denied)), + ("owner", Value::from(absolute.owner().as_str())), + ]); + let Value::Dict(members) = &mut value else { + unreachable!("dict builds a dict") + }; + if policy { + members.insert( + "allowed_paths".to_owned(), + strings(&absolute.policy().allowed), + ); + members.insert( + "writable_paths".to_owned(), + strings(&absolute.policy().writable), + ); + } + if kind { + members.insert("kind".to_owned(), Value::from(absolute.kind().as_str())); + } + Ok(value) +} + +/// Why reading a recorded launch failed. +enum Malformed { + /// The `KeyError`, `TypeError`, or `ValueError` that `_deserialize_launch` reports as a + /// malformed configuration. + Malformed, + /// Any other failure, which it reports as it is. + Exit(Exit), +} + +impl From for Malformed { + fn from(exit: Exit) -> Self { + Self::Exit(exit) + } +} + +impl From for Malformed { + fn from(error: crate::Error) -> Self { + Self::Exit(error.into()) + } +} + +/// Returns `str(value[name])` of the object at `id`. +fn text_member( + document: &Document, + id: NodeId, + name: &str, +) -> std::result::Result { + let value = member(document, id, name).ok_or(Malformed::Malformed)?; + py_str(document, value).ok_or(Malformed::Malformed) +} + +/// Returns `int(value)`, which `_deserialize_launch` reports as malformed unless it overflows. +fn integer(document: &Document, id: NodeId) -> std::result::Result { + py_int(document, id).map_err(|error| match error { + IntError::Invalid => Malformed::Malformed, + IntError::Overflow => { + Malformed::Exit(Exit::error("cannot convert float infinity to integer")) + } + }) +} + +/// Returns the items of the list or other iterable that Python iterates at `id`, as `for item in +/// value` sees them: a list's items, a dict's names, and a string's characters, which are no +/// mappings, so that only a list of objects reads as layers. +fn iterate(document: &Document, id: NodeId) -> std::result::Result>, Malformed> { + match document.node(id) { + Node::Array(items) => Ok(items.iter().map(|&item| Some(item)).collect()), + Node::Object(members) => Ok(members.iter().map(|_| None).collect()), + Node::String(text) => Ok(text.code_points().map(|_| None).collect()), + _ => Err(Malformed::Malformed), + } +} + +/// Returns the paths of a share's `key`, which must be a list, as `_deserialize_paths` reads +/// them. +fn paths_member( + document: &Document, + mount: NodeId, + key: &str, +) -> std::result::Result, Malformed> { + let value = member(document, mount, key).ok_or(Malformed::Malformed)?; + let Node::Array(items) = document.node(value) else { + return Err(Malformed::Malformed); + }; + items + .iter() + .map(|&item| py_str(document, item).ok_or(Malformed::Malformed)) + .collect() +} + +/// Reads one recorded share, as `_deserialize_mount` does, and checks it as +/// `SandboxMount.__post_init__` does. +fn deserialize_mount( + document: &Document, + id: NodeId, + policy: bool, + kind: bool, +) -> std::result::Result { + if !matches!(document.node(id), Node::Object(_)) { + return Err(Malformed::Malformed); + } + let guest_target = text_member(document, id, "guest_target")?; + let host_path = text_member(document, id, "host_path")?; + let access = text_member(document, id, "access")?; + let denied = paths_member(document, id, "denied_paths")?; + // Configurations written before ownership modes ran shares as the VMM. + let owner = match member(document, id, "owner") { + Some(owner) => py_str(document, owner).ok_or(Malformed::Malformed)?, + None => "vmm".to_owned(), + }; + let allowed = if policy { + paths_member(document, id, "allowed_paths")? + } else { + Vec::new() + }; + let writable = if policy { + paths_member(document, id, "writable_paths")? + } else { + Vec::new() + }; + let kind = if kind { + text_member(document, id, "kind")? + } else { + "directory".to_owned() + }; + validate_mount_target(&guest_target)?; + let access = MountAccess::from_name(&access)?; + let owner = MountOwner::from_name(&owner)?; + let kind = MountKind::from_name(&kind)?; + let policy = MountPolicy { + denied, + allowed, + writable, + }; + Ok(SandboxMount::new( + &guest_target, + &host_path, + access, + policy, + owner, + kind, + )?) +} + +/// Reads the shares of a recorded configuration, as `_deserialize_mounts` does. +fn deserialize_mounts( + document: &Document, + root: NodeId, +) -> std::result::Result, Malformed> { + let format = member(document, root, "format"); + let is_format = |expected| format.is_some_and(|format| equals_int(document, format, expected)); + let mounts_format = [ + MULTI_MOUNT_CONFIG_FORMAT, + POLICY_CONFIG_FORMAT, + FILE_MOUNT_CONFIG_FORMAT, + ] + .into_iter() + .find(|&format| is_format(format)); + if let Some(format) = mounts_format { + let mounts = member(document, root, "mounts").ok_or(Malformed::Malformed)?; + let Node::Array(items) = document.node(mounts) else { + return Err(Malformed::Malformed); + }; + return items + .iter() + .map(|&item| { + deserialize_mount( + document, + item, + format != MULTI_MOUNT_CONFIG_FORMAT, + format == FILE_MOUNT_CONFIG_FORMAT, + ) + }) + .collect(); + } + match member(document, root, "mount") { + None => Ok(Vec::new()), + Some(mount) if matches!(document.node(mount), Node::Null) => Ok(Vec::new()), + Some(mount) => Ok(vec![deserialize_mount(document, mount, false, false)?]), + } +} + +/// Reads the launch that a configuration records, as `_deserialize_launch` does, and checks it +/// against the host. +/// +/// A missing field, or a value of the wrong type, fails as `sandbox configuration is malformed`, +/// as does a layer whose role is none of `distro`, `runtime`, and `custom`, which Python would +/// accept and then leave out of the launch. A configuration whose format is not the one that its +/// shares need fails as `sandbox configuration format does not match its mounts`. +pub(crate) fn deserialize_launch(document: &Document) -> Result { + let malformed = || Exit::error("sandbox configuration is malformed"); + let launch = (|| -> std::result::Result { + let root = document.root(); + let layers_value = member(document, root, "layers").ok_or(Malformed::Malformed)?; + let mut layers = Vec::new(); + for layer in iterate(document, layers_value)? { + let layer = layer.ok_or(Malformed::Malformed)?; + if !matches!(document.node(layer), Node::Object(_)) { + return Err(Malformed::Malformed); + } + let role = text_member(document, layer, "role")?; + let path = text_member(document, layer, "path")?; + let uuid = text_member(document, layer, "uuid")?; + let role = LayerRole::from_name(&role).ok_or(Malformed::Malformed)?; + layers.push(SandboxLayer::new(role, &path, uuid)); + } + let identity = |name| -> std::result::Result { + let value = member(document, root, name).ok_or(Malformed::Malformed)?; + // An identity outside the range of IDs fails as root's does. + Ok(integer(document, value)? + .to_u32() + .filter(|&id| id != 0) + .unwrap_or(0)) + }; + let workload_identity = (identity("workload_uid")?, identity("workload_gid")?); + let scratch = text_member(document, root, "scratch")?; + let hostname = text_member(document, root, "hostname")?; + let limit = |name| -> std::result::Result, Malformed> { + let value = member(document, root, name).ok_or(Malformed::Malformed)?; + if matches!(document.node(value), Node::Null) { + return Ok(None); + } + integer(document, value).map(Some) + }; + let memory_max = limit("memory_max")?; + let pids_max = limit("pids_max")?; + let mounts = deserialize_mounts(document, root)?; + let launch = SandboxLaunch::new( + layers, + &scratch, + SandboxSettings { + hostname, + workload_identity, + memory_max, + pids_max, + mounts, + ..SandboxSettings::default() + }, + )?; + Ok(launch) + })() + .map_err(|error| match error { + Malformed::Malformed => malformed(), + Malformed::Exit(exit) => exit, + })?; + let format = member(document, document.root(), "format"); + if !format.is_some_and(|format| equals_int(document, format, config_format(launch.mounts()))) { + return Err(Exit::error( + "sandbox configuration format does not match its mounts", + )); + } + launch.validated()?; + Ok(launch) +} + +/// Returns `str(value)` of the member `name` of the configuration, failing as Python's +/// `KeyError` reads, `'NAME'`, if the member is missing. +pub(crate) fn config_text(document: &Document, name: &str) -> Result { + let value = member(document, document.root(), name).ok_or_else(|| Exit::error(repr(name)))?; + py_str(document, value).ok_or_else(|| Exit::error("sandbox configuration is malformed")) +} + +/// Returns `int(config[name])`, failing with the `KeyError`, `TypeError`, `ValueError`, or +/// `OverflowError` that Python raises. +pub(crate) fn config_int(document: &Document, name: &str) -> Result { + let value = member(document, document.root(), name).ok_or_else(|| Exit::error(repr(name)))?; + py_int(document, value).map_err(|error| match error { + IntError::Overflow => Exit::error("cannot convert float infinity to integer"), + IntError::Invalid => Exit::error(int_error(document, value)), + }) +} + +/// Returns the message of the error with which `int(value)` refuses the value at `id`. +fn int_error(document: &Document, id: NodeId) -> String { + let type_error = |name: &str| { + format!( + "int() argument must be a string, a bytes-like object or a real number, not '{name}'" + ) + }; + match document.node(id) { + Node::Null => type_error("NoneType"), + Node::Array(_) => type_error("list"), + Node::Object(_) => type_error("dict"), + Node::Float(_) => "cannot convert float NaN to integer".to_owned(), + Node::String(text) => match text.as_str() { + Some(text) => match Int::parse(text) { + Err(error) => error.message(text), + Ok(_) => String::new(), + }, + None => format!( + "invalid literal for int() with base 10: {}", + value::py_repr(document, id).unwrap_or_default() + ), + }, + Node::Integer(_) | Node::Bool(_) => String::new(), + } +} diff --git a/nvx-rs/src/host/cli/session.rs b/nvx-rs/src/host/cli/session.rs new file mode 100644 index 000000000..7f2a9ce2e --- /dev/null +++ b/nvx-rs/src/host/cli/session.rs @@ -0,0 +1,529 @@ +//! The control session of `nvx.py sandbox`, a port of `scripts/nvx_tools/control_session.py` on +//! the framing, sequencing, attachment, and transports of [`crate::host::session`]. +//! +//! It keeps the observable behavior of the Python session: +//! +//! - [`Session::connect`] retries the endpoint every 25 ms until its timeout, then attaches with +//! the capability within a fresh timeout. On Windows, it gives up at once on the errors that +//! Python does not retry. +//! - Each operation bounds its request and its response by one timeout. A transfer that times +//! out fails with `managed control request timed out` while it sends and `managed control +//! response timed out` while it receives. +//! - An endpoint that closes, as OpenVMM closes it when it shuts down, ends the operation with +//! [`Exit::EndpointClosed`], while a session that OpenVMM resets fails with `managed control +//! session was reset`. +//! - `exec` refuses invalid requests before it sends anything, with Python's messages, buffers +//! the workload's output, and accepts the exit categories of `MANAGED_EXIT_CATEGORIES`; a +//! refusal keeps its status and its category, which the guest sends in ASCII. +//! - A session whose transfer failed is closed, and refuses further requests. +//! +//! The transports also wait in slices of at most 250 ms, as Python's do, so that Ctrl-C ends a +//! wait within that time, except while a request is being sent. + +use std::io; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use std::time::Duration; + +use super::deadline::{Deadline, RETRY_INTERVAL, sleep}; +use super::{Exit, Result, check_interrupt}; +use crate::host::platform::{self, Transport}; +use crate::host::python::fs::os_error_message; +use crate::host::python::path::PurePath; +use crate::host::sandbox::options::validate_exec_environment; +use crate::host::session::{ControlSession, SessionError}; +use crate::protocol::{ + APP_ERROR, APP_EXEC, APP_EXIT, APP_FEATURES, APP_PING, APP_READY, APP_STDERR, APP_STDOUT, + APP_STOP, APP_STOPPED, CAPABILITY_LEN, GuestFeatures, MAX_OUTPUT_BYTES, UNSUPPORTED_OPERATION, + Workload, WorkloadEnvironment, encode_exec_payload, +}; + +/// The exit categories that `exec` accepts, `MANAGED_EXIT_CATEGORIES`. A cancellation, which this +/// session never requests, is not among them. +pub(crate) const EXIT_CATEGORIES: [&str; 5] = + ["exit", "timeout", "output-limit", "signal", "failed"]; +/// The most arguments of a workload, `APP_MAX_ARGUMENTS`. +const MAX_ARGUMENTS: usize = 64; +/// The longest argument and working directory, in UTF-8 bytes. +const MAX_ARGUMENT_BYTES: usize = 4096; +/// The longest that one wait of a transport blocks before it checks for Ctrl-C. +const POLL_SLICE: Duration = Duration::from_millis(250); + +/// The direction of the last transfer, for the message of a timeout. +const SENDING: u8 = 1; +/// See [`SENDING`]. +const RECEIVING: u8 = 2; + +/// What the transport observed of the failure of a transfer. +#[derive(Default)] +struct Probe { + /// [`SENDING`] or [`RECEIVING`]: the direction of the transfer in progress. + direction: AtomicU8, + /// Whether the endpoint closed the stream. + closed: AtomicBool, + /// Whether Ctrl-C ended a wait. + interrupted: AtomicBool, +} + +/// A transport that records what [`Probe`] reports, and waits in slices that Ctrl-C can end. +struct ProbedTransport { + /// The platform's transport. + inner: Box, + /// What this transport observed. + probe: Arc, +} + +/// Returns whether `error` tells that the endpoint closed the stream, as Python's +/// `_PEER_CLOSED` and `_PIPE_CLOSED_ERRORS` do. +fn closes(error: &io::Error) -> bool { + matches!( + error.kind(), + io::ErrorKind::BrokenPipe + | io::ErrorKind::ConnectionReset + | io::ErrorKind::ConnectionAborted + | io::ErrorKind::WriteZero + | io::ErrorKind::UnexpectedEof + ) || (cfg!(windows) && matches!(error.raw_os_error(), Some(109 | 232 | 233))) +} + +impl ProbedTransport { + /// Returns the error with which a wait that Ctrl-C ended fails. + fn interrupted(&self) -> io::Error { + self.probe.interrupted.store(true, Ordering::SeqCst); + io::Error::other("interrupted") + } +} + +impl Transport for ProbedTransport { + fn read(&mut self, buffer: &mut [u8], timeout: Option) -> io::Result { + self.probe.direction.store(RECEIVING, Ordering::SeqCst); + let deadline = timeout.map(|timeout| std::time::Instant::now() + timeout); + loop { + if platform::interrupted() { + return Err(self.interrupted()); + } + let left = deadline + .map(|deadline| deadline.saturating_duration_since(std::time::Instant::now())); + let slice = left.map_or(POLL_SLICE, |left| left.min(POLL_SLICE)); + match self.inner.read(buffer, Some(slice)) { + Ok(count) => { + if count == 0 { + self.probe.closed.store(true, Ordering::SeqCst); + } + return Ok(count); + } + Err(error) if error.kind() == io::ErrorKind::TimedOut => { + if left.is_some_and(|left| left <= slice) { + return Err(error); + } + } + Err(error) if error.kind() == io::ErrorKind::Interrupted => {} + Err(error) => { + if closes(&error) { + self.probe.closed.store(true, Ordering::SeqCst); + } + return Err(error); + } + } + } + } + + fn write_all(&mut self, data: &[u8], timeout: Option) -> io::Result<()> { + self.probe.direction.store(SENDING, Ordering::SeqCst); + match self.inner.write_all(data, timeout) { + Ok(()) => { + self.probe.direction.store(RECEIVING, Ordering::SeqCst); + Ok(()) + } + Err(error) => { + if closes(&error) { + self.probe.closed.store(true, Ordering::SeqCst); + } + Err(error) + } + } + } +} + +/// A workload that [`Session::exec`] runs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ExecRequest { + /// The program and its arguments. + pub(crate) arguments: Vec, + /// `--exec-timeout-ms`, as given. + pub(crate) timeout_ms: crate::host::python::builtins::Int, + /// `--cwd`. + pub(crate) cwd: Option, + /// The environment, if one was given. + pub(crate) environment: Option>, + /// `--inherit-default-environment`. + pub(crate) inherit_default_environment: bool, +} + +/// How a workload ended: `ManagedExecResult`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ExecResult { + /// The workload's status. + pub(crate) returncode: i32, + /// One of [`EXIT_CATEGORIES`]. + pub(crate) category: &'static str, + /// The workload's standard output. + pub(crate) stdout: Vec, + /// The workload's standard error. + pub(crate) stderr: Vec, +} + +/// An authenticated control session: `ControlSession` of `control_session.py`. +pub(crate) struct Session { + /// The session, until a transfer fails. + inner: Option, + /// What its transport observed. + probe: Arc, +} + +impl Session { + /// Connects to `endpoint`, which OpenVMM process `pid` serves, and attaches with + /// `capability`, as `ControlSession.connect(endpoint, capability, timeout)` does: the endpoint + /// gets `timeout` seconds to appear, and the attachment `timeout` seconds more. + /// + /// Like `nvx::host::session`, it refuses an endpoint that another process serves, which it + /// treats as one that has not appeared yet, rather than handing that process the capability. + pub(crate) fn connect( + endpoint: &str, + pid: u32, + capability: &[u8; CAPABILITY_LEN], + timeout: f64, + ) -> Result { + let path = PurePath::native(endpoint); + let deadline = Deadline::after(timeout); + let transport = loop { + check_interrupt()?; + let slice = deadline + .remaining() + .map_or(POLL_SLICE, |left| left.min(POLL_SLICE)); + match platform::connect_endpoint(path.as_str(), pid, slice) { + Ok(transport) => break transport, + Err(error) => { + if cfg!(windows) + && error + .raw_os_error() + .is_some_and(|code| raised_at_once(code as u32)) + { + return Err(Exit::error(os_error_message(&error, None))); + } + if deadline.passed() { + return Err(Exit::error(format!( + "managed control endpoint did not become available: {path}" + ))); + } + sleep(RETRY_INTERVAL)?; + } + } + }; + Self::attach(transport, capability, timeout) + } + + /// Attaches over a connected `transport` with `capability` within `timeout` seconds, as the + /// second half of [`connect`](Self::connect). + fn attach( + transport: Box, + capability: &[u8; CAPABILITY_LEN], + timeout: f64, + ) -> Result { + let probe = Arc::new(Probe::default()); + let transport = Box::new(ProbedTransport { + inner: transport, + probe: Arc::clone(&probe), + }); + let deadline = Deadline::after(timeout); + probe.direction.store(SENDING, Ordering::SeqCst); + let mut session = Self { inner: None, probe }; + let attached = ControlSession::attach(transport, capability, deadline.instant()) + .map_err(|error| session.failure(error))?; + session.inner = Some(attached); + Ok(session) + } + + /// Returns the open session, or fails as a closed `ControlSession` does. + fn session(&mut self) -> Result<&mut ControlSession> { + self.inner + .as_mut() + .ok_or_else(|| Exit::error("managed control session is closed")) + } + + /// Sends a request and closes the session if the transfer fails. + fn send(&mut self, kind: u8, request_id: u64, payload: &[u8], deadline: Deadline) -> Result { + self.probe.direction.store(SENDING, Ordering::SeqCst); + let sent = self + .session()? + .send_request(kind, request_id, payload, deadline.instant()); + sent.map_err(|error| self.fail(error)) + } + + /// Reads the next response and closes the session if the transfer fails. + fn receive(&mut self, deadline: Deadline) -> Result<(u8, u64, i32, Vec)> { + self.probe.direction.store(RECEIVING, Ordering::SeqCst); + let received = self.session()?.read_response(Some(deadline.instant())); + received.map_err(|error| self.fail(error)) + } + + /// Closes the session after a failed transfer and returns the failure. + fn fail(&mut self, error: SessionError) -> Exit { + self.inner = None; + self.failure(error) + } + + /// Returns what Python raises for `error`. + fn failure(&self, error: SessionError) -> Exit { + if self.probe.interrupted.load(Ordering::SeqCst) { + return Exit::Interrupted; + } + let closed = self.probe.closed.load(Ordering::SeqCst); + match error { + SessionError::TimedOut => { + Exit::error(if self.probe.direction.load(Ordering::SeqCst) == SENDING { + "managed control request timed out" + } else { + "managed control response timed out" + }) + } + SessionError::Closed => Exit::EndpointClosed, + SessionError::Io(error) | SessionError::Refused(error) => { + if closed || closes(&error) { + Exit::EndpointClosed + } else { + Exit::error(os_error_message(&error, None)) + } + } + SessionError::Reset => Exit::error("managed control session was reset"), + SessionError::AuthenticationFailed => { + Exit::error("control capability authentication failed") + } + SessionError::ProcessExited => Exit::EndpointClosed, + SessionError::Protocol(message) => Exit::error(protocol_message(message)), + } + } + + /// Waits until the guest agent acknowledges readiness, as `ControlSession.ping` does. + pub(crate) fn ping(&mut self, timeout: f64) -> Result { + let request_id = request_id()?; + let deadline = Deadline::after(timeout); + self.send(APP_PING, request_id, &[], deadline)?; + let (kind, response_id, status, payload) = self.receive(deadline)?; + if kind != APP_READY || response_id != request_id || status != 0 || !payload.is_empty() { + return Err(Exit::error("managed guest did not acknowledge readiness")); + } + Ok(()) + } + + /// Asks the guest agent which control features its image provides. An agent that predates + /// the request refuses it as an unsupported operation, so its image provides none. + pub(crate) fn features(&mut self, timeout: f64) -> Result { + let request_id = request_id()?; + let deadline = Deadline::after(timeout); + self.send(APP_FEATURES, request_id, &[], deadline)?; + let (kind, response_id, status, payload) = self.receive(deadline)?; + if response_id != request_id { + return Err(Exit::error( + "managed guest returned a mismatched request ID", + )); + } + match kind { + APP_READY if status == 0 => GuestFeatures::decode(&payload) + .map_err(|_| Exit::error("managed guest returned an invalid features response")), + APP_ERROR if payload == UNSUPPORTED_OPERATION => Ok(GuestFeatures::NONE), + _ => Err(Exit::error( + "managed guest returned an invalid features response", + )), + } + } + + /// Runs a workload and returns how it ended, as `ControlSession.exec` does: the request and + /// every response must arrive within `response_timeout` seconds of each other's start. + pub(crate) fn exec( + &mut self, + request: &ExecRequest, + response_timeout: f64, + ) -> Result { + if !(response_timeout > 0.0 && response_timeout < f64::INFINITY) { + return Err(Exit::error( + "managed exec response timeout must be positive and finite", + )); + } + let arguments = &request.arguments; + if !(1..=MAX_ARGUMENTS).contains(&arguments.len()) { + return Err(Exit::error("managed exec requires 1 through 64 arguments")); + } + for argument in arguments { + if argument.is_empty() || argument.len() > MAX_ARGUMENT_BYTES || argument.contains('\0') + { + return Err(Exit::error( + "managed exec argument is empty or exceeds 4096 bytes", + )); + } + } + if !arguments[0].starts_with('/') { + return Err(Exit::error("managed exec entrypoint must be absolute")); + } + let timeout_ms = request + .timeout_ms + .to_u32() + .ok_or_else(|| Exit::error("managed exec timeout must be 0 through 4294967295 ms"))?; + if let Some(cwd) = &request.cwd + && (cwd.is_empty() + || cwd.len() > MAX_ARGUMENT_BYTES + || cwd.contains('\0') + || !cwd.starts_with('/')) + { + return Err(Exit::error( + "managed exec working directory must be an absolute path of at most 4096 bytes", + )); + } + if let Some(entries) = &request.environment { + validate_exec_environment(entries)?; + } + let environment = match &request.environment { + None => WorkloadEnvironment::Default, + Some(entries) if request.inherit_default_environment => { + WorkloadEnvironment::Layered(entries) + } + Some(entries) => WorkloadEnvironment::Replaced(entries), + }; + let workload = Workload { + argv: arguments.clone(), + timeout_ms, + cwd: request.cwd.as_deref(), + environment, + }; + // The checks above leave only the size of the request for the encoder to refuse. + let payload = encode_exec_payload(&workload) + .map_err(|_| Exit::error("managed exec request exceeds the protocol limit"))?; + let request_id = request_id()?; + let deadline = Deadline::after(response_timeout); + self.send(APP_EXEC, request_id, &payload, deadline)?; + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + loop { + let (kind, response_id, status, response) = self.receive(deadline)?; + if response_id != request_id { + return Err(Exit::error( + "managed guest returned a mismatched request ID", + )); + } + match kind { + APP_STDOUT | APP_STDERR => { + // The guest agent forwards at most this much output, so more breaks the + // protocol, as `nvx::host::instance` also finds. + if stdout.len() + stderr.len() + response.len() > MAX_OUTPUT_BYTES { + return Err(Exit::error(format!( + "managed guest exceeded the {MAX_OUTPUT_BYTES}-byte output limit" + ))); + } + let output = if kind == APP_STDOUT { + &mut stdout + } else { + &mut stderr + }; + output.extend_from_slice(&response); + } + APP_EXIT => { + if !response.is_ascii() { + return Err(Exit::error( + "managed guest returned an invalid exit category", + )); + } + let category = EXIT_CATEGORIES + .iter() + .find(|category| category.as_bytes() == response) + .ok_or_else(|| { + Exit::error("managed guest returned an unsupported exit category") + })?; + return Ok(ExecResult { + returncode: status, + category, + stdout, + stderr, + }); + } + APP_ERROR => { + // `bytes.decode("ascii", "replace")`. + let category: String = response + .iter() + .map(|&byte| { + if byte.is_ascii() { + char::from(byte) + } else { + '\u{fffd}' + } + }) + .collect(); + return Err(Exit::error(format!( + "managed guest rejected exec (status={status}, category={category})" + ))); + } + _ => { + return Err(Exit::error( + "managed guest returned an invalid exec response", + )); + } + } + } + } + + /// Asks the guest agent to shut the VM down, as `ControlSession.stop` does. + pub(crate) fn stop(&mut self, timeout: f64) -> Result { + let request_id = request_id()?; + let deadline = Deadline::after(timeout); + self.send(APP_STOP, request_id, &[], deadline)?; + let (kind, response_id, status, payload) = self.receive(deadline)?; + if kind != APP_STOPPED || response_id != request_id || status != 0 || !payload.is_empty() { + return Err(Exit::error("managed guest did not acknowledge stop")); + } + Ok(()) + } +} + +/// Returns Python's message for a violation of the protocol that `nvx::host::session` reports. +/// +/// The two sessions check the same records in the same order, and their messages differ only +/// where Python folds a case into a broader one. +fn protocol_message(message: String) -> String { + match message.as_str() { + "control attach response carried a payload" => { + "control attach response carried an invalid payload".to_owned() + } + "control endpoint returned an oversized outer record" => { + "control endpoint returned an invalid outer record".to_owned() + } + // Python checks the length of the frame with the record that carries it. + "control endpoint returned a truncated application frame" => { + "control endpoint returned an invalid data record".to_owned() + } + _ => message, + } +} + +/// Returns whether Python's `_NamedPipeStream.connect` raises the Windows error `code` at once +/// instead of retrying: those that CPython maps to an `errno` other than `ENOENT`, `EACCES`, +/// `EBUSY`, `EAGAIN`, and `EINVAL`, its default. +fn raised_at_once(code: u32) -> bool { + matches!( + code, + // E2BIG, ENOEXEC, EBADF, ECHILD, and ENOMEM. + 10 | 11 | 188 + ..=202 | 6 | 114 | 130 | 128 | 129 | 7 | 8 | 9 | 1816 + // EEXIST, EXDEV, ENOTDIR, EMFILE, ENOSPC, EPIPE, ENOTEMPTY, and EILSEQ. + | 80 | 183 | 17 | 267 | 4 | 112 | 109 | 232 | 145 | 1113 + ) +} + +/// Returns a request ID: `secrets.randbits(64) or 1`. +fn request_id() -> Result { + let mut bytes = [0u8; 8]; + getrandom::fill(&mut bytes) + .map_err(|error| Exit::error(format!("cannot generate a request ID: {error}")))?; + Ok(u64::from_le_bytes(bytes).max(1)) +} + +#[cfg(test)] +mod tests; diff --git a/nvx-rs/src/host/cli/session/tests.rs b/nvx-rs/src/host/cli/session/tests.rs new file mode 100644 index 000000000..760ed503f --- /dev/null +++ b/nvx-rs/src/host/cli/session/tests.rs @@ -0,0 +1,1024 @@ +//! The tests of `scripts/test_control_session.py`, on in-memory streams and on the endpoints of +//! each host, except those that the tests of [`crate::host::session`] cover. + +use std::collections::VecDeque; +use std::sync::Mutex; +use std::sync::mpsc::{self, RecvTimeoutError, TrySendError}; +use std::thread; +use std::time::Instant; + +use super::*; +use crate::host::python::builtins::Int; +use crate::protocol::{ + EXEC_CWD_PRESENT, EXEC_ENVIRONMENT_PRESENT, EXEC_EXTENDED, EXEC_INHERIT_DEFAULT_ENV, + OUTER_DATA, OUTER_HEADER_LEN, OUTER_HOST_ATTACH, OUTER_READY, OUTER_RESET, decode_app, + decode_outer_header, encode_app, encode_outer, +}; + +const CAPABILITY: [u8; CAPABILITY_LEN] = [7; CAPABILITY_LEN]; +const INSTANCE: [u8; 16] = [0x11; 16]; + +/// What a [`Recording`] transport records: the direction and timeout of each transfer. +type Transfers = Arc)>>>; + +/// One end of an in-memory stream, like one socket of `socket.socketpair()`: it reads what the +/// other end writes, reads the end of the stream once the other end is gone, and holds at most +/// `capacity` unread writes, after which a write waits for the reader until its timeout. +struct End { + incoming: mpsc::Receiver>, + outgoing: mpsc::SyncSender>, + buffer: VecDeque, +} + +fn stream_pair(capacity: usize) -> (End, End) { + let (to_guest, from_host) = mpsc::sync_channel(capacity); + let (to_host, from_guest) = mpsc::sync_channel(64); + ( + End { + incoming: from_guest, + outgoing: to_guest, + buffer: VecDeque::new(), + }, + End { + incoming: from_host, + outgoing: to_host, + buffer: VecDeque::new(), + }, + ) +} + +impl Transport for End { + fn read(&mut self, buffer: &mut [u8], timeout: Option) -> io::Result { + if self.buffer.is_empty() { + let chunk = match timeout { + Some(timeout) => self.incoming.recv_timeout(timeout), + None => self + .incoming + .recv() + .map_err(|_| RecvTimeoutError::Disconnected), + }; + match chunk { + Ok(chunk) => self.buffer.extend(chunk), + Err(RecvTimeoutError::Timeout) => return Err(io::ErrorKind::TimedOut.into()), + Err(RecvTimeoutError::Disconnected) => return Ok(0), + } + } + let count = buffer.len().min(self.buffer.len()); + for (byte, value) in buffer.iter_mut().zip(self.buffer.drain(..count)) { + *byte = value; + } + Ok(count) + } + + fn write_all(&mut self, data: &[u8], timeout: Option) -> io::Result<()> { + let deadline = timeout.map(|timeout| Instant::now() + timeout); + let mut chunk = data.to_vec(); + loop { + match self.outgoing.try_send(chunk) { + Ok(()) => return Ok(()), + Err(TrySendError::Disconnected(_)) => { + return Err(io::ErrorKind::BrokenPipe.into()); + } + Err(TrySendError::Full(returned)) => { + if deadline.is_some_and(|deadline| Instant::now() >= deadline) { + return Err(io::ErrorKind::TimedOut.into()); + } + chunk = returned; + thread::sleep(Duration::from_millis(5)); + } + } + } + } +} + +/// The guest side of a session: OpenVMM's endpoint and the guest agent behind it. +struct Guest { + stream: T, + sequence: u64, +} + +impl Guest { + fn new(stream: T) -> Self { + Self { + stream, + sequence: 0, + } + } + + /// Reads `length` bytes, or returns `None` at the end of the stream. + fn read_exact(&mut self, length: usize) -> Option> { + let mut data = vec![0; length]; + let mut filled = 0; + while filled < length { + match self + .stream + .read(&mut data[filled..], Some(Duration::from_secs(10))) + { + Ok(0) | Err(_) => return None, + Ok(count) => filled += count, + } + } + Some(data) + } + + /// Reads one outer record and returns its type and payload. + fn record(&mut self) -> Option<(u8, Vec)> { + let header = self.read_exact(OUTER_HEADER_LEN)?; + let header = decode_outer_header(header.as_slice().try_into().unwrap()).unwrap(); + Some((header.record_type, self.read_exact(header.length)?)) + } + + /// Authenticates the host as OpenVMM's endpoint does. + fn accept(&mut self) { + let (record_type, payload) = self.record().unwrap(); + assert_eq!( + (record_type, payload.as_slice()), + (OUTER_HOST_ATTACH, &CAPABILITY[..]) + ); + let ready = encode_outer(OUTER_READY, &INSTANCE, 1, 0, &[]).unwrap(); + self.stream.write_all(&ready, None).unwrap(); + } + + /// Reads one application request and returns its kind, request ID, and payload. + fn request(&mut self) -> (u8, u64, Vec) { + let (record_type, frame) = self.record().unwrap(); + assert_eq!(record_type, OUTER_DATA); + let frame = decode_app(&frame).unwrap(); + (frame.kind, frame.request_id, frame.payload.to_vec()) + } + + fn send(&mut self, kind: u8, request_id: u64, status: i32, payload: &[u8]) { + self.sequence += 1; + let frame = encode_app(kind, request_id, status, payload); + let record = encode_outer(OUTER_DATA, &INSTANCE, 1, self.sequence, &frame).unwrap(); + self.stream.write_all(&record, None).unwrap(); + } +} + +/// Returns a session attached over an in-memory stream to a guest that `serve` plays. +fn session_with( + serve: impl FnOnce(&mut Guest) + Send + 'static, +) -> (Session, thread::JoinHandle<()>) { + let (host, guest) = stream_pair(64); + let worker = thread::spawn(move || { + let mut guest = Guest::new(guest); + guest.accept(); + serve(&mut guest); + }); + let session = Session::attach(Box::new(host), &CAPABILITY, 5.0).unwrap(); + (session, worker) +} + +fn request(arguments: &[&str]) -> ExecRequest { + ExecRequest { + arguments: arguments + .iter() + .map(|argument| (*argument).to_owned()) + .collect(), + timeout_ms: Int::from(0_u32), + cwd: None, + environment: None, + inherit_default_environment: false, + } +} + +/// Returns the message of a failure. +fn message(result: Result) -> String { + match result { + Ok(value) => panic!("unexpected success: {value:?}"), + Err(exit) => exit + .message() + .map_or_else(|| format!("{exit:?}"), str::to_owned), + } +} + +/// Returns the payload of the exec request of `workload`, which the guest answers. +fn exec_payload(workload: ExecRequest) -> Vec { + let (sender, receiver) = mpsc::channel(); + let (mut session, worker) = session_with(move |guest| { + let (kind, request_id, payload) = guest.request(); + assert_eq!(kind, APP_EXEC); + sender.send(payload).unwrap(); + guest.send(APP_EXIT, request_id, 0, b"exit"); + }); + session.exec(&workload, 5.0).unwrap(); + worker.join().unwrap(); + receiver.recv().unwrap() +} + +fn u16_at(payload: &[u8], offset: usize) -> u16 { + u16::from_le_bytes(payload[offset..offset + 2].try_into().unwrap()) +} + +fn u32_at(payload: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(payload[offset..offset + 4].try_into().unwrap()) +} + +/// Returns the length-prefixed values that start at `offset`, and the offset after them. +fn values(payload: &[u8], mut offset: usize, count: usize) -> (Vec>, usize) { + let mut values = Vec::new(); + for _ in 0..count { + let length = u32_at(payload, offset) as usize; + values.push(payload[offset + 4..offset + 4 + length].to_vec()); + offset += 4 + length; + } + (values, offset) +} + +#[test] +fn exec_streams_output_and_returns_bounded_status() { + let (mut session, worker) = session_with(|guest| { + let (kind, request_id, payload) = guest.request(); + assert_eq!(kind, APP_EXEC); + // The legacy request: the timeout, the argument count, and no extension. + assert_eq!( + ( + u32_at(&payload, 0), + u16_at(&payload, 4), + u16_at(&payload, 6) + ), + (5000, 3, 0) + ); + guest.send(APP_STDOUT, request_id, 0, b"hello"); + guest.send(APP_STDERR, request_id, 0, b"warning"); + guest.send(APP_EXIT, request_id, 7, b"exit"); + }); + let mut workload = request(&["/bin/sh", "-c", "echo hello"]); + workload.timeout_ms = Int::from(5000_u32); + let result = session.exec(&workload, 5.0).unwrap(); + worker.join().unwrap(); + assert_eq!( + result, + ExecResult { + returncode: 7, + category: "exit", + stdout: b"hello".to_vec(), + stderr: b"warning".to_vec(), + } + ); +} + +#[test] +fn exec_rejects_unbounded_or_relative_arguments() { + let (mut session, worker) = session_with(|_| {}); + assert_eq!( + message(session.exec(&request(&["relative"]), 1.0)), + "managed exec entrypoint must be absolute" + ); + assert_eq!( + message(session.exec(&request(&["/bin/true"; 65]), 1.0)), + "managed exec requires 1 through 64 arguments" + ); + for argument in ["", "a\0b", &"a".repeat(4097)] { + assert_eq!( + message(session.exec(&request(&["/bin/true", argument]), 1.0)), + "managed exec argument is empty or exceeds 4096 bytes" + ); + } + drop(session); + worker.join().unwrap(); +} + +#[test] +fn exec_encodes_extended_cwd_and_environment_exactly() { + let environment = ["EMPTY=", "SPACED=a b", "EQUALS=a=b", "UTF8=\u{2603}"]; + let cwd = "/tmp/space \u{2603}"; + let payload = exec_payload(ExecRequest { + arguments: ["/bin/sh", "-c", "printf exact"] + .map(str::to_owned) + .to_vec(), + timeout_ms: Int::from(0xFFFF_FFFF_u32), + cwd: Some(cwd.to_owned()), + environment: Some(environment.map(str::to_owned).to_vec()), + inherit_default_environment: false, + }); + assert_eq!( + ( + u32_at(&payload, 0), + u16_at(&payload, 4), + u16_at(&payload, 6), + u16_at(&payload, 8), + u16_at(&payload, 10), + u32_at(&payload, 12) as usize + ), + ( + 0xFFFF_FFFF, + 3, + EXEC_EXTENDED, + EXEC_CWD_PRESENT | EXEC_ENVIRONMENT_PRESENT, + 4, + cwd.len() + ) + ); + let (arguments, offset) = values(&payload, 16, 3); + assert_eq!(arguments, [&b"/bin/sh"[..], b"-c", b"printf exact"]); + assert_eq!(&payload[offset..offset + cwd.len()], cwd.as_bytes()); + let (entries, end) = values(&payload, offset + cwd.len(), 4); + assert_eq!(entries, environment.map(|entry| entry.as_bytes().to_vec())); + assert_eq!(end, payload.len()); +} + +fn environment_payload(environment: Option<&[&str]>, inherit: bool) -> Vec { + let mut workload = request(&["/bin/true"]); + workload.environment = + environment.map(|entries| entries.iter().map(|entry| (*entry).to_owned()).collect()); + workload.inherit_default_environment = inherit; + exec_payload(workload) +} + +#[test] +fn exec_distinguishes_omitted_and_empty_environment() { + let omitted = environment_payload(None, false); + let empty = environment_payload(Some(&[]), false); + assert_eq!(u16_at(&omitted, 6), 0); + assert_eq!(u16_at(&empty, 6), EXEC_EXTENDED); + assert_eq!(u16_at(&empty, 8), EXEC_ENVIRONMENT_PRESENT); + assert_eq!(u16_at(&empty, 10), 0); +} + +#[test] +fn exec_layers_only_a_supplied_environment_over_the_defaults() { + let layered_flags = EXEC_ENVIRONMENT_PRESENT | EXEC_INHERIT_DEFAULT_ENV; + let fields = |payload: &[u8]| { + ( + u16_at(payload, 6), + u16_at(payload, 8), + u16_at(payload, 10), + u32_at(payload, 12), + ) + }; + let layered = environment_payload(Some(&["A=1"]), true); + assert_eq!(fields(&layered), (EXEC_EXTENDED, layered_flags, 1, 0)); + assert!(layered.ends_with(b"\x03\x00\x00\x00A=1")); + let empty = environment_payload(Some(&[]), true); + assert_eq!(fields(&empty), (EXEC_EXTENDED, layered_flags, 0, 0)); + let replaced = environment_payload(Some(&["A=1"]), false); + assert_eq!(u16_at(&replaced, 8), EXEC_ENVIRONMENT_PRESENT); + // An omitted environment already is the default one, so the request stays the legacy one, + // which the guest agent accepts without an environment. + assert_eq!( + environment_payload(None, true), + environment_payload(None, false) + ); +} + +#[test] +fn exec_accepts_full_uint32_timeout_range_without_waiting() { + for timeout in [0_u32, 3_600_001, 86_400_000, 0xFFFF_FFFF] { + let mut workload = request(&["/bin/true"]); + workload.timeout_ms = Int::from(timeout); + let started = Instant::now(); + assert_eq!(u32_at(&exec_payload(workload), 0), timeout); + assert!(started.elapsed() < Duration::from_secs(5)); + } +} + +/// Returns a session whose guest reports how many bytes reach it before the end of the stream. +fn silent_session() -> (Session, thread::JoinHandle<()>, mpsc::Receiver) { + let (sender, receiver) = mpsc::channel(); + let (session, worker) = session_with(move |guest| { + let mut buffer = [0u8; 64]; + let count = guest.stream.read(&mut buffer, None).unwrap_or(0); + sender.send(count).unwrap(); + }); + (session, worker, receiver) +} + +#[test] +fn exec_rejects_invalid_timeout_and_execution_fields_before_sending() { + let (mut session, worker, received) = silent_session(); + for timeout in [Int::from(-1_i64), Int::parse("4294967296").unwrap()] { + let mut workload = request(&["/bin/true"]); + workload.timeout_ms = timeout; + assert_eq!( + message(session.exec(&workload, 1.0)), + "managed exec timeout must be 0 through 4294967295 ms" + ); + } + for cwd in [ + "", + "relative", + "/bad\0path", + &format!("/{}", "a".repeat(4096)), + ] { + let mut workload = request(&["/bin/true"]); + workload.cwd = Some(cwd.to_owned()); + assert_eq!( + message(session.exec(&workload, 1.0)), + "managed exec working directory must be an absolute path of at most 4096 bytes" + ); + } + let invalid = "managed exec environment entries must be non-empty KEY=VALUE strings of at \ + most 4096 bytes"; + for (environment, expected) in [ + (&["MISSING_EQUALS"][..], invalid), + (&["=empty-name"][..], invalid), + ( + &["DUP=1", "DUP=2"][..], + "managed exec environment contains duplicate key: DUP", + ), + ] { + let mut workload = request(&["/bin/true"]); + workload.environment = Some( + environment + .iter() + .map(|entry| (*entry).to_owned()) + .collect(), + ); + assert_eq!(message(session.exec(&workload, 1.0)), expected); + } + // Nothing reached the guest, which reads the end of the stream. + drop(session); + worker.join().unwrap(); + assert_eq!(received.recv().unwrap(), 0); +} + +#[test] +fn exec_rejects_invalid_response_timeout_before_sending() { + let (mut session, worker, received) = silent_session(); + for timeout in [0.0, -1.0, f64::NAN, f64::INFINITY] { + assert_eq!( + message(session.exec(&request(&["/bin/true"]), timeout)), + "managed exec response timeout must be positive and finite" + ); + } + drop(session); + worker.join().unwrap(); + assert_eq!(received.recv().unwrap(), 0); +} + +#[test] +fn exec_rejects_unknown_exit_category() { + // A cancellation, which this session never requests, is not among the categories. + for category in [&b"guest-provided-detail"[..], b"cancelled"] { + let (mut session, worker) = session_with(move |guest| { + let (_, request_id, _) = guest.request(); + guest.send(APP_EXIT, request_id, 125, category); + }); + assert_eq!( + message(session.exec(&request(&["/bin/true"]), 5.0)), + "managed guest returned an unsupported exit category" + ); + worker.join().unwrap(); + } + let (mut session, worker) = session_with(|guest| { + let (_, request_id, _) = guest.request(); + guest.send(APP_EXIT, request_id, 0, b"\xffexit"); + }); + assert_eq!( + message(session.exec(&request(&["/bin/true"]), 5.0)), + "managed guest returned an invalid exit category" + ); + worker.join().unwrap(); +} + +#[test] +fn exec_refusal_keeps_status_category_and_diagnostic() { + let (mut session, worker) = session_with(|guest| { + let (_, request_id, _) = guest.request(); + guest.send( + APP_STDERR, + request_id, + 0, + b"nvx-managed-agent: cannot enter working directory /missing: No such file or \ + directory\n", + ); + guest.send(APP_ERROR, request_id, 2, b"cwd-failed"); + }); + let mut workload = request(&["/bin/true"]); + workload.cwd = Some("/missing".to_owned()); + assert_eq!( + message(session.exec(&workload, 5.0)), + "managed guest rejected exec (status=2, category=cwd-failed)" + ); + worker.join().unwrap(); + // A category that is not ASCII is decoded with replacement characters. + let (mut session, worker) = session_with(|guest| { + let (_, request_id, _) = guest.request(); + guest.send(APP_ERROR, request_id, 5, b"bad\xff"); + }); + assert_eq!( + message(session.exec(&request(&["/bin/true"]), 5.0)), + "managed guest rejected exec (status=5, category=bad\u{fffd})" + ); + worker.join().unwrap(); +} + +#[test] +fn session_reset_is_not_a_closed_endpoint() { + // A reset comes from an OpenVMM that still serves the endpoint, so it must not look like the + // closure with which OpenVMM shuts down. + let (mut session, worker) = session_with(|guest| { + let _ = guest.request(); + let reset = encode_outer(OUTER_RESET, &INSTANCE, 1, 0, &[]).unwrap(); + guest.stream.write_all(&reset, None).unwrap(); + let _ = guest.record(); + }); + let failure = session.ping(5.0).unwrap_err(); + assert!( + matches!(&failure, Exit::Error(text) if text == "managed control session was reset"), + "{failure:?}" + ); + drop(session); + worker.join().unwrap(); +} + +/// A guest transport that answers each request at once, PING with READY, EXEC with an exit, and +/// STOP with STOPPED, records the direction and timeout of each transfer, and from the transfer +/// `from` on fails each transfer in the direction `failure` by timing out: the +/// `_RecordingStream` of the Python tests. +struct Recording { + responses: VecDeque, + sequence: u64, + transfers: Transfers, + failure: Option<(&'static str, usize)>, +} + +impl Recording { + fn new(failure: Option<(&'static str, usize)>) -> (Self, Transfers) { + let transfers = Transfers::default(); + let transport = Self { + responses: VecDeque::new(), + sequence: 0, + transfers: Arc::clone(&transfers), + failure, + }; + (transport, transfers) + } + + fn transfer(&mut self, kind: &'static str, timeout: Option) -> io::Result<()> { + let mut transfers = self.transfers.lock().unwrap(); + let index = transfers.len(); + transfers.push((kind, timeout)); + match self.failure { + Some((failing, from)) if failing == kind && index >= from => { + Err(io::ErrorKind::TimedOut.into()) + } + _ => Ok(()), + } + } +} + +impl Transport for Recording { + fn read(&mut self, buffer: &mut [u8], timeout: Option) -> io::Result { + self.transfer("read", timeout)?; + let count = buffer.len().min(self.responses.len()); + for (byte, value) in buffer.iter_mut().zip(self.responses.drain(..count)) { + *byte = value; + } + Ok(count) + } + + fn write_all(&mut self, data: &[u8], timeout: Option) -> io::Result<()> { + self.transfer("write", timeout)?; + let header = decode_outer_header(data[..OUTER_HEADER_LEN].try_into().unwrap()).unwrap(); + let response = if header.record_type == OUTER_HOST_ATTACH { + encode_outer(OUTER_READY, &INSTANCE, 1, 0, &[]).unwrap() + } else { + let request = decode_app(&data[OUTER_HEADER_LEN..]).unwrap(); + let (kind, payload) = match request.kind { + APP_PING => (APP_READY, &b""[..]), + APP_EXEC => (APP_EXIT, &b"exit"[..]), + _ => (APP_STOPPED, &b""[..]), + }; + self.sequence += 1; + let frame = encode_app(kind, request.request_id, 0, payload); + encode_outer(OUTER_DATA, &INSTANCE, 1, self.sequence, &frame).unwrap() + }; + self.responses.extend(response); + Ok(()) + } +} + +#[test] +fn operations_bound_their_requests_by_their_deadlines() { + // Issue #464: an operation's timeout bounds sending its request as well as receiving its + // response, so attach, ping, exec, and stop each write within the time in which they then + // read: no transfer gets more than what is left of its operation's timeout. + let (transport, transfers) = Recording::new(None); + let mut session = Session::attach(Box::new(transport), &CAPABILITY, 7.0).unwrap(); + let mut operations = vec![(7.0, transfers.lock().unwrap().split_off(0))]; + session.ping(5.0).unwrap(); + operations.push((5.0, transfers.lock().unwrap().split_off(0))); + session.exec(&request(&["/bin/true"]), 3.0).unwrap(); + operations.push((3.0, transfers.lock().unwrap().split_off(0))); + session.stop(2.0).unwrap(); + operations.push((2.0, transfers.lock().unwrap().split_off(0))); + for (timeout, recorded) in operations { + let kinds: Vec<_> = recorded.iter().map(|(kind, _)| *kind).collect(); + assert_eq!(kinds.first(), Some(&"write"), "{kinds:?}"); + assert!(kinds[1..].iter().all(|kind| *kind == "read"), "{kinds:?}"); + for (kind, left) in recorded { + let left = left.unwrap().as_secs_f64(); + // Like Python's, reads wait in slices of at most 250 ms, so that Ctrl-C ends them. + let (least, most) = if kind == "write" { + (timeout - 1.0, timeout) + } else { + (0.0, 0.25) + }; + assert!(left > least && left <= most, "{kind}: {left} of {timeout}"); + } + } +} + +#[test] +fn failed_transfer_closes_the_session() { + // A transfer that fails, as by timing out, may cut its record short, after which the + // endpoint would frame every later record wrongly. + for (failure, expected) in [ + ("write", "managed control request timed out"), + ("read", "managed control response timed out"), + ] { + // The attachment's write and read succeed. + let (transport, transfers) = Recording::new(Some((failure, 2))); + let mut session = Session::attach(Box::new(transport), &CAPABILITY, 5.0).unwrap(); + assert_eq!(message(session.ping(5.0)), expected); + let count = transfers.lock().unwrap().len(); + assert_eq!( + message(session.exec(&request(&["/bin/true"]), 5.0)), + "managed control session is closed" + ); + assert_eq!( + message(session.stop(5.0)), + "managed control session is closed" + ); + assert_eq!(transfers.lock().unwrap().len(), count); + } +} + +#[test] +fn request_to_a_stalled_endpoint_times_out_and_closes_the_session() { + // Issue #464: a request that the endpoint does not read must not hold the operation past its + // timeout. + let (host, guest) = stream_pair(0); + let (release, released) = mpsc::channel::<()>(); + let worker = thread::spawn(move || { + let mut guest = Guest::new(guest); + guest.accept(); + // The endpoint reads nothing more until the client gives up. + let _ = released.recv_timeout(Duration::from_secs(10)); + }); + let mut session = Session::attach(Box::new(host), &CAPABILITY, 5.0).unwrap(); + let started = Instant::now(); + assert_eq!( + message(session.exec(&request(&["/bin/true"]), 0.25)), + "managed control request timed out" + ); + let elapsed = started.elapsed(); + assert!(elapsed >= Duration::from_millis(250), "{elapsed:?}"); + assert!(elapsed < Duration::from_millis(2250), "{elapsed:?}"); + assert_eq!( + message(session.ping(5.0)), + "managed control session is closed" + ); + release.send(()).unwrap(); + worker.join().unwrap(); +} + +#[test] +fn response_that_does_not_arrive_times_out() { + let (mut session, worker) = session_with(|guest| { + let _ = guest.request(); + // The guest answers nothing until the host gives up. + let _ = guest.record(); + }); + let started = Instant::now(); + assert_eq!( + message(session.ping(0.2)), + "managed control response timed out" + ); + assert!(started.elapsed() < Duration::from_secs(2)); + drop(session); + worker.join().unwrap(); +} + +#[test] +fn a_closed_endpoint_ends_the_operation_with_its_own_error() { + // OpenVMM closes the endpoint as it tears the VM down, which start and stop tell apart from + // other failures. + let (mut session, worker) = session_with(|guest| { + let _ = guest.record(); + }); + let failure = session.ping(5.0).unwrap_err(); + worker.join().unwrap(); + assert!(matches!(failure, Exit::EndpointClosed), "{failure:?}"); + assert_eq!(failure.message(), Some("managed control endpoint closed")); + // So does a write to an endpoint that is gone. + let (mut session, worker) = session_with(|_| {}); + worker.join().unwrap(); + let failure = session.stop(5.0).unwrap_err(); + assert!(matches!(failure, Exit::EndpointClosed), "{failure:?}"); +} + +#[test] +fn output_beyond_the_guest_limit_breaks_the_protocol() { + // The guest agent forwards at most 1 MiB of output, so a guest that sends more misbehaves. + let (mut session, worker) = session_with(|guest| { + let (_, request_id, _) = guest.request(); + let chunk = vec![b'x'; 60_000]; + for _ in 0..18 { + guest.send(APP_STDOUT, request_id, 0, &chunk); + } + guest.send(APP_EXIT, request_id, 0, b"exit"); + }); + assert_eq!( + message(session.exec(&request(&["/bin/true"]), 5.0)), + format!("managed guest exceeded the {MAX_OUTPUT_BYTES}-byte output limit") + ); + drop(session); + worker.join().unwrap(); +} + +#[test] +fn features_of_a_guest_that_predates_them_are_none() { + let (mut session, worker) = session_with(|guest| { + let (kind, request_id, _) = guest.request(); + assert_eq!(kind, APP_FEATURES); + guest.send(APP_ERROR, request_id, 95, UNSUPPORTED_OPERATION); + let (_, request_id, _) = guest.request(); + guest.send( + APP_READY, + request_id, + 0, + &GuestFeatures::SANDBOX_AGENT.encode(), + ); + let (_, request_id, _) = guest.request(); + guest.send(APP_ERROR, request_id, 22, b"invalid-request"); + }); + assert_eq!(session.features(5.0).unwrap(), GuestFeatures::NONE); + assert_eq!(session.features(5.0).unwrap(), GuestFeatures::SANDBOX_AGENT); + assert_eq!( + message(session.features(5.0)), + "managed guest returned an invalid features response" + ); + worker.join().unwrap(); +} + +#[test] +fn protocol_violations_keep_the_messages_of_python() { + let (mut session, worker) = session_with(|guest| { + let (_, request_id, _) = guest.request(); + guest.send(APP_READY, request_id ^ 1, 0, &[]); + }); + assert_eq!( + message(session.ping(5.0)), + "managed guest did not acknowledge readiness" + ); + worker.join().unwrap(); + let (mut session, worker) = session_with(|guest| { + let (_, request_id, _) = guest.request(); + guest.send(APP_STDOUT, request_id ^ 1, 0, b"x"); + }); + assert_eq!( + message(session.exec(&request(&["/bin/true"]), 5.0)), + "managed guest returned a mismatched request ID" + ); + worker.join().unwrap(); + let (mut session, worker) = session_with(|guest| { + let (_, request_id, _) = guest.request(); + guest.send(APP_READY, request_id, 0, b""); + }); + assert_eq!( + message(session.stop(5.0)), + "managed guest did not acknowledge stop" + ); + worker.join().unwrap(); +} + +/// The endpoints of POSIX hosts: Unix-domain sockets that this process serves. +#[cfg(unix)] +mod endpoints { + use std::io::{Read, Write}; + use std::os::unix::net::{UnixListener, UnixStream}; + + use super::*; + + /// The server side of a Unix-domain socket. + struct Server(UnixStream); + + impl Transport for Server { + fn read(&mut self, buffer: &mut [u8], timeout: Option) -> io::Result { + self.0.set_read_timeout(timeout)?; + self.0.read(buffer) + } + + fn write_all(&mut self, data: &[u8], _timeout: Option) -> io::Result<()> { + self.0.write_all(data) + } + } + + /// Serves `serve` on a fresh endpoint and returns a session connected to it. + fn connected( + serve: impl FnOnce(&mut Guest) + Send + 'static, + ) -> (Session, thread::JoinHandle<()>, tempfile::TempDir) { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("control.sock"); + let listener = UnixListener::bind(&path).unwrap(); + let worker = thread::spawn(move || { + let (stream, _) = listener.accept().unwrap(); + let mut guest = Guest::new(Server(stream)); + guest.accept(); + serve(&mut guest); + }); + let session = + Session::connect(path.to_str().unwrap(), std::process::id(), &CAPABILITY, 5.0).unwrap(); + (session, worker, directory) + } + + #[test] + fn read_reports_a_closed_endpoint() { + let (mut session, worker, _directory) = connected(|guest| { + let _ = guest.record(); + }); + let failure = session.ping(5.0).unwrap_err(); + worker.join().unwrap(); + assert!(matches!(failure, Exit::EndpointClosed), "{failure:?}"); + } + + #[test] + fn reset_and_broken_pipe_report_a_closed_endpoint() { + // OpenVMM may close the socket before it reads everything that the client sent, which + // resets the client's reads, and a later write fails with a broken pipe. + let (mut session, worker, _directory) = connected(|guest| { + let _ = guest.read_exact(OUTER_HEADER_LEN); + }); + let mut workload = request(&["/bin/true"]); + workload + .arguments + .extend(std::iter::repeat_n("a".repeat(4000), 10)); + let failure = session.exec(&workload, 5.0).unwrap_err(); + worker.join().unwrap(); + assert!(matches!(failure, Exit::EndpointClosed), "{failure:?}"); + let (mut session, worker, _directory) = connected(|_| {}); + worker.join().unwrap(); + let failure = session.stop(5.0).unwrap_err(); + assert!(matches!(failure, Exit::EndpointClosed), "{failure:?}"); + } + + #[test] + fn endpoints_that_never_appear_time_out_with_their_path() { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("control.sock"); + let started = Instant::now(); + let failure = + Session::connect(path.to_str().unwrap(), std::process::id(), &CAPABILITY, 0.2) + .err() + .unwrap(); + assert!(started.elapsed() >= Duration::from_millis(200)); + assert_eq!( + failure.message().unwrap(), + format!( + "managed control endpoint did not become available: {}", + path.display() + ) + ); + } +} + +/// The endpoints of Windows hosts: named pipes that this process serves, through the Win32 calls +/// that the standard library lacks. +#[cfg(windows)] +#[allow(unsafe_code)] +mod endpoints { + use std::fs::File; + use std::io::{Read, Write}; + use std::os::windows::io::{AsRawHandle, FromRawHandle}; + + use windows_sys::Win32::Foundation::{ + ERROR_PIPE_CONNECTED, GetLastError, INVALID_HANDLE_VALUE, + }; + use windows_sys::Win32::Storage::FileSystem::PIPE_ACCESS_DUPLEX; + use windows_sys::Win32::System::Pipes::{ + ConnectNamedPipe, CreateNamedPipeW, PIPE_READMODE_BYTE, PIPE_TYPE_BYTE, PIPE_WAIT, + }; + + use super::*; + + /// The server side of a named pipe. + struct Server(File); + + impl Transport for Server { + fn read(&mut self, buffer: &mut [u8], _timeout: Option) -> io::Result { + match self.0.read(buffer) { + // ERROR_BROKEN_PIPE: the client closed its end. + Err(error) if error.raw_os_error() == Some(109) => Ok(0), + result => result, + } + } + + fn write_all(&mut self, data: &[u8], _timeout: Option) -> io::Result<()> { + self.0.write_all(data) + } + } + + /// Creates a named pipe that this process serves, and returns its name and server end. + fn create_pipe() -> (String, File) { + let mut id = [0u8; 16]; + getrandom::fill(&mut id).unwrap(); + let id: String = id.iter().map(|byte| format!("{byte:02x}")).collect(); + let name = format!(r"\\.\pipe\nvx-cli-session-test-{id}"); + let wide: Vec = name.encode_utf16().chain(Some(0)).collect(); + // SAFETY: `wide` is NUL-terminated and outlives the call. + let handle = unsafe { + CreateNamedPipeW( + wide.as_ptr(), + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 65_536, + 65_536, + 0, + std::ptr::null(), + ) + }; + assert_ne!(handle, INVALID_HANDLE_VALUE); + // SAFETY: CreateNamedPipeW returned a fresh handle that nothing else owns. + (name, unsafe { File::from_raw_handle(handle) }) + } + + /// Waits for the client of a pipe. + fn accept(server: &File) { + // SAFETY: the handle is the open server end of a pipe. + let connected = unsafe { ConnectNamedPipe(server.as_raw_handle(), std::ptr::null_mut()) }; + // SAFETY: reads the calling thread's last error. + assert!(connected != 0 || unsafe { GetLastError() } == ERROR_PIPE_CONNECTED); + } + + /// Serves `serve` on a fresh pipe and returns a session connected to it. + fn connected( + serve: impl FnOnce(&mut Guest) + Send + 'static, + ) -> (Session, thread::JoinHandle<()>) { + let (name, server) = create_pipe(); + let worker = thread::spawn(move || { + accept(&server); + let mut guest = Guest::new(Server(server)); + guest.accept(); + serve(&mut guest); + }); + let endpoint = name.replace('\\', "/"); + let session = Session::connect(&endpoint, std::process::id(), &CAPABILITY, 5.0).unwrap(); + (session, worker) + } + + #[test] + fn write_reports_a_closed_endpoint() { + // OpenVMM closes the pipe as it tears the VM down. + let (mut session, worker) = connected(|_| {}); + worker.join().unwrap(); + let failure = session.ping(5.0).unwrap_err(); + assert!(matches!(failure, Exit::EndpointClosed), "{failure:?}"); + } + + #[test] + fn read_reports_a_closed_endpoint_after_its_data() { + let (mut session, worker) = connected(|guest| { + let (_, request_id, _) = guest.request(); + guest.send(APP_READY, request_id, 0, &[]); + let _ = guest.request(); + }); + session.ping(5.0).unwrap(); + let failure = session.ping(5.0).unwrap_err(); + worker.join().unwrap(); + assert!(matches!(failure, Exit::EndpointClosed), "{failure:?}"); + } + + #[test] + fn read_times_out_and_closes_the_session() { + let (release, released) = mpsc::channel::<()>(); + let (mut session, worker) = connected(move |guest| { + let _ = guest.request(); + let _ = released.recv_timeout(Duration::from_secs(10)); + }); + let started = Instant::now(); + assert_eq!( + message(session.ping(0.1)), + "managed control response timed out" + ); + assert!(started.elapsed() < Duration::from_millis(2100)); + release.send(()).unwrap(); + worker.join().unwrap(); + // Like Python's session, a session whose transfer failed is closed. + assert_eq!( + message(session.ping(5.0)), + "managed control session is closed" + ); + } + + #[test] + fn endpoints_that_never_appear_time_out_with_their_path() { + let started = Instant::now(); + let failure = Session::connect( + "//./pipe/nvx-cli-session-test-missing", + std::process::id(), + &CAPABILITY, + 0.2, + ) + .err() + .unwrap(); + assert!(started.elapsed() >= Duration::from_millis(200)); + assert_eq!( + failure.message().unwrap(), + r"managed control endpoint did not become available: \\.\pipe\nvx-cli-session-test-missing" + ); + } +} diff --git a/nvx-rs/src/host/mod.rs b/nvx-rs/src/host/mod.rs index 129d27afd..26a07c23a 100644 --- a/nvx-rs/src/host/mod.rs +++ b/nvx-rs/src/host/mod.rs @@ -1,31 +1,45 @@ //! The host side of NVX. //! -//! - [`artifacts`] locates the OpenVMM executable and the guest kernel and initramfs. +//! - [`artifacts`] locates the OpenVMM executable and the guest kernel and initramfs, and every +//! artifact of the guests that `nvx.py` runs, in a repository checkout or a release. //! - [`launch`] builds OpenVMM's command line from a [`LaunchSpec`](launch::LaunchSpec): the //! managed lifecycle, the workload identity, the kernel command line, and the network and //! filesystem options. -//! - [`network`] lowers egress rules into OpenVMM's rules, and [`filesystem`] plans the export of -//! host paths through OpenVMM's aggregate virtio-fs device. +//! - [`network`] lowers egress rules, and the egress policy files of `nvx.py`, into OpenVMM's +//! rules, and [`filesystem`] plans the export of host paths through OpenVMM's aggregate +//! virtio-fs device. //! - [`instance`] supervises one VM instance in a directory of its own: it starts OpenVMM and //! waits until the guest is ready, reconciles recorded state with the host, runs workloads, //! and stops the VM. [`report`] reads OpenVMM's outcome report. //! - [`session`] speaks the authenticated control protocol, [`process`] launches and terminates //! OpenVMM processes, and [`platform`] holds the primitives of Linux and Windows hosts. Other //! hosts compile but report NVX as unavailable. +//! - [`sandbox`] is the launch profile of `nvx.py sandbox`: a container over read-only EROFS +//! layers and a private ext4 scratch disk, with live shares, and the checks of its options. +//! - `cli` (feature `cli`) is the `nvx-rs` command-line interface, which `nvx.py` runs for the +//! steps that it delegates: artifact lookup, egress policy compilation, and sandboxes. +//! +//! The ports of `nvx.py` code keep its Python semantics, which the private `python` module +//! provides. //! //! The host modules deny unsafe code. On Unix, the private `sys` module confines the system calls //! that the standard library lacks behind documented safe functions, which call `nix`. The -//! Windows platform module still calls `windows-sys` directly. +//! Windows platform module, and the module of the Win32 calls that Python's filesystem queries +//! make, still call `windows-sys` directly. #![deny(unsafe_code)] pub mod artifacts; +#[cfg(feature = "cli")] +pub mod cli; pub mod filesystem; pub mod instance; pub mod launch; pub mod network; pub mod platform; pub mod process; +pub(crate) mod python; pub mod report; +pub mod sandbox; pub mod session; #[cfg(unix)] #[allow(unsafe_code)] diff --git a/nvx-rs/src/host/network/address.rs b/nvx-rs/src/host/network/address.rs new file mode 100644 index 000000000..68759b8e5 --- /dev/null +++ b/nvx-rs/src/host/network/address.rs @@ -0,0 +1,729 @@ +//! IP networks as Python's `ipaddress` module parses, orders, and prints them, and the intervals +//! of addresses that egress rules select. +//! +//! The egress policy compiler ports `scripts/nvx_tools/egress_policy.py`, which reads CIDRs with +//! `ipaddress.ip_interface`, so [`parse_interface`] accepts exactly what it accepts: host bits, +//! which it clears, IPv4 netmasks and hostmasks such as `/255.0.0.0`, prefix lengths with leading +//! zeros, embedded IPv4 in IPv6 addresses, and IPv6 scopes, which callers reject. A [`Network`] +//! prints as `str(network)` does in Python 3.13 and later, which writes an IPv4-mapped IPv6 +//! address such as `::ffff:192.0.2.0` with its IPv4 address in dotted form. + +use std::fmt; +use std::net::Ipv4Addr; + +use crate::host::python::text::PyText; + +/// Address family of a [`Network`]. +/// +/// IPv4 orders before IPv6, as Python's IP versions 4 and 6 do. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum Family { + /// IPv4: 32-bit addresses. + V4, + /// IPv6: 128-bit addresses. + V6, +} + +impl Family { + /// Both families, in the order in which the compiler lowers them. + pub(crate) const ALL: [Self; 2] = [Self::V4, Self::V6]; + + /// Returns the number of bits of an address of this family. + const fn bits(self) -> u32 { + match self { + Self::V4 => 32, + Self::V6 => 128, + } + } + + /// Returns the last address of this family, whose bits are all set. + pub(crate) const fn last_address(self) -> u128 { + match self { + Self::V4 => u32::MAX as u128, + Self::V6 => u128::MAX, + } + } + + /// Returns every address of this family as one interval. + pub(crate) const fn everything(self) -> Interval { + (0, self.last_address()) + } +} + +/// An inclusive interval of addresses of one family: its first and its last address. +pub(crate) type Interval = (u128, u128); + +/// A network of one family, without host bits. +/// +/// Networks order as the compiler sorts its output: by family, then by address, then by prefix +/// length. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Network { + /// Address family. + family: Family, + /// First address of the network, with every host bit clear. + address: u128, + /// Number of leading network bits, at most the family's address width. + prefix: u8, +} + +impl Network { + /// Returns the network of `family` with `prefix` that holds `address`, clearing its host + /// bits. + fn new(family: Family, address: u128, prefix: u32) -> Self { + let network = Self { + family, + address, + prefix: prefix as u8, + }; + Self { + address: address & !network.host_mask(), + ..network + } + } + + /// Returns the address family of the network. + pub(crate) fn family(self) -> Family { + self.family + } + + /// Returns the interval of the network's addresses: its network and broadcast addresses. + pub(crate) fn interval(self) -> Interval { + (self.address, self.address | self.host_mask()) + } + + /// Returns the host bits of an address of the network's family. + fn host_mask(self) -> u128 { + let prefix = u32::from(self.prefix); + if prefix >= self.family.bits() { + 0 + } else { + self.family.last_address() >> prefix + } + } + + /// Returns whether `other` lies within this network, as Python's `subnet_of` decides. + pub(crate) fn contains(self, other: Self) -> bool { + let (first, last) = self.interval(); + let (other_first, other_last) = other.interval(); + self.family == other.family && first <= other_first && last >= other_last + } +} + +impl fmt::Display for Network { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.family { + Family::V4 => write!(formatter, "{}", Ipv4Addr::from(self.address as u32))?, + Family::V6 => write_ipv6(formatter, self.address)?, + } + write!(formatter, "/{}", self.prefix) + } +} + +/// Writes an IPv6 address as Python's `str(IPv6Address)` does: an IPv4-mapped address as +/// `::ffff:` and its IPv4 address in dotted form, and any other address as lowercase hexadecimal +/// groups whose first longest run of two or more zero groups becomes `::`. +fn write_ipv6(formatter: &mut fmt::Formatter<'_>, address: u128) -> fmt::Result { + if address >> 32 == 0xFFFF { + return write!(formatter, "::ffff:{}", Ipv4Addr::from(address as u32)); + } + let groups: [u16; 8] = std::array::from_fn(|index| (address >> (112 - 16 * index)) as u16); + // The first of the longest runs of zero groups, if a run has at least two groups. + let mut best: Option<(usize, usize)> = None; + let mut index = 0; + while index < groups.len() { + if groups[index] != 0 { + index += 1; + continue; + } + let start = index; + while index < groups.len() && groups[index] == 0 { + index += 1; + } + let length = index - start; + if length > best.map_or(1, |(_, best_length)| best_length) { + best = Some((start, length)); + } + } + let write_groups = |formatter: &mut fmt::Formatter<'_>, groups: &[u16]| { + for (index, group) in groups.iter().enumerate() { + if index > 0 { + formatter.write_str(":")?; + } + write!(formatter, "{group:x}")?; + } + Ok(()) + }; + match best { + None => write_groups(formatter, &groups), + Some((start, length)) => { + write_groups(formatter, &groups[..start])?; + formatter.write_str("::")?; + write_groups(formatter, &groups[start + length..]) + } + } +} + +/// Why [`parse_interface`] refused a string. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum InterfaceError { + /// The string is neither an IPv4 nor an IPv6 interface. + Invalid, + /// The string is an IPv6 interface that names a scope, such as `fe80::1%eth0/64`. + Scoped, +} + +/// Parses `text` as Python's `ipaddress.ip_interface` does and returns its network, whose host +/// bits are clear. +pub(crate) fn parse_interface(text: &PyText) -> Result { + let text = text.as_bytes(); + // `ip_interface` tries IPv4 first, and no string is both. + if let Some(network) = parse_ipv4_interface(text) { + return Ok(network); + } + match parse_ipv6_interface(text) { + Some((network, false)) => Ok(network), + Some((_, true)) => Err(InterfaceError::Scoped), + None => Err(InterfaceError::Invalid), + } +} + +/// Splits `text` at every `/` as `_split_optional_netmask` does, and returns the address and the +/// netmask, if any, or `None` for more than one `/`. +fn split_netmask(text: &[u8]) -> Option<(&[u8], Option<&[u8]>)> { + let mut parts = text.split(|&byte| byte == b'/'); + let address = parts.next()?; + let netmask = parts.next(); + parts.next().is_none().then_some((address, netmask)) +} + +/// Parses an `IPv4Interface`. +fn parse_ipv4_interface(text: &[u8]) -> Option { + let (address, netmask) = split_netmask(text)?; + let address = parse_ipv4(address)?; + let prefix = match netmask { + None => 32, + Some(netmask) => { + prefix_from_prefix_string(netmask, 32).or_else(|| prefix_from_ipv4_mask(netmask))? + } + }; + Some(Network::new(Family::V4, u128::from(address), prefix)) +} + +/// Parses an `IPv6Interface` and returns its network and whether it names a scope. +fn parse_ipv6_interface(text: &[u8]) -> Option<(Network, bool)> { + let (address, netmask) = split_netmask(text)?; + // `_split_scope_id`: a scope follows the first `%`, and is not empty and has no other `%`. + let (address, scoped) = match address.iter().position(|&byte| byte == b'%') { + None => (address, false), + Some(position) => { + let scope = &address[position + 1..]; + if scope.is_empty() || scope.contains(&b'%') { + return None; + } + (&address[..position], true) + } + }; + let address = parse_ipv6(address)?; + let prefix = match netmask { + None => 128, + Some(netmask) => prefix_from_prefix_string(netmask, 128)?, + }; + Some((Network::new(Family::V6, address, prefix), scoped)) +} + +/// Parses a prefix length as `_prefix_from_prefix_string` does: ASCII digits, leading zeros +/// included, whose value is at most `maximum`. +fn prefix_from_prefix_string(text: &[u8], maximum: u32) -> Option { + // Python's `int` refuses strings of more than 4300 digits, its default + // `sys.get_int_max_str_digits()`, even when they are mostly leading zeros. + const MAX_INT_DIGITS: usize = 4300; + if text.is_empty() || text.len() > MAX_INT_DIGITS || !text.iter().all(u8::is_ascii_digit) { + return None; + } + let significant = &text[text.iter().take_while(|&&byte| byte == b'0').count()..]; + if significant.len() > 3 { + return None; + } + let value = significant + .iter() + .fold(0, |value, digit| value * 10 + u32::from(digit - b'0')); + (value <= maximum).then_some(value) +} + +/// Parses an IPv4 netmask, such as `255.255.0.0`, or a hostmask, such as `0.0.255.255`, as +/// `_prefix_from_ip_string` does, and returns its prefix length. The ambiguous all-ones and +/// all-zeros masks are netmasks. +fn prefix_from_ipv4_mask(text: &[u8]) -> Option { + let mask = parse_ipv4(text)?; + prefix_from_ipv4_netmask(mask).or_else(|| prefix_from_ipv4_netmask(!mask)) +} + +/// Returns the prefix length of an IPv4 netmask: leading ones and trailing zeros. +fn prefix_from_ipv4_netmask(mask: u32) -> Option { + let prefix = mask.leading_ones(); + (mask.checked_shl(prefix).unwrap_or(0) == 0).then_some(prefix) +} + +/// Parses an IPv4 address as `IPv4Address._ip_int_from_string` does: four decimal octets of at +/// most three ASCII digits each, without leading zeros, and at most 255. +fn parse_ipv4(text: &[u8]) -> Option { + let mut octets = text.split(|&byte| byte == b'.'); + let mut address = 0u32; + for _ in 0..4 { + let octet = octets.next()?; + let valid = !octet.is_empty() + && octet.len() <= 3 + && octet.iter().all(u8::is_ascii_digit) + && (octet == b"0" || octet[0] != b'0'); + if !valid { + return None; + } + let value = octet + .iter() + .fold(0u32, |value, digit| value * 10 + u32::from(digit - b'0')); + if value > 255 { + return None; + } + address = (address << 8) | value; + } + octets.next().is_none().then_some(address) +} + +/// One colon-separated part of an IPv6 address: its text, or a group that an embedded IPv4 +/// address contributes. +#[derive(Clone, Copy)] +enum Part<'a> { + /// A hexadecimal group as written, possibly empty. + Text(&'a [u8]), + /// A group of an embedded IPv4 address. + Group(u16), +} + +impl Part<'_> { + /// Returns whether the part is empty, as at a `::`. + fn is_empty(self) -> bool { + matches!(self, Self::Text(text) if text.is_empty()) + } + + /// Returns the value of the group, as `_parse_hextet` parses it: one to four hexadecimal + /// digits. + fn value(self) -> Option { + match self { + Self::Group(group) => Some(group), + Self::Text(text) => { + if text.is_empty() || text.len() > 4 { + return None; + } + text.iter().try_fold(0u16, |value, &digit| { + Some((value << 4) | (char::from(digit).to_digit(16)? as u16)) + }) + } + } + } +} + +/// Parses an IPv6 address without a scope as `IPv6Address._ip_int_from_string` does. +fn parse_ipv6(text: &[u8]) -> Option { + // Python counts characters, not bytes, but no address with a non-ASCII character is valid. + if text.is_empty() || text.len() > 45 || !text.is_ascii() { + return None; + } + // At most eight groups and one more part for a leading or trailing `::`; `split` keeps the + // rest of a longer address in a tenth part. + const MAX_PARTS: usize = 9; + let mut parts: Vec> = text + .splitn(MAX_PARTS + 1, |&byte| byte == b':') + .map(Part::Text) + .collect(); + if parts.len() < 3 { + return None; + } + if let Some(&Part::Text(last)) = parts.last() + && last.contains(&b'.') + { + let ipv4 = parse_ipv4(last)?; + parts.pop(); + parts.push(Part::Group((ipv4 >> 16) as u16)); + parts.push(Part::Group(ipv4 as u16)); + } + if parts.len() > MAX_PARTS { + return None; + } + let count = parts.len(); + // A `::` is an empty part between the endpoints, and at most one is allowed. + let mut skip = None; + for (index, part) in parts.iter().enumerate().take(count - 1).skip(1) { + if part.is_empty() { + if skip.is_some() { + return None; + } + skip = Some(index); + } + } + let (high, low, skipped) = match skip { + Some(skip) => { + let mut high = skip; + let mut low = count - skip - 1; + if parts[0].is_empty() { + high -= 1; + if high != 0 { + return None; + } + } + if parts[count - 1].is_empty() { + low -= 1; + if low != 0 { + return None; + } + } + let skipped = 8usize + .checked_sub(high + low) + .filter(|&skipped| skipped >= 1)?; + (high, low, skipped) + } + None => { + if count != 8 || parts[0].is_empty() || parts[count - 1].is_empty() { + return None; + } + (count, 0, 0) + } + }; + let mut address = 0u128; + for part in &parts[..high] { + address = (address << 16) | u128::from(part.value()?); + } + address = address.checked_shl(16 * skipped as u32).unwrap_or(0); + for part in &parts[count - low..] { + address = (address << 16) | u128::from(part.value()?); + } + Some(address) +} + +/// The networks that Python's `ipaddress.summarize_address_range` yields for an interval: the +/// fewest networks that cover it exactly, from its first address. +pub(crate) struct Summary { + /// Address family of the interval. + family: Family, + /// First address that the networks yielded so far do not cover, or `None` once they cover the + /// interval. + next: Option, + /// Last address of the interval. + last: u128, +} + +/// Returns the networks that cover the addresses of `family` from `first` through `last`, as +/// `ipaddress.summarize_address_range` yields them. +pub(crate) fn summarize(family: Family, (first, last): Interval) -> Summary { + #[cfg(test)] + probe::SUMMARIES.with(|count| count.set(count.get() + 1)); + Summary { + family, + next: Some(first), + last, + } +} + +impl Iterator for Summary { + type Item = Network; + + fn next(&mut self) -> Option { + let first = self.next.filter(|&first| first <= self.last)?; + let bits = self.family.bits(); + let alignment = if first == 0 { + bits + } else { + first.trailing_zeros().min(bits) + }; + // The largest power of two that fits in the rest of the interval; the whole IPv6 address + // space holds 2^128 addresses, one more than a `u128` holds. + let span = self.last - first; + let fitting = span + .checked_add(1) + .map_or(128, |count| 127 - count.leading_zeros()); + let size_bits = alignment.min(fitting); + let network = Network::new(self.family, first, bits - size_bits); + // Python stops once the next address passes the family's last address. + self.next = 1u128 + .checked_shl(size_bits) + .and_then(|size| first.checked_add(size)) + .filter(|&next| next - 1 != self.family.last_address()); + Some(network) + } +} + +/// Returns the union of `intervals`, sorted, with overlapping and adjacent intervals merged, as +/// `_merge_intervals` does. +pub(crate) fn merge(intervals: impl IntoIterator) -> Vec { + let mut sorted: Vec = intervals.into_iter().collect(); + #[cfg(test)] + probe::MERGED_INTERVALS.with(|count| count.set(count.get() + sorted.len())); + sorted.sort_unstable(); + let mut merged: Vec = Vec::with_capacity(sorted.len()); + for (start, end) in sorted { + match merged.last_mut() { + Some(previous) if start <= previous.1.saturating_add(1) => { + previous.1 = previous.1.max(end); + } + _ => merged.push((start, end)), + } + } + merged +} + +/// Returns the addresses of `sources` that `exclusions` do not hold, as `_subtract_intervals` +/// does. Both lists are sorted and their intervals disjoint. +pub(crate) fn subtract(sources: &[Interval], exclusions: &[Interval]) -> Vec { + let mut remaining = Vec::new(); + let mut first_exclusion = 0; + for &(source_start, source_end) in sources { + while first_exclusion < exclusions.len() && exclusions[first_exclusion].1 < source_start { + first_exclusion += 1; + } + let mut cursor = source_start; + // Whether the cursor passed the last address of the family, which a `u128` cannot hold. + let mut exhausted = false; + for &(excluded_start, excluded_end) in exclusions[first_exclusion..] + .iter() + .take_while(|(excluded_start, _)| *excluded_start <= source_end) + { + if cursor < excluded_start { + remaining.push((cursor, excluded_start - 1)); + } + match excluded_end.checked_add(1) { + Some(next) => cursor = cursor.max(next), + None => { + exhausted = true; + break; + } + } + if cursor > source_end { + break; + } + } + if !exhausted && cursor <= source_end { + remaining.push((cursor, source_end)); + } + } + remaining +} + +/// Counters that tests read to check how much work the compiler does, as the Python tests check +/// by mocking `ipaddress.summarize_address_range` and `_merge_intervals`. +#[cfg(test)] +pub(crate) mod probe { + use std::cell::Cell; + + thread_local! { + /// Number of [`summarize`](super::summarize) calls on this thread. + pub(crate) static SUMMARIES: Cell = const { Cell::new(0) }; + /// Number of intervals that [`merge`](super::merge) received on this thread. + pub(crate) static MERGED_INTERVALS: Cell = const { Cell::new(0) }; + } + + /// Resets the counters and returns the result of `operation` with the + /// [`summarize`](super::summarize) calls and merged intervals that it caused. + pub(crate) fn count(operation: impl FnOnce() -> T) -> (T, usize, usize) { + SUMMARIES.with(|count| count.set(0)); + MERGED_INTERVALS.with(|count| count.set(0)); + let result = operation(); + ( + result, + SUMMARIES.with(Cell::get), + MERGED_INTERVALS.with(Cell::get), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn network(text: &str) -> Result { + parse_interface(&PyText::from(text)).map(|network| network.to_string()) + } + + #[test] + fn interfaces_parse_as_python_does() { + for (text, expected) in [ + ("10.0.0.5/24", "10.0.0.0/24"), + ("10.0.0.0/255.0.0.0", "10.0.0.0/8"), + ("10.0.0.0/0.255.255.255", "10.0.0.0/8"), + ("10.0.0.0/255.255.255.255", "10.0.0.0/32"), + ("10.0.0.0/0.0.0.0", "0.0.0.0/0"), + ("10.0.0.0/08", "10.0.0.0/8"), + ("10.0.0.0/0008", "10.0.0.0/8"), + ("192.0.2.7", "192.0.2.7/32"), + ("::/0", "::/0"), + ("::/000128", "::/128"), + ("::1.2.3.4", "::102:304/128"), + ("1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8/128"), + ("1:2:3:4:5:6:7::", "1:2:3:4:5:6:7:0/128"), + ("::2:3:4:5:6:7:8", "0:2:3:4:5:6:7:8/128"), + ("1:2:3:4:5:6:1.2.3.4", "1:2:3:4:5:6:102:304/128"), + ("1:2:3:4:5::1.2.3.4", "1:2:3:4:5:0:102:304/128"), + ("0000::", "::/128"), + ("::FFFF:1.2.3.4", "::ffff:1.2.3.4/128"), + ("::ffff:c000:201/120", "::ffff:192.0.2.0/120"), + ("::c000:200/120", "::c000:200/120"), + ("::1.2.3.4/96", "::/96"), + ("2001:DB8::/32", "2001:db8::/32"), + ("::ffff:0:0/95", "::fffe:0:0/95"), + ("1:0:0:2:0:0:0:3", "1:0:0:2::3/128"), + ("0:0:1::", "0:0:1::/128"), + ("::1:2:3:4:5:6:7", "0:1:2:3:4:5:6:7/128"), + ("fe80::1/64", "fe80::/64"), + ] { + assert_eq!(network(text).as_deref(), Ok(expected), "{text}"); + } + let long_prefix = format!("10.0.0.0/{}8", "0".repeat(4299)); + assert_eq!(network(&long_prefix).as_deref(), Ok("10.0.0.0/8")); + } + + #[test] + fn invalid_and_scoped_interfaces_are_refused() { + for text in [ + "10.0.0.0/+8", + "10.0.0.0/-8", + " 10.0.0.0/8", + "10.0.0.0/8 ", + "10.0.0.0/", + "10.0.0.0//8", + "10.0.0.0/8/8", + "010.0.0.0/8", + "1.2.3", + "1.2.3.4.5", + "256.0.0.0", + "1.2.3.4/33", + "1.2.3.4/255.0.255.0", + "1.2.3.4/255.255.255.000", + "::/129", + "fe80::1%/64", + "fe80::1%a%b", + "fe80::1%%", + "1::2::3", + ":1::2", + "1::2:", + "1:2:3:4:5:6:7:8:9", + "1:2:3:4:5:6:7:1.2.3.4", + "1:2:3:4:5:6::1.2.3.4", + "00000::", + "::ffff:1.2.3.04", + "::1.2.3", + "1.2.3.4/255.255.255.0x", + "\u{FF11}.2.3.4", + "::/12\u{FF18}", + "0:0:0:0:0:0:0:0:0", + ":::", + "1:::2", + "", + "/8", + "1.2.3.4/0x8", + "1.2.3.4/8.0", + "::/0x8", + "1.2.3.4/\u{0668}", + "fe80::1%eth0/129", + "::1:2:3:4:5:6:7:8", + "1:2:3:4:5:6:7:8:", + ":1:2:3:4:5:6:7", + ] { + assert_eq!(network(text), Err(InterfaceError::Invalid), "{text:?}"); + } + let long_prefix = format!("10.0.0.0/{}8", "0".repeat(4300)); + assert_eq!(network(&long_prefix), Err(InterfaceError::Invalid)); + for text in [ + "fe80::1%eth0/64", + "fe80::%eth0/64", + "fe80::1%1", + "::ffff:192.0.2.1%eth0/120", + "fe80::1%\u{FF11}", + "1:2:3:4:5:6:7:8%x/128", + ] { + assert_eq!(network(text), Err(InterfaceError::Scoped), "{text:?}"); + } + } + + #[test] + fn ipv6_addresses_print_as_python_does() { + for (address, expected) in [ + (0u128, "::/128"), + (1, "::1/128"), + (0xFFFF_0000_0000, "::ffff:0.0.0.0/128"), + (0xFFFF_C000_0201, "::ffff:192.0.2.1/128"), + (0xFFFE_C000_0201, "::fffe:c000:201/128"), + (0x2001_0db8 << 96, "2001:db8::/128"), + ((0x2001_0db8 << 96) | 1, "2001:db8::1/128"), + // The longest run of zero groups becomes `::`, the first of two equally long runs, + // and a single zero group stays. + ((1 << 112) | (1 << 64) | 1, "1:0:0:1::1/128"), + ((1 << 112) | (1 << 64) | (1 << 16) | 1, "1::1:0:0:1:1/128"), + ( + (1 << 112) | (2 << 96) | (3 << 80) | (4 << 64) | (5 << 48) | (6 << 32) | (7 << 16), + "1:2:3:4:5:6:7:0/128", + ), + (u128::MAX, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"), + ] { + let network = Network::new(Family::V6, address, 128); + assert_eq!(network.to_string(), expected, "{address:#x}"); + } + } + + #[test] + fn summaries_match_python() { + let summary = |family, first, last| -> Vec { + summarize(family, (first, last)) + .map(|network| network.to_string()) + .collect() + }; + let ipv4 = |text: &str| u128::from(u32::from(text.parse::().unwrap())); + assert_eq!( + summary(Family::V4, ipv4("192.0.2.0"), ipv4("192.0.2.130")), + ["192.0.2.0/25", "192.0.2.128/31", "192.0.2.130/32"] + ); + assert_eq!(summary(Family::V4, 0, u128::from(u32::MAX)), ["0.0.0.0/0"]); + assert_eq!( + summary(Family::V4, 1, u128::from(u32::MAX)).len(), + 32, + "every power of two below 2^32" + ); + assert_eq!(summary(Family::V6, 0, u128::MAX), ["::/0"]); + assert_eq!( + summary(Family::V6, u128::MAX, u128::MAX), + ["ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"] + ); + assert_eq!(summary(Family::V6, 1, u128::MAX).len(), 128); + assert_eq!(summary(Family::V6, 1 << 127, u128::MAX), ["8000::/1"]); + } + + #[test] + fn intervals_merge_and_subtract_at_the_family_bounds() { + assert_eq!( + merge([(5, 9), (0, 2), (3, 4), (20, 30), (25, 26)]), + [(0, 9), (20, 30)] + ); + assert_eq!(merge([(0, u128::MAX), (7, 9)]), [(0, u128::MAX)]); + assert_eq!(merge([(10, u128::MAX), (0, 9)]), [(0, u128::MAX)]); + assert!(merge([]).is_empty()); + assert_eq!( + subtract(&[(0, 100)], &[(10, 20), (30, 40)]), + [(0, 9), (21, 29), (41, 100)] + ); + assert_eq!(subtract(&[(0, u128::MAX)], &[(1, u128::MAX)]), [(0, 0)]); + assert!(subtract(&[(5, u128::MAX)], &[(0, u128::MAX)]).is_empty()); + assert_eq!( + subtract(&[(0, 9), (20, 29)], &[(5, 24)]), + [(0, 4), (25, 29)] + ); + assert_eq!(subtract(&[(0, 9)], &[]), [(0, 9)]); + } + + #[test] + fn containment_is_family_aware() { + let parse = |text: &str| parse_interface(&PyText::from(text)).unwrap(); + assert!(parse("10.0.0.0/8").contains(parse("10.1.0.0/16"))); + assert!(!parse("10.0.0.0/8").contains(parse("11.0.0.0/16"))); + assert!(!parse("::/96").contains(parse("0.0.0.0/0"))); + assert!(!parse("0.0.0.0/0").contains(parse("::/96"))); + assert_eq!(parse("10.0.0.0/8").interval(), (0x0A00_0000, 0x0AFF_FFFF)); + } +} diff --git a/nvx-rs/src/host/network/lowering.rs b/nvx-rs/src/host/network/lowering.rs new file mode 100644 index 000000000..4f5858977 --- /dev/null +++ b/nvx-rs/src/host/network/lowering.rs @@ -0,0 +1,716 @@ +//! The lowering of egress rules into OpenVMM's native rules, which the egress policy compiler of +//! [`policy`](super::policy) and [`openvmm_arguments`](super::openvmm_arguments) share. +//! +//! It ports `scripts/nvx_tools/egress_policy.py` exactly. Rules of one action, allow or deny, +//! become [`Rule`]s: one address family, the address intervals that the rule's destinations +//! select once their exceptions are subtracted, and a protocol and port range. [`lower`] then +//! emits the native rules of each family, within one budget of [`MAX_RULES_PER_ACTION`]: +//! +//! 1. the union of the rules for every protocol, as the fewest networks that cover it; +//! 2. for ICMP, TCP, and UDP, the union of the rules for every port of the protocol, as the +//! networks that cover it together with the addresses of step 1, except those that step 1 +//! covers entirely; +//! 3. for TCP and UDP, a sweep over the ports at which rules start and end: each segment of +//! ports with the same active rules gets the networks of their union, without the addresses +//! that steps 1 and 2 cover, and a network whose addresses stay active extends its range. +//! +//! The native rules are then sorted by family, address, prefix length, protocol, and ports, and +//! written as `CIDR`, `CIDR:PROTOCOL`, `CIDR:PROTOCOL:PORT`, or `CIDR:PROTOCOL:FIRST-LAST`. + +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet}; +use std::rc::Rc; + +use super::address::{self, Family, InterfaceError, Interval, Network}; +use super::policy::MAX_RULES_PER_ACTION; +use crate::error::{Error, Result}; +use crate::host::python::text::PyText; + +/// A protocol that a native rule can select. +/// +/// Protocols order by name, as the compiler sorts them. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum NativeProtocol { + /// ICMP, or ICMPv6 for IPv6 networks. + Icmp, + /// TCP. + Tcp, + /// UDP. + Udp, +} + +impl NativeProtocol { + /// Returns OpenVMM's name of the protocol. + pub(crate) const fn as_str(self) -> &'static str { + match self { + Self::Icmp => "icmp", + Self::Tcp => "tcp", + Self::Udp => "udp", + } + } +} + +/// The protocol of a rule as written: a native protocol, or `any`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum RuleProtocol { + /// TCP. + Tcp, + /// UDP. + Udp, + /// ICMP. + Icmp, + /// Every protocol, or TCP and UDP with ports. + Any, +} + +impl RuleProtocol { + /// Parses one of `tcp`, `udp`, `icmp`, and `any`, which must be lowercase. + pub(crate) fn parse(text: &PyText) -> Option { + match text.as_bytes() { + b"tcp" => Some(Self::Tcp), + b"udp" => Some(Self::Udp), + b"icmp" => Some(Self::Icmp), + b"any" => Some(Self::Any), + _ => None, + } + } + + /// Returns the native protocol, or `None` for `any`. + pub(crate) fn native(self) -> Option { + match self { + Self::Tcp => Some(NativeProtocol::Tcp), + Self::Udp => Some(NativeProtocol::Udp), + Self::Icmp => Some(NativeProtocol::Icmp), + Self::Any => None, + } + } + + /// Returns the native protocols that a port selector of this protocol selects: TCP and UDP + /// for `any`. + fn port_protocols(self) -> &'static [NativeProtocol] { + match self { + Self::Tcp => &[NativeProtocol::Tcp], + Self::Udp => &[NativeProtocol::Udp], + Self::Icmp => &[NativeProtocol::Icmp], + Self::Any => &[NativeProtocol::Tcp, NativeProtocol::Udp], + } + } +} + +/// An inclusive range of ports. +pub(crate) type PortRange = (u16, u16); + +/// Traffic that a rule selects at its destinations: a protocol, or every protocol, and a range of +/// its ports, or every port. +pub(crate) type Selector = (Option, Option); + +/// A value that should be a port number. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum PortValue { + /// An integer, saturated to the range of `i128`. + Integer(i128), + /// Anything else, including a Boolean, which Python's `_port` refuses although `bool` is a + /// subclass of `int`. + Other, +} + +/// A rule of one action, as the compiler lowers it. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Rule { + /// Address family of the rule's addresses. + pub(crate) family: Family, + /// Addresses that the rule selects: sorted, disjoint intervals of `family`, possibly none. + /// + /// The rules of one rule's selectors share them, as Python's share one tuple, so that many + /// destinations times many selectors cost no more memory than they take to write. + pub(crate) addresses: Rc<[Interval]>, + /// Protocol that the rule selects, or `None` for every protocol. + pub(crate) protocol: Option, + /// Ports that the rule selects, or `None` for every port. Only TCP and UDP rules have ports. + pub(crate) ports: Option, +} + +/// Returns an [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) error with `message`. +pub(crate) fn invalid(message: impl Into) -> Error { + Error::unsupported(message) +} + +/// Parses the CIDR `value` of the field `description`, as `_network` does: the network of an +/// IPv4 or IPv6 interface without an IPv6 scope. +pub(crate) fn network(value: &PyText, description: &str) -> Result { + address::parse_interface(value).map_err(|error| { + let value = value.render(); + invalid(match error { + InterfaceError::Invalid => { + format!("{description} is not a valid IPv4 or IPv6 CIDR: {value}") + } + InterfaceError::Scoped => { + format!("{description} must not name an IPv6 scope: {value}") + } + }) + }) +} + +/// Returns the error for a CIDR field whose value is not a string. +pub(crate) fn not_a_cidr_string(description: &str) -> Error { + invalid(format!("{description} must be an IPv4 or IPv6 CIDR string")) +} + +/// Returns the family of the destination `description`, whose network is `parent`, and the +/// addresses that it holds outside `exceptions`, as `_subtract_exclusions` does. Each exception, +/// which is parsed when it is read, must lie within `parent`. +pub(crate) fn destination( + parent: Network, + exceptions: impl IntoIterator>, + description: &str, +) -> Result<(Family, Vec)> { + let mut excluded = Vec::new(); + for (index, exception) in exceptions.into_iter().enumerate() { + let exception = exception?; + if !parent.contains(exception) { + return Err(invalid(format!( + "{description}.except[{index}] must be contained in {parent}" + ))); + } + excluded.push(exception.interval()); + } + Ok(( + parent.family(), + address::subtract(&[parent.interval()], &address::merge(excluded)), + )) +} + +/// Returns the addresses of each family that `destinations`, each a family and its addresses, +/// select, for the families that have destinations, as an MXC rule's `to` selects them. +pub(crate) fn group_destinations( + destinations: &[(Family, Vec)], +) -> Vec<(Family, Vec)> { + Family::ALL + .into_iter() + .filter(|&family| destinations.iter().any(|(each, _)| *each == family)) + .map(|family| { + let addresses = address::merge( + destinations + .iter() + .filter(|(each, _)| *each == family) + .flat_map(|(_, addresses)| addresses.iter().copied()), + ); + (family, addresses) + }) + .collect() +} + +/// Checks the port `value` of the field `description`, as `_port` does. +fn port(value: PortValue, description: &str) -> Result { + match value { + PortValue::Other => Err(invalid(format!("{description} must be an integer"))), + PortValue::Integer(port) => u16::try_from(port) + .ok() + .filter(|&port| port != 0) + .ok_or_else(|| invalid(format!("{description} must be between 1 and 65535"))), + } +} + +/// Returns the range of the `port` and optional `endPort` fields of the selector +/// `description`, which ends at `port` without `endPort`. +pub(crate) fn port_range( + start: PortValue, + end: Option, + description: &str, +) -> Result { + let start = port(start, &format!("{description}.port"))?; + let end = match end { + Some(end) => port(end, &format!("{description}.endPort"))?, + None => start, + }; + if end < start { + return Err(invalid(format!( + "{description}.endPort {end} cannot be below port {start}" + ))); + } + Ok((start, end)) +} + +/// Returns the selectors of an MXC port selector `description` whose protocol is valid, as the +/// rest of `_parse_mxc_port` does. +pub(crate) fn port_selectors( + protocol: RuleProtocol, + port: Option, + end_port: Option, + description: &str, +) -> Result> { + let Some(port) = port else { + if end_port.is_some() { + return Err(invalid(format!("{description}.endPort requires port"))); + } + return Ok(vec![(protocol.native(), None)]); + }; + port_protocol_selectors(protocol, port, end_port, description) +} + +/// Returns the selectors of a protocol with ports, which ICMP does not have. +pub(crate) fn port_protocol_selectors( + protocol: RuleProtocol, + port: PortValue, + end_port: Option, + description: &str, +) -> Result> { + if protocol == RuleProtocol::Icmp { + return Err(invalid(format!( + "{description}.port is not supported with icmp" + ))); + } + let range = port_range(port, end_port, description)?; + Ok(protocol + .port_protocols() + .iter() + .map(|&protocol| (Some(protocol), Some(range))) + .collect()) +} + +/// Returns the rules that select each of `selectors` at the addresses of each family of +/// `destinations`, which the rules share, as `_parse_mxc_rule` and `_parse_legacy_rule` build +/// them. Without destinations, the rules select every address of both families, and without +/// selectors every protocol and port. +pub(crate) fn combine( + destinations: Option)>>, + selectors: Option>, +) -> Vec { + let destinations: Vec<(Family, Rc<[Interval]>)> = match destinations { + Some(destinations) => destinations + .into_iter() + .map(|(family, addresses)| (family, Rc::from(addresses))) + .collect(), + None => Family::ALL + .into_iter() + .map(|family| (family, Rc::from([family.everything()]))) + .collect(), + }; + let selectors = selectors.unwrap_or_else(|| vec![(None, None)]); + destinations + .iter() + .flat_map(|(family, addresses)| { + selectors.iter().map(|&(protocol, ports)| Rule { + family: *family, + addresses: Rc::clone(addresses), + protocol, + ports, + }) + }) + .collect() +} + +/// A native rule: a network, and optionally a protocol and a range of its ports. Native rules +/// order as the compiler sorts them. +type NativeRule = (Network, Option, Option); + +/// Returns the error of an action `category` whose rules need more native rules than OpenVMM +/// accepts. +fn too_many_rules(category: &str) -> Error { + invalid(format!( + "{category} emits at most {MAX_RULES_PER_ACTION} native rules" + )) +} + +/// Lowers the rules of the action `category` into native rules, in the order in which the +/// compiler sorts them, as `_compile_category` does once it has parsed them. +pub(crate) fn lower(rules: &[Rule], category: &str) -> Result> { + // A native rule matches only its own family, so each family is lowered on its own, within + // the action's shared limit. + let mut lowered: Vec = Vec::new(); + for family in Family::ALL { + let family_rules: Vec<&Rule> = rules.iter().filter(|rule| rule.family == family).collect(); + if !family_rules.is_empty() { + let budget = MAX_RULES_PER_ACTION - lowered.len(); + let family_lowered = lower_family(&family_rules, family, category, budget)?; + lowered.extend(family_lowered); + } + } + lowered.sort_unstable(); + Ok(lowered.into_iter().map(render).collect()) +} + +/// Writes a native rule in OpenVMM's form. +fn render((network, protocol, ports): NativeRule) -> String { + match (protocol, ports) { + (None, _) => network.to_string(), + (Some(protocol), None) => format!("{network}:{}", protocol.as_str()), + (Some(protocol), Some((start, end))) if start == end => { + format!("{network}:{}:{start}", protocol.as_str()) + } + (Some(protocol), Some((start, end))) => { + format!("{network}:{}:{start}-{end}", protocol.as_str()) + } + } +} + +/// Lowers the rules of one family, emitting at most `budget` native rules, as `_compile_family` +/// does. +fn lower_family( + rules: &[&Rule], + family: Family, + category: &str, + budget: usize, +) -> Result> { + let address_only = union(rules.iter().copied().filter(|rule| rule.protocol.is_none())); + let mut lowered: Vec = + intervals_to_networks(&address_only, family, budget, category)? + .into_iter() + .map(|network| (network, None, None)) + .collect(); + // Addresses that already match every port of each protocol. + let mut covered: BTreeMap> = BTreeMap::new(); + for protocol in [ + NativeProtocol::Icmp, + NativeProtocol::Tcp, + NativeProtocol::Udp, + ] { + let protocol_wide = union( + rules + .iter() + .copied() + .filter(|rule| rule.protocol == Some(protocol) && rule.ports.is_none()), + ); + if protocol_wide.is_empty() { + continue; + } + let networks = protocol_intervals_to_networks( + &protocol_wide, + &address_only, + family, + budget - lowered.len(), + category, + )?; + lowered.extend( + networks + .into_iter() + .map(|network| (network, Some(protocol), None)), + ); + covered.insert( + protocol, + address::merge(address_only.iter().chain(&protocol_wide).copied()), + ); + } + for protocol in [NativeProtocol::Tcp, NativeProtocol::Udp] { + let covered = covered.get(&protocol).unwrap_or(&address_only); + let port_rules = lower_port_rules( + rules, + protocol, + family, + category, + covered, + budget - lowered.len(), + )?; + lowered.extend(port_rules); + } + Ok(lowered) +} + +/// Returns the union of the addresses of `rules`, as `_compile_family` merges them, reading the +/// addresses that rules share once: the rules of one rule's selectors would otherwise repeat +/// them once for each selector. +fn union<'a>(rules: impl IntoIterator) -> Vec { + let sets: BTreeSet> = rules + .into_iter() + .map(|rule| AddressSet(&rule.addresses)) + .collect(); + address::merge(sets.into_iter().flat_map(|set| set.0.iter().copied())) +} + +/// Returns the networks that cover `intervals`, failing once they exceed `maximum`, as +/// `_intervals_to_networks` does. +fn intervals_to_networks( + intervals: &[Interval], + family: Family, + maximum: usize, + category: &str, +) -> Result> { + let mut networks = Vec::new(); + for &interval in intervals { + for network in address::summarize(family, interval) { + if networks.len() >= maximum { + return Err(too_many_rules(category)); + } + networks.push(network); + } + } + Ok(networks) +} + +/// Returns the networks that cover `protocol_intervals` without those that `covered` holds +/// entirely, failing once they exceed `maximum`, as `_protocol_intervals_to_networks` does. +/// +/// Rules over `covered` already match the traffic, so widening networks into it keeps the result +/// compact without changing what matches. +fn protocol_intervals_to_networks( + protocol_intervals: &[Interval], + covered: &[Interval], + family: Family, + maximum: usize, + category: &str, +) -> Result> { + let combined = address::merge(protocol_intervals.iter().chain(covered).copied()); + let mut networks = Vec::new(); + for interval in combined { + for network in address::summarize(family, interval) { + if address::subtract(&[network.interval()], covered).is_empty() { + continue; + } + if networks.len() >= maximum { + return Err(too_many_rules(category)); + } + networks.push(network); + } + } + Ok(networks) +} + +/// Lowers the rules of one family for the ports of `protocol`, TCP or UDP, emitting at most +/// `budget` native rules, as `_lower_protocol_rules` does. +/// +/// Rules start and end at port boundaries. Between two consecutive boundaries, the active rules +/// select the union of their addresses; the networks of the previous segment whose addresses stay +/// selected extend their ranges, and new networks cover the rest of the union, without the +/// addresses that `covered` already matches on every port. +fn lower_port_rules( + rules: &[&Rule], + protocol: NativeProtocol, + family: Family, + category: &str, + covered: &[Interval], + budget: usize, +) -> Result> { + // Each boundary port, and the address sets that start (+1) or end (-1) there. Ports run to + // 65536, where rules that end at port 65535 end. + let mut events: BTreeMap)>> = BTreeMap::new(); + // Whether each address set holds addresses that `covered` does not, which rules that share + // their addresses ask once. + let mut uncovered: BTreeMap, bool> = BTreeMap::new(); + for rule in rules { + let Some((start, end)) = rule.ports else { + continue; + }; + if rule.protocol != Some(protocol) || rule.addresses.is_empty() { + continue; + } + let addresses = AddressSet(&rule.addresses); + if !*uncovered + .entry(addresses) + .or_insert_with(|| !address::subtract(addresses.0, covered).is_empty()) + { + continue; + } + events + .entry(u32::from(start)) + .or_default() + .push((1, addresses)); + events + .entry(u32::from(end) + 1) + .or_default() + .push((-1, addresses)); + } + + // The number of active rules with each address set. + let mut active: BTreeMap, i64> = BTreeMap::new(); + let mut port_ranges: BTreeMap> = BTreeMap::new(); + // Networks whose last range ends just before the current segment. + let mut open_networks: Vec = Vec::new(); + let mut rule_count = 0; + let mut previous_port: Option = None; + for (&port, port_events) in &events { + if previous_port.is_some() && active.is_empty() { + open_networks.clear(); + } + if let Some(previous) = previous_port + && previous < port + && !active.is_empty() + { + let addresses = address::merge( + active + .keys() + .flat_map(|addresses| addresses.0.iter().copied()), + ); + let available = address::merge(addresses.iter().chain(covered).copied()); + let mut kept: Vec = Vec::new(); + for &network in &open_networks { + if address::subtract(&[network.interval()], &available).is_empty() + && let Some(range) = port_ranges + .get_mut(&network) + .and_then(|ranges| ranges.last_mut()) + { + range.1 = port - 1; + kept.push(network); + } + } + let kept_cover = address::merge( + covered + .iter() + .copied() + .chain(kept.iter().map(|network| network.interval())), + ); + let networks = protocol_intervals_to_networks( + &addresses, + &kept_cover, + family, + budget - rule_count, + category, + )?; + for network in networks { + if rule_count >= budget { + return Err(too_many_rules(category)); + } + port_ranges + .entry(network) + .or_default() + .push((previous, port - 1)); + rule_count += 1; + kept.push(network); + } + open_networks = kept; + } + for &(direction, addresses) in port_events { + let count = active.entry(addresses).or_insert(0); + *count += direction; + if *count == 0 { + active.remove(&addresses); + } + } + previous_port = Some(port); + } + Ok(port_ranges + .into_iter() + .flat_map(|(network, ranges)| { + ranges.into_iter().map(move |(start, end)| { + // Segments end before a boundary of at most 65536, so their ports fit. + let range = (start as u16, end as u16); + (network, Some(protocol), Some(range)) + }) + }) + .collect()) +} + +/// The addresses of a rule, which compare by value, as Python's tuples do: the port sweep counts +/// the active rules of each address set, as `_lower_protocol_rules` counts them in a `Counter`. +/// Like CPython's dictionaries, it compares identical addresses without reading them, so that the +/// rules of one rule's selectors, which share their addresses, compare at once. +#[derive(Debug, Clone, Copy)] +struct AddressSet<'a>(&'a [Interval]); + +impl PartialEq for AddressSet<'_> { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} + +impl Eq for AddressSet<'_> {} + +impl PartialOrd for AddressSet<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AddressSet<'_> { + fn cmp(&self, other: &Self) -> Ordering { + if std::ptr::eq(self.0, other.0) { + Ordering::Equal + } else { + self.0.cmp(other.0) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_rules_of_one_rule_share_its_addresses() { + let ipv4: Vec = vec![(1, 1), (3, 3)]; + let ipv6: Vec = vec![(5, 6)]; + let selectors: Vec = vec![ + (Some(NativeProtocol::Tcp), Some((80, 80))), + (Some(NativeProtocol::Udp), Some((53, 60))), + (None, None), + ]; + let rules = combine( + Some(vec![(Family::V4, ipv4.clone()), (Family::V6, ipv6.clone())]), + Some(selectors.clone()), + ); + assert_eq!(rules.len(), 6); + for (family_rules, (family, addresses)) in rules + .chunks(selectors.len()) + .zip([(Family::V4, &ipv4), (Family::V6, &ipv6)]) + { + for (rule, &(protocol, ports)) in family_rules.iter().zip(&selectors) { + assert_eq!(rule.family, family); + assert_eq!(*rule.addresses, **addresses); + assert_eq!((rule.protocol, rule.ports), (protocol, ports)); + assert!(Rc::ptr_eq(&rule.addresses, &family_rules[0].addresses)); + } + assert_eq!( + Rc::strong_count(&family_rules[0].addresses), + selectors.len() + ); + } + + let rules = combine(None, Some(selectors.clone())); + assert_eq!(rules.len(), 6); + for (family_rules, family) in rules.chunks(selectors.len()).zip(Family::ALL) { + for rule in family_rules { + assert_eq!(rule.family, family); + assert_eq!(*rule.addresses, [family.everything()]); + assert!(Rc::ptr_eq(&rule.addresses, &family_rules[0].addresses)); + } + } + + let rules = combine(Some(vec![(Family::V6, ipv6.clone())]), None); + assert_eq!( + rules, + [Rule { + family: Family::V6, + addresses: Rc::from(ipv6), + protocol: None, + ports: None, + }] + ); + } + + #[test] + fn address_sets_compare_by_value() { + let addresses: Rc<[Interval]> = Rc::from([(1, 2), (4, 4)]); + let copy: Rc<[Interval]> = Rc::from(&*addresses); + let longer: Rc<[Interval]> = Rc::from([(1, 2), (4, 4), (6, 6)]); + let later: Rc<[Interval]> = Rc::from([(1, 3)]); + let empty: Rc<[Interval]> = Rc::from([]); + assert_eq!(AddressSet(&addresses), AddressSet(&addresses)); + assert_eq!(AddressSet(&addresses), AddressSet(©)); + assert!(AddressSet(&empty) < AddressSet(&addresses)); + assert!(AddressSet(&addresses) < AddressSet(&longer)); + assert!(AddressSet(&longer) < AddressSet(&later)); + let sets: BTreeSet> = [&later, &addresses, ©, &empty, &longer] + .into_iter() + .map(|addresses| AddressSet(addresses)) + .collect(); + let sorted: Vec<&[Interval]> = sets.iter().map(|set| set.0).collect(); + assert_eq!(sorted, [&*empty, &*addresses, &*longer, &*later]); + } + + #[test] + fn shared_addresses_are_merged_once() { + let addresses: Vec = (0..64).map(|index| (2 * index, 2 * index)).collect(); + // The union of the 64 rules for every protocol reads their addresses once; for ICMP, so + // do the cover of its networks and the addresses that it covers. Python merges the + // addresses of each rule into the union: 64 times as many. + for (protocol, merged) in [(None, 64), (Some(NativeProtocol::Icmp), 3 * 64)] { + let rules = combine( + Some(vec![(Family::V4, addresses.clone())]), + Some(vec![(protocol, None); 64]), + ); + assert_eq!(rules.len(), 64); + let (lowered, _, merged_intervals) = address::probe::count(|| lower(&rules, "allow")); + assert_eq!(lowered.unwrap().len(), 64, "{protocol:?}"); + assert_eq!(merged_intervals, merged, "{protocol:?}"); + } + } +} diff --git a/nvx-rs/src/host/network.rs b/nvx-rs/src/host/network/mod.rs similarity index 73% rename from nvx-rs/src/host/network.rs rename to nvx-rs/src/host/network/mod.rs index 22ce03cba..72dfc5115 100644 --- a/nvx-rs/src/host/network.rs +++ b/nvx-rs/src/host/network/mod.rs @@ -1,21 +1,33 @@ -//! Translation of a network posture into OpenVMM options. +//! Translation of a network posture into OpenVMM options, and the egress policy files of +//! `nvx.py`. //! //! OpenVMM's portable profile enforces an egress default plus IPv4 and IPv6 allow and deny rules //! that match a network of one family and, optionally, one protocol (TCP, UDP, or ICMP) and, for -//! TCP or UDP, one port or an inclusive range of ports; deny rules take precedence. [`EgressRule`]s -//! are expanded into that form exactly: exceptions are subtracted from their networks, a rule -//! without destinations matches `0.0.0.0/0` and `::/0`, and `any` protocol with a port or a port -//! range becomes a TCP and a UDP rule. Rules that cannot be expressed exactly are rejected. +//! TCP or UDP, one port or an inclusive range of ports; deny rules take precedence. +//! [`EgressRule`]s are lowered into that form exactly, by the lowering that also compiles egress +//! policy files ([`policy`]): exceptions are subtracted from their networks, a rule without +//! destinations matches `0.0.0.0/0` and `::/0`, `any` protocol with a port or a port range becomes +//! a TCP and a UDP rule, destinations merge into the fewest networks that cover them, rules for +//! every port of a protocol subsume narrower rules, and overlapping and adjacent port ranges +//! merge. Each action emits at most [`MAX_RULES`] OpenVMM rules, sorted by family, address, prefix +//! length, protocol, and ports. //! //! Host-loopback access is bidirectional: with it allowed, the guest reaches host loopback //! services through its gateway, and OpenVMM publishes the guest's listeners on host loopback. //! [`LoopbackForward`]s publish further host loopback ports at guest ports of their choice. +mod address; +mod lowering; +pub mod policy; + use std::collections::HashSet; use std::net::Ipv4Addr; +use self::address::{Family, Interval}; +use self::lowering::{PortValue, Rule, RuleProtocol}; use crate::error::{Error, Result}; -use crate::net::cidr::{self, Cidr}; +use crate::host::python::text::PyText; +use crate::net::cidr; /// Largest number of allow rules, and of deny rules, that OpenVMM accepts. pub const MAX_RULES: usize = 256; @@ -62,11 +74,14 @@ pub enum Protocol { } /// A destination network of an [`EgressRule`], without its exceptions. +/// +/// Networks are read as egress policy files read their CIDRs ([`policy`]): host bits are cleared, +/// IPv4 netmasks such as `/255.0.0.0` are accepted, and IPv6 scopes are refused. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Destination { /// Destination address or CIDR, such as `"10.0.0.0/8"` or `"192.0.2.1"`. pub cidr: String, - /// Sub-networks of `cidr` that the rule does not match. + /// Sub-networks of `cidr` that the rule does not match, each within `cidr`. pub except: Vec, } @@ -149,7 +164,9 @@ impl EgressRule { } /// The egress rules of one action, and the name by which messages refer to them, such as -/// `egress.allow`. Messages name a rule by its index, as in `egress.allow[0]`. +/// `egress.allow`. Messages name a rule by its index and its fields by their MXC names, as in +/// `egress.allow[0].to[1].cidr`, and the action by the name, as in `egress.allow emits at most 256 +/// native rules`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EgressRules { /// The name of the rules in messages. @@ -269,10 +286,12 @@ impl NetworkPolicy { /// Without a policy, or when egress is denied without allow rules and ingress and host-loopback /// access are denied, no network device is attached. Otherwise the portable profile applies the /// egress posture, the ingress default, the host-loopback access, and the host-loopback forwards. +/// Each action's rules are lowered as an egress policy file's are ([`policy`]), and fail with its +/// messages, which name the rules after [`EgressRules::name`]. /// -/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) when a rule cannot be -/// expressed exactly, when the rules of one action expand to more than [`MAX_RULES`] OpenVMM -/// rules, when host-loopback access is allowed for a guest network whose prefix is longer than +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) when a rule is invalid, +/// when the rules of one action need more than [`MAX_RULES`] OpenVMM rules, when host-loopback +/// access is allowed for a guest network whose prefix is longer than /// [`MAX_HOST_LOOPBACK_PREFIX`], or when the host-loopback forwards break the rules of /// [`NetworkPolicy::host_loopback_forwards`]. pub fn openvmm_arguments( @@ -321,30 +340,21 @@ pub fn openvmm_arguments( ] .map(str::to_owned) .to_vec(); + // As in an egress policy file, the allow rules are checked and lowered before the deny rules. + let mut deny = Vec::new(); for (rules, option) in [ (&policy.allow, "--network-egress-allow"), (&policy.deny, "--network-egress-deny"), ] { - let field = &rules.name; - let mut expanded = Vec::new(); - for (index, rule) in rules.rules.iter().enumerate() { - expand_rule(rule, &format!("{field}[{index}]"), &mut expanded)?; - } - expanded.sort_unstable(); - expanded.dedup(); - if expanded.len() > MAX_RULES { - return Err(Error::unsupported(format!( - "{field} expands to {} OpenVMM rules; the openvmm backend accepts at most \ - {MAX_RULES}", - expanded.len() - ))); - } - for rule in expanded { + let parsed = parse_rules(rules)?; + let lowered = lowering::lower(&parsed.concat(), &rules.name)?; + for rule in lowered { arguments.push(option.to_owned()); arguments.push(rule); } + deny = parsed; } - check_loopback_forwards(policy, guest_network)?; + check_loopback_forwards(policy, &deny, guest_network)?; for forward in &policy.host_loopback_forwards { arguments.push("--host-loopback-forward".to_owned()); arguments.push(format!( @@ -357,10 +367,67 @@ pub fn openvmm_arguments( Ok(arguments) } -/// Checks the host-loopback forwards of a policy whose rules expand, and whose host-loopback +/// Parses the rules of one action, each into the rules that the lowering takes, as an egress +/// policy file's MXC rules are parsed: an empty `to` or `ports` is a rule without that field. +fn parse_rules(rules: &EgressRules) -> Result>> { + rules + .rules + .iter() + .enumerate() + .map(|(index, rule)| parse_rule(rule, &format!("{}[{index}]", rules.name))) + .collect() +} + +/// Parses one rule, which messages call `description`. +fn parse_rule(rule: &EgressRule, description: &str) -> Result> { + let mut destinations = None; + if !rule.to.is_empty() { + let mut parsed = Vec::with_capacity(rule.to.len()); + for (index, peer) in rule.to.iter().enumerate() { + let description = format!("{description}.to[{index}]"); + let network = |value: &str, field: &str| lowering::network(&PyText::from(value), field); + let parent = network(&peer.cidr, &format!("{description}.cidr"))?; + parsed.push(lowering::destination( + parent, + peer.except.iter().enumerate().map(|(exception, value)| { + network(value, &format!("{description}.except[{exception}]")) + }), + &description, + )?); + } + destinations = Some(lowering::group_destinations(&parsed)); + } + let mut selectors = None; + if !rule.ports.is_empty() { + let mut parsed = Vec::new(); + for (index, port) in rule.ports.iter().enumerate() { + let protocol = match port.protocol { + Protocol::Tcp => RuleProtocol::Tcp, + Protocol::Udp => RuleProtocol::Udp, + Protocol::Icmp => RuleProtocol::Icmp, + Protocol::Any => RuleProtocol::Any, + }; + let value = |port: u16| PortValue::Integer(i128::from(port)); + parsed.extend(lowering::port_selectors( + protocol, + port.port.map(value), + port.end_port.map(value), + &format!("{description}.ports[{index}]"), + )?); + } + selectors = Some(parsed); + } + Ok(lowering::combine(destinations, selectors)) +} + +/// Checks the host-loopback forwards of a policy whose rules lower, and whose host-loopback /// access is allowed when it has forwards, against the rules of -/// [`NetworkPolicy::host_loopback_forwards`]. -fn check_loopback_forwards(policy: &NetworkPolicy, guest_network: &str) -> Result<()> { +/// [`NetworkPolicy::host_loopback_forwards`]. `deny` holds the parsed deny rules. +fn check_loopback_forwards( + policy: &NetworkPolicy, + deny: &[Vec], + guest_network: &str, +) -> Result<()> { let forwards = &policy.host_loopback_forwards; if forwards.is_empty() { return Ok(()); @@ -401,11 +468,19 @@ fn check_loopback_forwards(policy: &NetworkPolicy, guest_network: &str) -> Resul "host-loopback forwards need egress allowed by default for their replies".to_owned(), ); } - let Some(network) = guest_subnet(guest_network) else { + let Some((first, last)) = guest_subnet(guest_network) else { return unsupported(format!("guest network {guest_network:?} is not valid")); }; - for (index, rule) in policy.deny.rules.iter().enumerate() { - if rule_covers(rule, network) { + // A deny rule covers the guest network if it selects one of its addresses, on any protocol. + for (index, rules) in deny.iter().enumerate() { + let covers = rules.iter().any(|rule| { + rule.family == Family::V4 + && rule + .addresses + .iter() + .any(|&(start, end)| start <= last && first <= end) + }); + if covers { return unsupported(format!( "{}[{index}] cannot cover the guest network, which carries host-loopback forward \ replies", @@ -416,132 +491,15 @@ fn check_loopback_forwards(policy: &NetworkPolicy, guest_network: &str) -> Resul Ok(()) } -/// Returns the IPv4 network of `guest_network`, a static guest address in `ADDRESS/PREFIX` form. -fn guest_subnet(guest_network: &str) -> Option { +/// Returns the addresses of the IPv4 network of `guest_network`, a static guest address in +/// `ADDRESS/PREFIX` form. +fn guest_subnet(guest_network: &str) -> Option { let (address, prefix) = guest_network.split_once('/')?; let address: Ipv4Addr = address.parse().ok()?; let prefix = cidr::parse_prefix_length(prefix).filter(|prefix| (1..=32).contains(prefix))?; let mask = u32::MAX << (32 - u32::from(prefix)); - Cidr::parse(&format!( - "{}/{prefix}", - Ipv4Addr::from(u32::from(address) & mask) - )) - .ok() -} - -/// Returns whether `rule`, whose destinations parse, matches an address of `network`. -fn rule_covers(rule: &EgressRule, network: Cidr) -> bool { - rule.to.is_empty() - || rule.to.iter().any(|peer| { - let Ok(destination) = Cidr::parse(&peer.cidr) else { - return false; - }; - let excluded = peer - .except - .iter() - .filter_map(|value| Cidr::parse(value).ok()) - .collect::>(); - destination - .subtract(&excluded, MAX_RULES) - .is_none_or(|remainder| remainder.iter().any(|part| part.overlaps(network))) - }) -} - -/// Appends the OpenVMM rules equivalent to `rule`. -fn expand_rule(rule: &EgressRule, field: &str, output: &mut Vec) -> Result<()> { - let unsupported = |message: String| Err(Error::unsupported(format!("{field}{message}"))); - let mut networks = Vec::new(); - if rule.to.is_empty() { - networks.extend([Cidr::everything(false), Cidr::everything(true)]); - } - for (index, peer) in rule.to.iter().enumerate() { - let Ok(network) = Cidr::parse(&peer.cidr) else { - return unsupported(format!(".to[{index}]: the destination is not a network")); - }; - let mut excluded = Vec::new(); - for value in &peer.except { - match Cidr::parse(value) { - Ok(exclusion) if network.contains(exclusion) => excluded.push(exclusion), - _ => { - return unsupported(format!( - ".to[{index}].except: an exception is not a network within the destination" - )); - } - } - } - let Some(remainder) = network.subtract(&excluded, MAX_RULES - networks.len()) else { - return unsupported(format!( - " covers more than {MAX_RULES} networks after removing its exceptions" - )); - }; - networks.extend(remainder); - } - - let mut selectors = Vec::new(); - if rule.ports.is_empty() { - selectors.push(Selector::Everything); - } - for (index, port) in rule.ports.iter().enumerate() { - let protocols: &[&'static str] = match port.protocol { - Protocol::Tcp => &["tcp"], - Protocol::Udp => &["udp"], - Protocol::Icmp => &["icmp"], - Protocol::Any => &["tcp", "udp"], - }; - let Some(start) = port.port else { - if port.end_port.is_some() { - return unsupported(format!(".ports[{index}].endPort requires port")); - } - if port.protocol == Protocol::Any { - selectors.push(Selector::Everything); - } else { - selectors.extend(protocols.iter().copied().map(Selector::Protocol)); - } - continue; - }; - if port.protocol == Protocol::Icmp { - return unsupported(format!(".ports[{index}]: ICMP has no ports")); - } - let end = port.end_port.unwrap_or(start); - if end < start { - return unsupported(format!(".ports[{index}].endPort must not be below port")); - } - selectors.extend( - protocols - .iter() - .map(|&protocol| Selector::Ports(protocol, start, end)), - ); - } - - for network in &networks { - for selector in &selectors { - output.push(match *selector { - Selector::Everything => network.to_string(), - Selector::Protocol(protocol) => format!("{network}:{protocol}"), - Selector::Ports(protocol, start, end) if start == end => { - format!("{network}:{protocol}:{start}") - } - Selector::Ports(protocol, start, end) => { - format!("{network}:{protocol}:{start}-{end}") - } - }); - if output.len() > MAX_RULES * 4 { - return unsupported(format!(" expands to more than {MAX_RULES} OpenVMM rules")); - } - } - } - Ok(()) -} - -/// The traffic that one OpenVMM rule selects at its destination network. -#[derive(Clone, Copy)] -enum Selector { - /// Every protocol and port. - Everything, - /// Every port of TCP or UDP, or every ICMP message. - Protocol(&'static str), - /// The TCP or UDP ports from the first through the last, both included. - Ports(&'static str, u16, u16), + let network = u32::from(address) & mask; + Some((u128::from(network), u128::from(network | !mask))) } #[cfg(test)] @@ -721,6 +679,9 @@ mod tests { EgressRule::to("10.0.0.0/8"), EgressRule::to("10.0.0.0/24"), EgressRule::to("10.0.0.77").on_port(Protocol::Tcp, 443), + // Networks are read as egress policy files read them, host bits and netmasks included. + EgressRule::to("10.0.0.77/24"), + EgressRule::to("10.0.0.0/255.255.255.0"), ] { rejected(&valid.clone().with_deny(rule), "forward replies"); } @@ -785,7 +746,7 @@ mod tests { } #[test] - fn ipv6_rules_expand_exactly() { + fn ipv6_rules_lower_exactly() { let egress = NetworkPolicy::new(Access::Deny) .with_allow(EgressRule::to("2001:db8:1::/64").on_port(Protocol::Tcp, 443)) .with_allow(EgressRule { @@ -803,16 +764,17 @@ mod tests { .with_allow(EgressRule::to("::/0").on_protocol(Protocol::Icmp)) .with_deny(EgressRule::to("2001:db8:1::123")); let arguments = openvmm_arguments(Some(&egress), "10.0.0.2/24").unwrap(); + // Rules sort by family and then by address, so `::/0` leads the IPv6 rules. assert_eq!( rules(&arguments, "--network-egress-allow"), [ "192.0.2.0/24:icmp", + "::/0:icmp", "2001:db8:1::/64:tcp:443", "2001:db8:2::/128:tcp:53", "2001:db8:2::/128:udp:53", "2001:db8:2::2/127:tcp:53", "2001:db8:2::2/127:udp:53", - "::/0:icmp", ] ); assert_eq!( @@ -821,6 +783,167 @@ mod tests { ); } + #[test] + fn rules_lower_like_egress_policy_files() { + let lowered = |rules: Vec| { + let mut egress = NetworkPolicy::new(Access::Deny); + egress.allow.rules = rules; + let arguments = openvmm_arguments(Some(&egress), "10.0.0.2/24").unwrap(); + self::rules(&arguments, "--network-egress-allow") + }; + let tcp = |cidr: &str, port, end_port| { + EgressRule::to(cidr).on_port_range(Protocol::Tcp, port, end_port) + }; + // Destinations merge into the fewest networks, and port ranges into one range. + assert_eq!( + lowered(vec![EgressRule { + to: (0..300) + .map(|index| Destination::new(format!("10.0.{}.0/24", index % 256))) + .collect(), + ports: vec![PortSelector { + protocol: Protocol::Tcp, + port: Some(1), + end_port: Some(2), + }], + }]), + ["10.0.0.0/16:tcp:1-2"] + ); + assert_eq!( + lowered(vec![ + tcp("192.0.2.7", 8000, 8004), + tcp("192.0.2.7", 8003, 8010), + tcp("192.0.2.6", 8000, 8010), + ]), + ["192.0.2.6/31:tcp:8000-8010"] + ); + // Rules for every port of a protocol, or every protocol, subsume narrower ones. + assert_eq!( + lowered(vec![ + EgressRule::to("192.0.2.7").on_port(Protocol::Tcp, 443), + EgressRule::to("192.0.2.0/24").on_protocol(Protocol::Tcp), + EgressRule::to("198.51.100.0/24").on_port(Protocol::Udp, 53), + EgressRule::to("198.51.100.0/23"), + ]), + ["192.0.2.0/24:tcp", "198.51.100.0/23"] + ); + // Host bits are cleared, as an egress policy file's are. + assert_eq!( + lowered(vec![EgressRule::to("10.0.0.5/255.255.255.0")]), + ["10.0.0.0/24"] + ); + } + + #[test] + fn invalid_rules_are_rejected_with_the_policy_compilers_messages() { + let port = |protocol, port, end_port| EgressRule { + to: vec![Destination::new("10.0.0.0/8")], + ports: vec![PortSelector { + protocol, + port, + end_port, + }], + }; + let excluding = |cidr: &str, except: &str| EgressRule { + to: vec![ + Destination::new("192.0.2.0/24"), + Destination { + cidr: cidr.to_owned(), + except: vec![except.to_owned()], + }, + ], + ports: Vec::new(), + }; + for (rule, message) in [ + ( + port(Protocol::Icmp, Some(8), None), + "egress.allow[0].ports[0].port is not supported with icmp", + ), + // Structural validation rejects these first, unless the backend is called directly. + ( + port(Protocol::Tcp, Some(90), Some(80)), + "egress.allow[0].ports[0].endPort 80 cannot be below port 90", + ), + ( + port(Protocol::Udp, None, Some(80)), + "egress.allow[0].ports[0].endPort requires port", + ), + ( + port(Protocol::Tcp, Some(0), None), + "egress.allow[0].ports[0].port must be between 1 and 65535", + ), + ( + port(Protocol::Any, Some(80), Some(0)), + "egress.allow[0].ports[0].endPort must be between 1 and 65535", + ), + ( + EgressRule::to("example.com"), + "egress.allow[0].to[0].cidr is not a valid IPv4 or IPv6 CIDR: example.com", + ), + ( + EgressRule::to("fe80::1%eth0/64"), + "egress.allow[0].to[0].cidr must not name an IPv6 scope: fe80::1%eth0/64", + ), + ( + excluding("10.0.0.0/8", "11.0.0.0/16"), + "egress.allow[0].to[1].except[0] must be contained in 10.0.0.0/8", + ), + ( + excluding("10.0.0.7/8", "10.1.0.0/+16"), + "egress.allow[0].to[1].except[0] is not a valid IPv4 or IPv6 CIDR: 10.1.0.0/+16", + ), + // 129 separate ranges of both TCP and UDP need 258 OpenVMM rules. + ( + EgressRule { + to: vec![Destination::new("10.0.0.0/8")], + ports: (0..129) + .map(|index| PortSelector { + protocol: Protocol::Any, + port: Some(3 * index + 1), + end_port: Some(3 * index + 2), + }) + .collect(), + }, + "egress.allow emits at most 256 native rules", + ), + ] { + let egress = NetworkPolicy::new(Access::Deny).with_allow(rule.clone()); + let error = openvmm_arguments(Some(&egress), "10.0.0.2/24").unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unsupported, "{rule:?}"); + assert_eq!(error.message(), message, "{rule:?}"); + } + // The allow rules are checked and lowered before the deny rules. + let egress = NetworkPolicy::new(Access::Allow) + .with_allow(EgressRule::to("10.0.0.0/33")) + .with_deny(port(Protocol::Icmp, Some(8), None)); + let error = openvmm_arguments(Some(&egress), "10.0.0.2/24").unwrap_err(); + assert_eq!( + error.message(), + "egress.allow[0].to[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.0/33" + ); + } + + #[test] + fn exceptions_that_split_a_network_too_finely_are_rejected() { + // Without a limit, these exceptions would split the address space into 512 * 23 networks. + let peer = Destination { + cidr: "0.0.0.0/0".to_owned(), + except: (0..512u32) + .map(|index| std::net::Ipv4Addr::from(index << 23).to_string()) + .collect(), + }; + let rule = EgressRule { + to: vec![peer], + ports: Vec::new(), + }; + let egress = NetworkPolicy::new(Access::Deny).with_allow(rule); + let error = openvmm_arguments(Some(&egress), "10.0.0.2/24").unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unsupported); + assert_eq!( + error.message(), + "egress.allow emits at most 256 native rules" + ); + } + #[test] fn exclusions_remain_local_to_their_rules() { let excluding = |cidr: &str, excluded: &str| EgressRule { @@ -908,74 +1031,4 @@ mod tests { ["192.0.2.7/32:tcp:8005"] ); } - - #[test] - fn inexpressible_rules_are_rejected() { - let port = |protocol, port, end_port| EgressRule { - to: vec![Destination::new("10.0.0.0/8")], - ports: vec![PortSelector { - protocol, - port, - end_port, - }], - }; - for rule in [ - port(Protocol::Icmp, Some(8), None), - // Structural validation rejects these first, unless the backend is called directly. - port(Protocol::Tcp, Some(90), Some(80)), - port(Protocol::Udp, None, Some(80)), - EgressRule { - to: (0..300) - .map(|index| Destination::new(format!("10.0.{}.0/24", index % 256))) - .collect(), - ports: vec![PortSelector { - protocol: Protocol::Tcp, - port: Some(1), - end_port: Some(2), - }], - }, - // 129 separate ranges of both TCP and UDP need 258 OpenVMM rules. - EgressRule { - to: vec![Destination::new("10.0.0.0/8")], - ports: (0..129) - .map(|index| PortSelector { - protocol: Protocol::Any, - port: Some(3 * index + 1), - end_port: Some(3 * index + 2), - }) - .collect(), - }, - ] { - let egress = NetworkPolicy::new(Access::Deny).with_allow(rule.clone()); - assert_eq!( - openvmm_arguments(Some(&egress), "10.0.0.2/24") - .unwrap_err() - .kind(), - ErrorKind::Unsupported, - "{rule:?}" - ); - } - } - - #[test] - fn exceptions_that_split_a_network_too_finely_are_rejected() { - // Without a limit, these exceptions would split the address space into 512 * 23 networks. - let peer = Destination { - cidr: "0.0.0.0/0".to_owned(), - except: (0..512u32) - .map(|index| std::net::Ipv4Addr::from(index << 23).to_string()) - .collect(), - }; - let rule = EgressRule { - to: vec![peer], - ports: Vec::new(), - }; - let egress = NetworkPolicy::new(Access::Deny).with_allow(rule); - let error = openvmm_arguments(Some(&egress), "10.0.0.2/24").unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unsupported); - assert!( - error.message().contains("covers more than 256 networks"), - "{error}" - ); - } } diff --git a/nvx-rs/src/host/network/policy/mod.rs b/nvx-rs/src/host/network/policy/mod.rs new file mode 100644 index 000000000..bf5d07048 --- /dev/null +++ b/nvx-rs/src/host/network/policy/mod.rs @@ -0,0 +1,489 @@ +//! Egress policy files, which `nvx.py run` and `nvx.py sandbox` accept with +//! `--network-egress-policy-file`, and their compilation into OpenVMM's native rules. +//! +//! This is a faithful port of `scripts/nvx_tools/egress_policy.py`: it accepts the same +//! documents, compiles them to the same rules, and fails with the same messages. +//! +//! # Format +//! +//! A policy is a JSON object of at most [`MAX_POLICY_FILE_SIZE`] bytes of UTF-8, without a +//! byte-order mark, whose only fields are `allow` and `deny`, each an array of rules. A field may +//! appear only once in an object, and an integer may have at most 64 digits. Each rule has one of +//! two forms: +//! +//! - The MXC form, `{"to": [PEER, ...], "ports": [PORT, ...]}`, whose fields are both optional: +//! - each `PEER` is `{"cidr": CIDR, "except": [CIDR, ...]}`, a destination network without +//! the networks of its optional `except`, which must lie within it; a rule without `to` +//! selects every address of both families; +//! - each `PORT` is `{"protocol": PROTOCOL, "port": FIRST, "endPort": LAST}`, all optional, +//! where `PROTOCOL` is `tcp`, `udp`, `icmp`, or `any`, the default; `port` selects one TCP or +//! UDP port, and with `endPort` the ports from `port` through `endPort`, from 1 to 65535; a +//! rule without `ports` selects every protocol and port. +//! - The legacy flat form, `{"cidr": CIDR, "except": [CIDR, ...], "protocol": PROTOCOL, "port": +//! FIRST, "endPort": LAST}`, which needs `cidr`, and `protocol` with `port`. +//! +//! `any` selects every protocol, or with a port both TCP and UDP. A `CIDR` is an IPv4 or IPv6 +//! network as Python's `ipaddress.ip_interface` reads it: host bits are cleared, IPv4 netmasks +//! such as `/255.255.0.0` are accepted, and IPv6 scopes are refused. +//! +//! # Compilation +//! +//! Each action compiles to at most [`MAX_RULES_PER_ACTION`] native rules: `CIDR`, `CIDR:PROTOCOL`, +//! `CIDR:PROTOCOL:PORT`, or `CIDR:PROTOCOL:FIRST-LAST`, sorted by family, address, prefix length, +//! protocol, and ports. Destinations are merged into the fewest networks that cover them, rules +//! for every port of a protocol subsume the narrower rules that they cover, and overlapping and +//! adjacent port ranges merge. OpenVMM applies deny rules before allow rules, so each action is +//! compiled on its own. +//! +//! # Fidelity +//! +//! Messages are Python's, byte for byte, with two representation choices: a string that Python +//! would hold with an unpaired surrogate, which JSON escapes such as `\ud800` and paths that are +//! not Unicode produce, shows each surrogate as `\udXXX`, which is how Python prints it, and +//! paths show as `str(pathlib.Path(path))`. Python's decoder raises `RecursionError` for documents +//! nested more deeply than its stack allows; this reader has no such limit, so those documents +//! fail validation like any other invalid policy. + +#[cfg(test)] +mod tests; + +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use serde::Serialize; + +use super::address::Family; +use super::lowering::{self, PortValue, Rule, RuleProtocol, Selector, invalid}; +use crate::error::{Error, Result}; +use crate::host::python::json::{self, Document, Hooks, Node, NodeId, ReadError}; +use crate::host::python::path::python_path; +use crate::host::python::text::PyText; + +/// Largest number of native rules that one action, allow or deny, compiles to: the number of +/// rules of each action that OpenVMM accepts, [`MAX_RULES`](super::MAX_RULES). +pub const MAX_RULES_PER_ACTION: usize = super::MAX_RULES; + +/// Largest egress policy file, in bytes: 1 MiB. +pub const MAX_POLICY_FILE_SIZE: usize = 1024 * 1024; + +/// Longest integer, in digits without its sign, that a policy file may hold. +const MAX_JSON_INTEGER_DIGITS: usize = 64; + +/// The native OpenVMM rules of an egress policy. +/// +/// Each rule is an `--network-egress-allow` or `--network-egress-deny` value: `CIDR`, +/// `CIDR:PROTOCOL`, `CIDR:PROTOCOL:PORT`, or `CIDR:PROTOCOL:FIRST-LAST`. Its serde form is +/// `{"allow": [...], "deny": [...]}`. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] +pub struct CompiledEgressPolicy { + /// Rules of traffic to permit, at most [`MAX_RULES_PER_ACTION`]. + pub allow: Vec, + /// Rules of traffic to block, which OpenVMM applies first, at most [`MAX_RULES_PER_ACTION`]. + pub deny: Vec, +} + +/// Compiles the egress policy file at `path`, as `compile_policy_file` in `nvx.py` does. +/// +/// Messages name the file as `str(pathlib.Path(path))`, which drops empty and `.` components and, +/// on Windows, writes `\` for `/`, and the file opened is that normalized path, as in Python. +/// +/// # Errors +/// +/// [`ErrorKind::Io`](crate::ErrorKind::Io) when the file cannot be read; otherwise +/// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) when the file exceeds +/// [`MAX_POLICY_FILE_SIZE`], is not UTF-8 JSON that Python reads, or holds an invalid policy; +/// see [`compile_policy_bytes`]. +pub fn compile_policy_file(path: &Path) -> Result { + let path = python_path(path); + let unreadable = |error: std::io::Error| { + Error::io(format!( + "failed to read egress policy file: {}", + path.display + )) + .with_source(error) + }; + // Reading one byte more than the limit detects oversized files whatever their metadata say. + let mut data = Vec::new(); + File::open(&path.native) + .and_then(|file| { + file.take(MAX_POLICY_FILE_SIZE as u64 + 1) + .read_to_end(&mut data) + }) + .map_err(unreadable)?; + compile_contents(&data, &path.display) +} + +/// Compiles `data`, the contents of the egress policy file at `path`, which messages name as +/// [`compile_policy_file`] does, after it has read them. +/// +/// # Errors +/// +/// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) with Python's message: +/// +/// - `egress policy file exceeds 1048576-byte limit: PATH`; +/// - `failed to read egress policy file: PATH` for data that is not UTF-8, or not JSON that +/// Python's `json.loads` reads; +/// - `duplicate JSON property: NAME` and `JSON integer exceeds 64-digit limit`; or +/// - an error of [`compile_policy`]. +pub fn compile_policy_bytes(data: &[u8], path: &Path) -> Result { + compile_contents(data, &python_path(path).display) +} + +/// Compiles the contents of the policy file that messages name `path`. +fn compile_contents(data: &[u8], path: &str) -> Result { + if data.len() > MAX_POLICY_FILE_SIZE { + return Err(invalid(format!( + "egress policy file exceeds {MAX_POLICY_FILE_SIZE}-byte limit: {path}" + ))); + } + let unreadable = || invalid(format!("failed to read egress policy file: {path}")); + let text = std::str::from_utf8(data).map_err(|_| unreadable())?; + let document = json::read(text, &mut StrictJson).map_err(|error| match error { + ReadError::Syntax { .. } => unreadable(), + ReadError::Hook(error) => error, + })?; + compile_document(&document) +} + +/// Compiles an egress policy that has already been parsed, as `compile_policy` in `nvx.py` does. +/// +/// A parsed value cannot repeat a field, and its integers have no length limit: those checks +/// belong to the file format, which [`compile_policy_bytes`] reads. +/// +/// # Errors +/// +/// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) with Python's message, such as +/// `allow[0].to[1].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.256/8` or `deny emits at most +/// 256 native rules`. +pub fn compile_policy(value: &serde_json::Value) -> Result { + compile_document(&Document::from_value(value)) +} + +/// The checks of `nvx.py`'s policy reader: `strict_json_object` refuses repeated fields, and +/// `_bounded_json_integer` integers of more than 64 digits. +struct StrictJson; + +impl Hooks for StrictJson { + type Error = Error; + + fn object(&mut self, members: &[(PyText, NodeId)]) -> Result<()> { + let mut seen = std::collections::HashSet::with_capacity(members.len()); + for (name, _) in members { + if !seen.insert(name) { + return Err(invalid(format!( + "duplicate JSON property: {}", + name.render() + ))); + } + } + Ok(()) + } + + fn integer(&mut self, literal: &str) -> Result<()> { + if literal.trim_start_matches('-').len() > MAX_JSON_INTEGER_DIGITS { + return Err(invalid(format!( + "JSON integer exceeds {MAX_JSON_INTEGER_DIGITS}-digit limit" + ))); + } + Ok(()) + } +} + +/// Field names of a policy. +const ROOT_FIELDS: &[&str] = &["allow", "deny"]; +/// Field names of an MXC rule. +const MXC_RULE_FIELDS: &[&str] = &["to", "ports"]; +/// Field names of a destination of an MXC rule. +const PEER_FIELDS: &[&str] = &["cidr", "except"]; +/// Field names of a port selector of an MXC rule. +const PORT_FIELDS: &[&str] = &["protocol", "port", "endPort"]; +/// Field names of a legacy flat rule. +const LEGACY_RULE_FIELDS: &[&str] = &["cidr", "except", "protocol", "port", "endPort"]; + +/// A JSON object of a policy document. +struct Object<'a> { + /// The document that holds the object. + document: &'a Document, + /// The object's members, whose names are distinct. + members: &'a [(PyText, NodeId)], +} + +impl<'a> Object<'a> { + /// Returns the value of the field `name`, if the object has it. + fn get(&self, name: &str) -> Option { + self.members + .iter() + .find(|(field, _)| field.as_bytes() == name.as_bytes()) + .map(|&(_, value)| value) + } + + /// Returns whether the object has the field `name`. + fn has(&self, name: &str) -> bool { + self.get(name).is_some() + } + + /// Fails if the object has a field other than `fields`, naming the first unknown field in + /// code-point order, as `sorted(set(rule) - FIELDS)[0]` does. + fn check_fields(&self, fields: &[&str], description: &str) -> Result<()> { + let unknown = self + .members + .iter() + .map(|(name, _)| name) + .filter(|name| { + !fields + .iter() + .any(|field| name.as_bytes() == field.as_bytes()) + }) + .min(); + match unknown { + Some(name) => Err(invalid(format!( + "{description} has unknown field '{}'", + name.render() + ))), + None => Ok(()), + } + } + + /// Returns whether the object has any of `fields`. + fn has_any(&self, fields: &[&str]) -> bool { + fields.iter().any(|field| self.has(field)) + } + + /// Returns the node of `id` in the object's document. + fn node(&self, id: NodeId) -> &'a Node { + self.document.node(id) + } +} + +/// Returns the object at `id`, as `_object` does. +fn object<'a>(document: &'a Document, id: NodeId, description: &str) -> Result> { + match document.node(id) { + Node::Object(members) => Ok(Object { document, members }), + _ => Err(invalid(format!("{description} must be an object"))), + } +} + +/// Returns the elements of the array at `id`, as `_array` does. +fn array<'a>(document: &'a Document, id: NodeId, description: &str) -> Result<&'a [NodeId]> { + match document.node(id) { + Node::Array(elements) => Ok(elements), + _ => Err(invalid(format!("{description} must be an array"))), + } +} + +/// Returns the value of a field that should be a port number. +fn port_value(node: &Node) -> PortValue { + match node { + // Integers longer than an `i128` are out of range either way. + Node::Integer(literal) => PortValue::Integer(literal.parse().unwrap_or_else(|_| { + if literal.starts_with('-') { + i128::MIN + } else { + i128::MAX + } + })), + _ => PortValue::Other, + } +} + +/// Returns the protocol of the field at `id`, as the policy reader checks it. +fn protocol(document: &Document, id: NodeId, description: &str) -> Result { + match document.node(id) { + Node::String(text) => RuleProtocol::parse(text), + _ => None, + } + .ok_or_else(|| { + invalid(format!( + "{description}.protocol must be tcp, udp, icmp, or any" + )) + }) +} + +/// Compiles a policy document, as `compile_policy` does. +fn compile_document(document: &Document) -> Result { + let root = object(document, document.root(), "egress policy")?; + root.check_fields(ROOT_FIELDS, "egress policy")?; + Ok(CompiledEgressPolicy { + allow: compile_category(document, root.get("allow"), "allow")?, + deny: compile_category(document, root.get("deny"), "deny")?, + }) +} + +/// Compiles the rules of one action, which a policy without the field does not have, as +/// `_compile_category` does: every rule is checked before any is lowered. +fn compile_category( + document: &Document, + id: Option, + category: &str, +) -> Result> { + let elements = match id { + Some(id) => array(document, id, category)?, + None => &[], + }; + let mut rules = Vec::new(); + for (index, &element) in elements.iter().enumerate() { + rules.extend(parse_rule( + document, + element, + &format!("{category}[{index}]"), + )?); + } + lowering::lower(&rules, category) +} + +/// Parses a rule of either form, as `_parse_rule` does. +fn parse_rule(document: &Document, id: NodeId, description: &str) -> Result> { + let rule = object(document, id, description)?; + if rule.has_any(LEGACY_RULE_FIELDS) { + if rule.has_any(MXC_RULE_FIELDS) { + return Err(invalid(format!( + "{description} cannot mix MXC to/ports fields with legacy flat fields" + ))); + } + return parse_legacy_rule(&rule, description); + } + parse_mxc_rule(&rule, description) +} + +/// Parses a destination network and its exceptions, the `cidr` and optional `except` fields of +/// `description`, as the rest of `_parse_peer` does. +fn parse_peer_fields( + object: &Object<'_>, + cidr: NodeId, + except: Option, + description: &str, +) -> Result<(Family, Vec<(u128, u128)>)> { + let parent = parse_network(object.node(cidr), &format!("{description}.cidr"))?; + let exceptions = match except { + Some(except) => array(object.document, except, &format!("{description}.except"))?, + None => &[], + }; + lowering::destination( + parent, + exceptions.iter().enumerate().map(|(index, &exception)| { + parse_network( + object.node(exception), + &format!("{description}.except[{index}]"), + ) + }), + description, + ) +} + +/// Parses the CIDR value of a field, as `_network` does. +fn parse_network(node: &Node, description: &str) -> Result { + match node { + Node::String(text) => lowering::network(text, description), + _ => Err(lowering::not_a_cidr_string(description)), + } +} + +/// Parses a destination of an MXC rule, as `_parse_peer` does. +fn parse_peer( + document: &Document, + id: NodeId, + description: &str, +) -> Result<(Family, Vec<(u128, u128)>)> { + let peer = object(document, id, description)?; + peer.check_fields(PEER_FIELDS, description)?; + let Some(cidr) = peer.get("cidr") else { + return Err(invalid(format!("{description}.cidr is required"))); + }; + parse_peer_fields(&peer, cidr, peer.get("except"), description) +} + +/// Parses a legacy flat rule, as `_parse_legacy_rule` does. +fn parse_legacy_rule(rule: &Object<'_>, description: &str) -> Result> { + rule.check_fields(LEGACY_RULE_FIELDS, description)?; + let Some(cidr) = rule.get("cidr") else { + return Err(invalid(format!("{description}.cidr is required"))); + }; + let (family, addresses) = parse_peer_fields(rule, cidr, rule.get("except"), description)?; + let destinations = Some(vec![(family, addresses)]); + let port = rule.get("port"); + let end_port = rule.get("endPort"); + if end_port.is_some() && port.is_none() { + return Err(invalid(format!("{description}.endPort requires port"))); + } + let Some(protocol) = rule.get("protocol") else { + if port.is_some() { + return Err(invalid(format!("{description}.port requires protocol"))); + } + return Ok(lowering::combine(destinations, None)); + }; + let protocol = self::protocol(rule.document, protocol, description)?; + let selectors: Vec = match port { + None => vec![(protocol.native(), None)], + Some(port) => lowering::port_protocol_selectors( + protocol, + port_value(rule.node(port)), + end_port.map(|end_port| port_value(rule.node(end_port))), + description, + )?, + }; + Ok(lowering::combine(destinations, Some(selectors))) +} + +/// Parses a port selector of an MXC rule, as `_parse_mxc_port` does. +fn parse_mxc_port(document: &Document, id: NodeId, description: &str) -> Result> { + let port = object(document, id, description)?; + port.check_fields(PORT_FIELDS, description)?; + let protocol = match port.get("protocol") { + Some(protocol) => self::protocol(document, protocol, description)?, + None => RuleProtocol::Any, + }; + lowering::port_selectors( + protocol, + port.get("port").map(|value| port_value(port.node(value))), + port.get("endPort") + .map(|value| port_value(port.node(value))), + description, + ) +} + +/// Parses an MXC rule, as `_parse_mxc_rule` does. +fn parse_mxc_rule(rule: &Object<'_>, description: &str) -> Result> { + rule.check_fields(MXC_RULE_FIELDS, description)?; + let mut destinations = None; + if let Some(to) = rule.get("to") { + let description = format!("{description}.to"); + let peers = array(rule.document, to, &description)?; + if peers.is_empty() { + return Err(invalid(format!( + "{description} must contain at least one destination" + ))); + } + let mut parsed = Vec::with_capacity(peers.len()); + for (index, &peer) in peers.iter().enumerate() { + parsed.push(parse_peer( + rule.document, + peer, + &format!("{description}[{index}]"), + )?); + } + // A native rule names one family, so the peers of each family form their own + // destination set. + destinations = Some(lowering::group_destinations(&parsed)); + } + let mut selectors = None; + if let Some(ports) = rule.get("ports") { + let description = format!("{description}.ports"); + let ports = array(rule.document, ports, &description)?; + if ports.is_empty() { + return Err(invalid(format!( + "{description} must contain at least one selector" + ))); + } + let mut parsed = Vec::new(); + for (index, &port) in ports.iter().enumerate() { + parsed.extend(parse_mxc_port( + rule.document, + port, + &format!("{description}[{index}]"), + )?); + } + selectors = Some(parsed); + } + Ok(lowering::combine(destinations, selectors)) +} diff --git a/nvx-rs/src/host/network/policy/tests.rs b/nvx-rs/src/host/network/policy/tests.rs new file mode 100644 index 000000000..caf4805c8 --- /dev/null +++ b/nvx-rs/src/host/network/policy/tests.rs @@ -0,0 +1,1166 @@ +//! Tests of the egress policy compiler: the parity fixture that the Python compiler recorded, and +//! ports of the cases of `scripts/test_egress_policy.py`. + +use std::path::{Path, PathBuf}; + +use serde_json::{Value, json}; + +use super::*; +use crate::ErrorKind; +use crate::host::network::address::probe; +use crate::host::python::path::{posix_str, windows_str}; + +/// The fixture that `scripts/nvx_rs_fixtures.py` writes, or the file that +/// `NVX_EGRESS_POLICY_FIXTURE` names, such as a larger corpus that it writes with `--output` and +/// `--random-cases`. +fn fixture() -> Value { + let path = std::env::var_os("NVX_EGRESS_POLICY_FIXTURE") + .map(PathBuf::from) + .unwrap_or_else(|| { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/egress-policy.json") + }); + let text = std::fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display())); + serde_json::from_str(&text).unwrap() +} + +/// Returns the bytes that a fixture case describes: text, or hexadecimal, each repeated. +fn input(segments: &Value) -> Vec { + let mut data = Vec::new(); + for segment in segments.as_array().unwrap() { + let bytes = match (segment.get("text"), segment.get("hex")) { + (Some(text), None) => text.as_str().unwrap().as_bytes().to_vec(), + (None, Some(hex)) => { + let hex = hex.as_str().unwrap(); + (0..hex.len()) + .step_by(2) + .map(|index| u8::from_str_radix(&hex[index..index + 2], 16).unwrap()) + .collect() + } + _ => panic!("invalid segment {segment}"), + }; + let repeat = segment + .get("repeat") + .map_or(1, |count| count.as_u64().unwrap()); + for _ in 0..repeat { + data.extend_from_slice(&bytes); + } + } + data +} + +/// Describes a compilation as the fixture does. +fn outcome(result: Result) -> Value { + match result { + Ok(compiled) => json!({"allow": compiled.allow, "deny": compiled.deny}), + Err(error) => json!({"error": error.message()}), + } +} + +/// Returns the expected outcome of a fixture case for a file that messages call `path`. +fn expected(expected: &Value, path: &str) -> Value { + match expected.get("error") { + Some(message) => json!({"error": message.as_str().unwrap().replace("{path}", path)}), + None => expected.clone(), + } +} + +#[test] +fn every_fixture_case_compiles_as_python_compiled_it() { + let fixture = fixture(); + let cases = fixture["cases"].as_array().unwrap(); + assert!(cases.len() > 500, "{} cases", cases.len()); + let directory = tempfile::tempdir().unwrap(); + let file = directory.path().join("policy.json"); + let file_display = python_path(&file).display; + let mut failures = Vec::new(); + for case in cases { + let name = case["name"].as_str().unwrap(); + let data = input(&case["input"]); + let actual = outcome(compile_policy_bytes(&data, Path::new("policy.json"))); + let wanted = expected(&case["expected"], "policy.json"); + if actual != wanted { + failures.push(format!("{name}:\n expected {wanted}\n actual {actual}")); + continue; + } + std::fs::write(&file, &data).unwrap(); + let actual = outcome(compile_policy_file(&file)); + let wanted = expected(&case["expected"], &file_display); + if actual != wanted { + failures.push(format!( + "{name} (file):\n expected {wanted}\n actual {actual}" + )); + } + } + assert!( + failures.is_empty(), + "{} of {} cases differ:\n{}", + failures.len(), + cases.len(), + failures.join("\n") + ); +} + +#[test] +fn unreadable_files_fail_as_in_python() { + let fixture = fixture(); + let directory = tempfile::tempdir().unwrap(); + for case in fixture["files"].as_array().unwrap() { + let path = match case["name"].as_str().unwrap() { + "missing" => directory.path().join("missing.json"), + "directory" => directory.path().to_path_buf(), + name => panic!("unknown file case {name}"), + }; + let error = compile_policy_file(&path).unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Io); + assert_eq!( + outcome(Err(error)), + expected(&case["expected"], &python_path(&path).display), + "{case}" + ); + } +} + +#[test] +fn paths_print_as_pathlib_prints_them() { + let fixture = fixture(); + for case in fixture["paths"].as_array().unwrap() { + let path = case["path"].as_str().unwrap(); + let posix = String::from_utf8(posix_str(path.as_bytes())).unwrap(); + assert_eq!(posix, case["posix"].as_str().unwrap(), "{path:?}"); + let units: Vec = path.encode_utf16().collect(); + let windows = String::from_utf16(&windows_str(&units)).unwrap(); + assert_eq!(windows, case["windows"].as_str().unwrap(), "{path:?}"); + } +} + +// Ports of `scripts/test_egress_policy.py`, one test for each of its tests, under the same names. + +/// Compiles `value` as `EgressPolicyTests.compile` does, from its JSON text in a policy file, and +/// checks that compiling the value itself agrees. +fn compile(value: Value) -> Result { + let data = serde_json::to_vec(&value).unwrap(); + let compiled = compile_policy_bytes(&data, Path::new("policy.json")); + let parsed = compile_policy(&value); + match (&compiled, &parsed) { + (Ok(compiled), Ok(parsed)) => assert_eq!(compiled, parsed, "{value}"), + (Err(compiled), Err(parsed)) => { + assert_eq!(compiled.message(), parsed.message(), "{value}"); + } + _ => panic!("{value}: the file gives {compiled:?} and the value {parsed:?}"), + } + compiled +} + +/// Compiles the text of a policy file. +fn compile_text(text: &str) -> Result { + compile_policy_bytes(text.as_bytes(), Path::new("policy.json")) +} + +/// Returns the message of a compilation that must fail. +fn message(result: Result) -> String { + let error = result.unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unsupported, "{error}"); + error.message().to_owned() +} + +/// Returns the rules of `category`, `allow` or `deny`. +fn action<'a>(compiled: &'a CompiledEgressPolicy, category: &str) -> &'a [String] { + match category { + "allow" => &compiled.allow, + "deny" => &compiled.deny, + _ => panic!("unknown category {category}"), + } +} + +/// Returns `object` with the fields of `extra` added. +fn merged(object: &Value, extra: &Value) -> Value { + let mut object = object.as_object().unwrap().clone(); + object.extend(extra.as_object().unwrap().clone()); + Value::Object(object) +} + +#[test] +fn test_compiles_inclusive_tcp_and_udp_ranges() { + let compiled = compile(json!({ + "allow": [ + { + "to": [{"cidr": "192.0.2.7"}], + "ports": [{"protocol": "tcp", "port": 8000, "endPort": 8002}], + }, + { + "to": [{"cidr": "198.51.100.0/24"}], + "ports": [{"protocol": "udp", "port": 5000, "endPort": 5001}], + }, + ] + })) + .unwrap(); + assert_eq!( + compiled.allow, + [ + "192.0.2.7/32:tcp:8000-8002", + "198.51.100.0/24:udp:5000-5001" + ] + ); + assert!(compiled.deny.is_empty()); +} + +#[test] +fn test_lowers_each_range_to_one_native_range_rule() { + let ranges = [ + ( + json!({"protocol": "tcp", "port": 8000, "endPort": 8010}), + vec!["192.0.2.7/32:tcp:8000-8010"], + ), + ( + json!({"protocol": "udp", "port": 5000, "endPort": 5010}), + vec!["192.0.2.7/32:udp:5000-5010"], + ), + ( + json!({"protocol": "tcp", "port": 1, "endPort": 65535}), + vec!["192.0.2.7/32:tcp:1-65535"], + ), + ( + json!({"protocol": "any", "port": 1, "endPort": 65535}), + vec!["192.0.2.7/32:tcp:1-65535", "192.0.2.7/32:udp:1-65535"], + ), + // A range of one port lowers to that port's rule. + ( + json!({"protocol": "tcp", "port": 8000, "endPort": 8000}), + vec!["192.0.2.7/32:tcp:8000"], + ), + ]; + for category in ["allow", "deny"] { + for (selector, expected) in &ranges { + for rule in [ + json!({"to": [{"cidr": "192.0.2.7"}], "ports": [selector]}), + merged(&json!({"cidr": "192.0.2.7"}), selector), + ] { + let compiled = compile(json!({category: [rule]})).unwrap(); + assert_eq!(action(&compiled, category), expected, "{category} {rule}"); + } + } + } +} + +#[test] +fn test_keeps_a_denied_port_inside_an_allowed_range() { + let compiled = compile(json!({ + "allow": [{ + "to": [{"cidr": "192.0.2.0/24"}], + "ports": [{"protocol": "tcp", "port": 8000, "endPort": 8010}], + }], + "deny": [{ + "to": [{"cidr": "192.0.2.0/24"}], + "ports": [{"protocol": "tcp", "port": 8005}], + }], + })) + .unwrap(); + // OpenVMM applies deny precedence, so each list keeps its own rule. + assert_eq!(compiled.allow, ["192.0.2.0/24:tcp:8000-8010"]); + assert_eq!(compiled.deny, ["192.0.2.0/24:tcp:8005"]); +} + +#[test] +fn test_merges_adjacent_and_overlapping_ranges_of_one_network() { + let ranges = |bounds: &[(u16, u16)]| -> Vec { + bounds + .iter() + .map(|&(start, end)| { + json!({"cidr": "192.0.2.7", "protocol": "tcp", "port": start, "endPort": end}) + }) + .collect() + }; + let singles: Vec<(u16, u16)> = (8000..=8010).map(|port| (port, port)).collect(); + for (rules, expected) in [ + ( + ranges(&[(8000, 8004), (8005, 8010)]), + vec!["192.0.2.7/32:tcp:8000-8010"], + ), + ( + ranges(&[(8000, 8008), (8003, 8010)]), + vec!["192.0.2.7/32:tcp:8000-8010"], + ), + ( + [ranges(&[(8000, 8010)]), ranges(&singles)].concat(), + vec!["192.0.2.7/32:tcp:8000-8010"], + ), + ( + ranges(&[(8000, 8004), (8006, 8010)]), + vec!["192.0.2.7/32:tcp:8000-8004", "192.0.2.7/32:tcp:8006-8010"], + ), + ] { + let compiled = compile(json!({"allow": rules})).unwrap(); + assert_eq!(compiled.allow, expected); + } + // Networks keep their own ranges where their segments differ. + let compiled = compile(json!({ + "allow": [ + {"cidr": "192.0.2.7", "protocol": "udp", "port": 1, "endPort": 10}, + {"cidr": "198.51.100.9", "protocol": "udp", "port": 5, "endPort": 20}, + ] + })) + .unwrap(); + assert_eq!( + compiled.allow, + ["192.0.2.7/32:udp:1-10", "198.51.100.9/32:udp:5-20"] + ); +} + +#[test] +fn test_keeps_network_range_whole_when_adjacent_rule_changes_union() { + let compiled = compile(json!({ + "allow": [ + {"cidr": "10.0.0.0/25", "protocol": "tcp", "port": 1, "endPort": 10}, + {"cidr": "10.0.0.128/25", "protocol": "tcp", "port": 5, "endPort": 6}, + ] + })) + .unwrap(); + assert_eq!( + compiled.allow, + ["10.0.0.0/24:tcp:5-6", "10.0.0.0/25:tcp:1-10"] + ); +} + +#[test] +fn test_rejects_invalid_port_shapes_and_values() { + for rule in [ + json!({"cidr": "192.0.2.0/24", "protocol": "tcp", "endPort": 80}), + json!({"cidr": "192.0.2.0/24", "protocol": "any", "endPort": 80}), + json!({"cidr": "192.0.2.0/24", "protocol": "tcp", "port": 81, "endPort": 80}), + json!({"cidr": "192.0.2.0/24", "protocol": "tcp", "port": 0}), + json!({"cidr": "192.0.2.0/24", "protocol": "udp", "port": 65536}), + json!({"cidr": "192.0.2.0/24", "protocol": "tcp", "port": true}), + json!({"cidr": "192.0.2.0/24", "protocol": "any", "port": 0}), + json!({"cidr": "192.0.2.0/24", "port": 80}), + json!({"cidr": "192.0.2.0/24", "protocol": "icmp", "port": 8}), + json!({"cidr": "192.0.2.0/24", "protocol": "icmp", "endPort": 8}), + json!({"cidr": "192.0.2.0/24", "protocol": "ICMP"}), + json!({"cidr": "192.0.2.0/24", "protocol": "sctp"}), + ] { + message(compile(json!({"allow": [rule]}))); + } +} + +#[test] +fn test_reports_invalid_port_ranges_clearly() { + let cidr = "192.0.2.0/24"; + for (rule, expected) in [ + ( + json!({ + "to": [{"cidr": cidr}], + "ports": [{"protocol": "tcp", "port": 8010, "endPort": 8000}], + }), + "deny[0].ports[0].endPort 8000 cannot be below port 8010", + ), + ( + json!({"ports": [{"protocol": "udp", "endPort": 8010}]}), + "deny[0].ports[0].endPort requires port", + ), + ( + json!({"ports": [{"endPort": 8010}]}), + "deny[0].ports[0].endPort requires port", + ), + ( + json!({"ports": [{"protocol": "tcp", "port": 8000, "endPort": 65536}]}), + "deny[0].ports[0].endPort must be between 1 and 65535", + ), + ( + json!({"cidr": cidr, "protocol": "tcp", "port": 8010, "endPort": 8000}), + "deny[0].endPort 8000 cannot be below port 8010", + ), + ( + json!({"cidr": cidr, "protocol": "udp", "endPort": 8010}), + "deny[0].endPort requires port", + ), + ( + json!({"cidr": cidr, "endPort": 8010}), + "deny[0].endPort requires port", + ), + ( + json!({"cidr": cidr, "protocol": "tcp", "port": 8000, "endPort": 65536}), + "deny[0].endPort must be between 1 and 65535", + ), + ( + json!({"cidr": cidr, "protocol": "tcp", "port": 0, "endPort": 8010}), + "deny[0].port must be between 1 and 65535", + ), + ] { + assert_eq!(message(compile(json!({"deny": [rule]}))), expected); + } +} + +#[test] +fn test_compiles_protocol_selectors_with_and_without_ports() { + let selectors = [ + (json!({"protocol": "tcp"}), vec!["192.0.2.7/32:tcp"]), + (json!({"protocol": "udp"}), vec!["192.0.2.7/32:udp"]), + (json!({"protocol": "icmp"}), vec!["192.0.2.7/32:icmp"]), + (json!({"protocol": "any"}), vec!["192.0.2.7/32"]), + (json!({}), vec!["192.0.2.7/32"]), + ( + json!({"protocol": "any", "port": 443}), + vec!["192.0.2.7/32:tcp:443", "192.0.2.7/32:udp:443"], + ), + ( + json!({"protocol": "any", "port": 443, "endPort": 444}), + vec!["192.0.2.7/32:tcp:443-444", "192.0.2.7/32:udp:443-444"], + ), + ]; + for category in ["allow", "deny"] { + for (selector, expected) in &selectors { + let rule = merged(&json!({"cidr": "192.0.2.7"}), selector); + let compiled = compile(json!({category: [rule]})).unwrap(); + assert_eq!( + action(&compiled, category), + expected, + "{category} {selector}" + ); + } + } +} + +#[test] +fn test_protocol_wide_rules_cover_their_port_rules_only() { + let compiled = compile(json!({ + "allow": [ + {"cidr": "192.0.2.7", "protocol": "tcp", "port": 443}, + {"cidr": "192.0.2.0/24", "protocol": "tcp"}, + {"cidr": "192.0.2.7", "protocol": "udp", "port": 443}, + {"cidr": "192.0.2.0/24", "protocol": "icmp"}, + ], + "deny": [ + {"cidr": "198.51.100.0/24", "protocol": "any", "port": 53}, + {"cidr": "198.51.100.0/24", "protocol": "udp"}, + ], + })) + .unwrap(); + assert_eq!( + compiled.allow, + [ + "192.0.2.0/24:icmp", + "192.0.2.0/24:tcp", + "192.0.2.7/32:udp:443" + ] + ); + assert_eq!( + compiled.deny, + ["198.51.100.0/24:tcp:53", "198.51.100.0/24:udp"] + ); +} + +#[test] +fn test_address_only_rules_cover_protocol_wide_rules() { + let compiled = compile(json!({ + "allow": [ + {"cidr": "192.0.2.0/24", "protocol": "udp"}, + {"cidr": "192.0.2.0/25"}, + {"cidr": "192.0.2.0/25", "protocol": "icmp"}, + {"cidr": "192.0.2.128/25", "protocol": "udp", "port": 53}, + ] + })) + .unwrap(); + // Widening the UDP rule into the address-only half keeps it to one prefix. + assert_eq!(compiled.allow, ["192.0.2.0/24:udp", "192.0.2.0/25"]); +} + +/// One address-only rule for each of [`MAX_RULES_PER_ACTION`] hosts in distinct networks. +fn address_only() -> Vec { + (0..MAX_RULES_PER_ACTION) + .map(|index| json!({"cidr": format!("10.0.{index}.1/32")})) + .collect() +} + +#[test] +fn test_protocol_wide_rules_share_the_native_rule_budget() { + let address_only = address_only(); + let mut rules = address_only[..MAX_RULES_PER_ACTION - 1].to_vec(); + rules.push(json!({"cidr": "192.0.2.0/24", "protocol": "tcp"})); + let accepted = compile(json!({"allow": rules})).unwrap(); + assert_eq!(accepted.allow.len(), MAX_RULES_PER_ACTION); + assert!(accepted.allow.contains(&"192.0.2.0/24:tcp".to_owned())); + + let mut rules = address_only.clone(); + rules.push(json!({"cidr": "192.0.2.0/24", "protocol": "icmp"})); + assert!(message(compile(json!({"allow": rules}))).contains("at most 256")); + + // Protocol `any` with a port range lowers to a TCP and a UDP range. + let any_range = json!({"cidr": "192.0.2.1", "protocol": "any", "port": 1, "endPort": 129}); + let mut rules = address_only[..MAX_RULES_PER_ACTION - 2].to_vec(); + rules.push(any_range.clone()); + let accepted = compile(json!({"deny": rules})).unwrap(); + assert_eq!(accepted.deny.len(), MAX_RULES_PER_ACTION); + assert!(accepted.deny.contains(&"192.0.2.1/32:udp:1-129".to_owned())); + let mut rules = address_only[..MAX_RULES_PER_ACTION - 1].to_vec(); + rules.push(any_range); + assert!(message(compile(json!({"deny": rules}))).contains("at most 256")); +} + +#[test] +fn test_normalizes_host_bits_like_the_native_cidr_parser() { + let compiled = + compile(json!({"allow": [{"cidr": "10.0.0.5/24"}, {"cidr": "fe80::1/64"}]})).unwrap(); + assert_eq!(compiled.allow, ["10.0.0.0/24", "fe80::/64"]); +} + +#[test] +fn test_subtracts_rule_local_cidr_exclusions() { + let compiled = compile(json!({ + "allow": [ + {"to": [{"cidr": "192.0.2.0/24", "except": ["192.0.2.128/25"]}]}, + {"to": [{"cidr": "192.0.2.200/32"}]}, + ], + "deny": [ + {"to": [{"cidr": "198.51.100.0/24", "except": ["198.51.100.128/25"]}]}, + ], + })) + .unwrap(); + assert_eq!(compiled.allow, ["192.0.2.0/25", "192.0.2.200/32"]); + assert_eq!(compiled.deny, ["198.51.100.0/25"]); +} + +#[test] +fn test_compiles_mxc_destination_and_port_unions() { + let compiled = compile(json!({ + "allow": [{"ports": [{"port": 53}, {"protocol": "icmp"}]}], + "deny": [{ + "to": [{"cidr": "198.51.100.0/25"}, {"cidr": "198.51.100.128/25"}], + "ports": [{"protocol": "tcp", "port": 80}, {"protocol": "udp", "port": 53}], + }], + })) + .unwrap(); + // A rule without destinations matches every destination of both families. + assert_eq!( + compiled.allow, + [ + "0.0.0.0/0:icmp", + "0.0.0.0/0:tcp:53", + "0.0.0.0/0:udp:53", + "::/0:icmp", + "::/0:tcp:53", + "::/0:udp:53", + ] + ); + assert_eq!( + compiled.deny, + ["198.51.100.0/24:tcp:80", "198.51.100.0/24:udp:53"] + ); +} + +#[test] +fn test_deduplicates_overlapping_exclusions_and_collapses_safe_prefixes() { + let compiled = compile(json!({ + "allow": [ + { + "cidr": "192.0.2.0/24", + "except": ["192.0.2.128/25", "192.0.2.128/25", "192.0.2.192/26"], + "protocol": "tcp", + "port": 443, + }, + {"cidr": "198.51.100.0/25", "protocol": "udp", "port": 53}, + {"cidr": "198.51.100.128/25", "protocol": "udp", "port": 53}, + ] + })) + .unwrap(); + assert_eq!( + compiled.allow, + ["192.0.2.0/25:tcp:443", "198.51.100.0/24:udp:53"] + ); +} + +#[test] +fn test_rejects_exclusions_outside_parent_or_wrong_family() { + for (cidr, excluded) in [ + ("192.0.2.0/24", "198.51.100.0/24"), + ("192.0.2.0/24", "2001:db8::/32"), + ("2001:db8::/32", "2001:db9::/48"), + ("2001:db8::/32", "192.0.2.0/24"), + ("::/96", "0.0.0.0/8"), + ] { + let policy = json!({"allow": [{"to": [{"cidr": cidr, "except": [excluded]}]}]}); + assert!( + message(compile(policy)).contains("except"), + "{cidr} {excluded}" + ); + } +} + +#[test] +fn test_compiles_ipv6_networks_with_every_selector() { + let selectors = [ + ( + json!({"ports": [{"protocol": "tcp"}]}), + vec!["2001:db8::7/128:tcp"], + ), + ( + json!({"ports": [{"protocol": "udp"}]}), + vec!["2001:db8::7/128:udp"], + ), + ( + json!({"ports": [{"protocol": "icmp"}]}), + vec!["2001:db8::7/128:icmp"], + ), + (json!({}), vec!["2001:db8::7/128"]), + ( + json!({"ports": [{"protocol": "any", "port": 443}]}), + vec!["2001:db8::7/128:tcp:443", "2001:db8::7/128:udp:443"], + ), + ( + json!({"ports": [{"protocol": "tcp", "port": 8000, "endPort": 8010}]}), + vec!["2001:db8::7/128:tcp:8000-8010"], + ), + ]; + for category in ["allow", "deny"] { + for (selector, expected) in &selectors { + let rule = merged(&json!({"to": [{"cidr": "2001:db8::7"}]}), selector); + let compiled = compile(json!({category: [rule]})).unwrap(); + assert_eq!( + action(&compiled, category), + expected, + "{category} {selector}" + ); + } + } +} + +#[test] +fn test_compiles_one_allowed_ipv6_network_with_a_denied_address() { + let compiled = compile(json!({ + "allow": [ + {"to": [{"cidr": "2001:db8:1::/64"}], "ports": [{"protocol": "tcp", "port": 443}]}, + {"to": [{"cidr": "::/0"}], "ports": [{"protocol": "udp", "port": 53}]}, + ], + "deny": [{"to": [{"cidr": "2001:db8:1::123"}]}], + })) + .unwrap(); + assert_eq!(compiled.allow, ["::/0:udp:53", "2001:db8:1::/64:tcp:443"]); + assert_eq!(compiled.deny, ["2001:db8:1::123/128"]); +} + +#[test] +fn test_flat_rules_accept_ipv6_networks() { + let compiled = compile(json!({ + "allow": [{ + "cidr": "2001:db8::/32", + "except": ["2001:db8::/33"], + "protocol": "tcp", + "port": 443, + }] + })) + .unwrap(); + assert_eq!(compiled.allow, ["2001:db8:8000::/33:tcp:443"]); +} + +#[test] +fn test_keeps_each_address_family_separate() { + // IPv4 0.0.0.0/0 and IPv6 ::/96 span the same integers, so lowering both families together + // would merge or cover one with the other. + let compiled = compile(json!({ + "allow": [ + { + "to": [{"cidr": "::/96"}, {"cidr": "0.0.0.0/0"}], + "ports": [{"protocol": "tcp", "port": 80}], + }, + {"to": [{"cidr": "192.0.2.0/24"}]}, + {"to": [{"cidr": "::c000:200/120"}], "ports": [{"protocol": "udp", "port": 53}]}, + ], + "deny": [ + {"to": [{"cidr": "0.0.0.0/0"}]}, + {"to": [{"cidr": "::/96"}], "ports": [{"protocol": "icmp"}]}, + ], + })) + .unwrap(); + assert_eq!( + compiled.allow, + [ + "0.0.0.0/0:tcp:80", + "192.0.2.0/24", + "::/96:tcp:80", + "::c000:200/120:udp:53", + ] + ); + assert_eq!(compiled.deny, ["0.0.0.0/0", "::/96:icmp"]); +} + +#[test] +fn test_lowers_the_destinations_of_each_family_separately() { + let compiled = compile(json!({ + "allow": [{ + "to": [ + {"cidr": "192.0.2.0/25"}, + {"cidr": "2001:db8::/32", "except": ["2001:db8::/33"]}, + {"cidr": "192.0.2.128/25"}, + ], + "ports": [{"protocol": "tcp", "port": 443}], + }] + })) + .unwrap(); + assert_eq!( + compiled.allow, + ["192.0.2.0/24:tcp:443", "2001:db8:8000::/33:tcp:443"] + ); +} + +#[test] +fn test_subtracts_ipv6_exclusions() { + let compiled = compile(json!({ + "deny": [{ + "to": [{"cidr": "2001:db8::/32", "except": ["2001:db8:1::/48", "2001:db8:1::/64"]}], + "ports": [{"protocol": "tcp", "port": 22}], + }] + })) + .unwrap(); + // `IPv6Network("2001:db8::/32").address_exclude(IPv6Network("2001:db8:1::/48"))`, sorted. + assert_eq!( + compiled.deny, + [ + "2001:db8::/48:tcp:22", + "2001:db8:2::/47:tcp:22", + "2001:db8:4::/46:tcp:22", + "2001:db8:8::/45:tcp:22", + "2001:db8:10::/44:tcp:22", + "2001:db8:20::/43:tcp:22", + "2001:db8:40::/42:tcp:22", + "2001:db8:80::/41:tcp:22", + "2001:db8:100::/40:tcp:22", + "2001:db8:200::/39:tcp:22", + "2001:db8:400::/38:tcp:22", + "2001:db8:800::/37:tcp:22", + "2001:db8:1000::/36:tcp:22", + "2001:db8:2000::/35:tcp:22", + "2001:db8:4000::/34:tcp:22", + "2001:db8:8000::/33:tcp:22", + ] + ); + assert_eq!(compiled.deny.len(), 16); +} + +#[test] +fn test_rejects_ipv6_scopes() { + // Clearing host bits also clears the scope of the network address, so the scoped addresses + // with host bits must not compile as unscoped. + let cidrs = [ + "fe80::%eth0/64", + "fe80::1%1", + "fe80::1%eth0/64", + "fe80::1%1/64", + "::ffff:192.0.2.1%eth0/120", + ]; + for category in ["allow", "deny"] { + for cidr in cidrs { + for (field, rule) in [ + ("to[0].cidr", json!({"to": [{"cidr": cidr}]})), + ( + "to[0].except[0]", + json!({"to": [{"cidr": "::/0", "except": [cidr]}]}), + ), + ("cidr", json!({"cidr": cidr})), + ("except[0]", json!({"cidr": "::/0", "except": [cidr]})), + ] { + assert_eq!( + message(compile(json!({category: [rule]}))), + format!("{category}[0].{field} must not name an IPv6 scope: {cidr}") + ); + } + } + } +} + +#[test] +fn test_both_families_share_the_native_rule_budget() { + // Ports that are not adjacent cannot form one range, so each lowers to its own native rule. + let rules = |ipv4_ports: u16, ipv6_ports: u16| { + let rule = |cidr: &str, ports: u16| { + let selectors: Vec = (0..ports) + .map(|index| json!({"protocol": "tcp", "port": 2 * index + 1})) + .collect(); + json!({"to": [{"cidr": cidr}], "ports": selectors}) + }; + json!({"allow": [rule("192.0.2.1", ipv4_ports), rule("2001:db8::1", ipv6_ports)]}) + }; + assert_eq!(compile(rules(128, 128)).unwrap().allow.len(), 256); + for (ipv4_ports, ipv6_ports) in [(129, 128), (128, 129)] { + assert!(message(compile(rules(ipv4_ports, ipv6_ports))).contains("at most 256")); + } +} + +#[test] +fn test_rejects_invalid_mxc_rule_shapes() { + for rule in [ + json!({"to": []}), + json!({"to": [{}]}), + json!({"to": [{"cidr": "192.0.2.0/24", "unknown": true}]}), + json!({"ports": []}), + json!({"ports": [{"protocol": "icmp", "port": 8}]}), + json!({"ports": [{"protocol": "ICMP"}]}), + json!({"ports": [{"protocol": "sctp"}]}), + json!({"ports": [{"endPort": 80}]}), + json!({"to": [{"cidr": "192.0.2.0/24"}], "cidr": "192.0.2.0/24"}), + ] { + message(compile(json!({"allow": [rule]}))); + } +} + +#[test] +fn test_rejects_unknown_and_malformed_fields() { + for value in [ + json!([]), + json!({"unknown": []}), + json!({"allow": {}}), + json!({"allow": ["192.0.2.0/24"]}), + json!({"allow": [{"cidr": "192.0.2.0/24", "unknown": 1}]}), + json!({"allow": [{"cidr": 7}]}), + json!({"allow": [{"cidr": "192.0.2.0/24", "except": "192.0.2.1"}]}), + json!({"allow": [{"cidr": "192.0.2.0/24", "protocol": null}]}), + ] { + message(compile(value)); + } +} + +#[test] +fn test_rejects_duplicate_json_fields() { + for text in [ + r#"{"deny":[{"cidr":"0.0.0.0/0"}],"deny":[]}"#, + r#"{"allow":[{"cidr":"192.0.2.0/24","port":80,"port":443,"protocol":"tcp"}]}"#, + ] { + assert!( + message(compile_text(text)).contains("duplicate JSON property"), + "{text}" + ); + } +} + +#[test] +fn test_empty_destination_does_not_expand_port_range() { + let compiled = compile(json!({ + "allow": [{ + "cidr": "192.0.2.0/24", + "except": ["192.0.2.0/24"], + "protocol": "tcp", + "port": 1, + "endPort": 65535, + }] + })) + .unwrap(); + assert!(compiled.allow.is_empty()); + assert!(compiled.deny.is_empty()); +} + +#[test] +fn test_limits_the_actual_policy_read() { + // The compiler reads one byte past the limit instead of trusting the file's metadata, which + // the Python test fakes as empty. + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("policy.json"); + std::fs::write(&path, vec![b' '; MAX_POLICY_FILE_SIZE + 1]).unwrap(); + assert!(message(compile_policy_file(&path)).contains("byte limit")); +} + +#[test] +fn test_reports_oversized_json_integer_rejection() { + let text = format!(r#"{{"allow":[{{"cidr":{}}}]}}"#, "9".repeat(10000)); + assert_eq!( + message(compile_text(&text)), + "JSON integer exceeds 64-digit limit" + ); +} + +#[test] +fn test_many_source_networks_can_collapse_within_native_budget() { + let start = u32::from(std::net::Ipv4Addr::new(192, 0, 2, 0)); + let rules: Vec = (0..512) + .map(|offset| { + let address = std::net::Ipv4Addr::from(start + offset); + json!({"cidr": address.to_string(), "protocol": "tcp", "port": 80}) + }) + .collect(); + let compiled = compile(json!({"allow": rules})).unwrap(); + assert_eq!(compiled.allow, ["192.0.2.0/23:tcp:80"]); +} + +#[test] +fn test_accepts_exact_256_rule_boundaries() { + // Ranges with a port between them cannot merge. + let separate_ranges = |cidr: &str, protocol: &str| -> Vec { + (0..MAX_RULES_PER_ACTION) + .map(|index| { + json!({ + "cidr": cidr, + "protocol": protocol, + "port": 3 * index + 1, + "endPort": 3 * index + 2, + }) + }) + .collect() + }; + let compiled = compile(json!({ + "allow": separate_ranges("192.0.2.1", "tcp"), + "deny": separate_ranges("198.51.100.1", "udp"), + })) + .unwrap(); + assert_eq!(compiled.allow.len(), 256); + assert_eq!(compiled.deny.len(), 256); + assert_eq!( + compiled.allow[..2], + ["192.0.2.1/32:tcp:1-2", "192.0.2.1/32:tcp:4-5"] + ); + assert_eq!(compiled.deny.last().unwrap(), "198.51.100.1/32:udp:766-767"); +} + +#[test] +fn test_rejects_257_rules_before_materializing_them() { + for category in ["allow", "deny"] { + let rules: Vec = (0..=MAX_RULES_PER_ACTION) + .map(|index| json!({"cidr": "192.0.2.1", "protocol": "tcp", "port": 2 * index + 1})) + .collect(); + assert!(message(compile(json!({category: rules}))).contains("at most 256")); + } +} + +#[test] +fn test_overlapping_segments_do_not_inflate_native_rule_budget() { + let internal_ports = (1000..1056).step_by(2); + let mut rules = vec![json!({ + "cidr": "0.0.0.0/0", + "except": ["10.0.0.0/8"], + "protocol": "tcp", + "port": 1, + "endPort": 65535, + })]; + rules.extend( + internal_ports + .clone() + .map(|port| json!({"cidr": "10.0.0.0/8", "protocol": "tcp", "port": port})), + ); + let compiled = compile(json!({"allow": rules})).unwrap(); + let mut expected: Vec = [ + "0.0.0.0/5", + "8.0.0.0/7", + "11.0.0.0/8", + "12.0.0.0/6", + "16.0.0.0/4", + "32.0.0.0/3", + "64.0.0.0/2", + "128.0.0.0/1", + ] + .iter() + .map(|cidr| format!("{cidr}:tcp:1-65535")) + .chain(internal_ports.map(|port| format!("0.0.0.0/0:tcp:{port}"))) + .collect(); + let mut actual = compiled.allow.clone(); + expected.sort(); + actual.sort(); + assert_eq!(actual, expected); + assert_eq!(compiled.allow.len(), 36); +} + +#[test] +fn test_bounds_exclusions_times_port_segments() { + // The exclusion leaves eight networks, and the gap between ports keeps each port a segment + // of its own. + let rules = |count: usize, port: &dyn Fn(usize) -> usize| -> Vec { + (0..count) + .map(|index| { + json!({ + "cidr": "192.0.2.0/24", + "except": ["192.0.2.1/32"], + "protocol": "tcp", + "port": port(index), + }) + }) + .collect() + }; + let separate = |index: usize| 2 * index + 1; + let accepted = compile(json!({"allow": rules(32, &separate)})).unwrap(); + assert_eq!(accepted.allow.len(), 256); + assert!(message(compile(json!({"allow": rules(33, &separate)}))).contains("at most 256")); + + // Adjacent ports merge into one range for each network. + let merged = compile(json!({"allow": rules(33, &|index| index + 1)})).unwrap(); + assert_eq!(merged.allow.len(), 8); + assert!(merged.allow.iter().all(|rule| rule.ends_with(":tcp:1-33"))); +} + +#[test] +fn test_delays_fragment_conversion_until_after_covering_union() { + let first = u32::from(std::net::Ipv4Addr::new(192, 0, 0, 1)); + let exclusions: Vec = (0..4096) + .map(|index| std::net::Ipv4Addr::from(first + 2 * index).to_string()) + .collect(); + let fragmented = json!({ + "cidr": "192.0.0.0/16", + "except": exclusions, + "protocol": "tcp", + "port": 80, + "endPort": 81, + }); + let covering = json!({"cidr": "192.0.0.0/16", "protocol": "tcp", "port": 80, "endPort": 81}); + // The Python test counts calls of `ipaddress.summarize_address_range` over both orders: one + // for each compilation, of the union, and none for the fragments. + let ((), summaries, _) = probe::count(|| { + for rules in [ + [fragmented.clone(), covering.clone()], + [covering.clone(), fragmented.clone()], + ] { + let policy = serde_json::to_vec(&json!({"allow": rules})).unwrap(); + let compiled = compile_policy_bytes(&policy, Path::new("policy.json")).unwrap(); + assert_eq!(compiled.allow, ["192.0.0.0/16:tcp:80-81"]); + } + }); + assert_eq!(summaries, 2); +} + +#[test] +fn test_validates_redundant_rules_before_canonicalization() { + let policy = json!({ + "allow": [ + {"cidr": "192.0.2.0/24"}, + {"cidr": "192.0.2.1", "unknown": true}, + ] + }); + assert!(message(compile(policy)).contains("unknown field")); +} + +#[test] +fn test_address_only_rule_covers_large_protocol_range() { + let ranged = json!({"cidr": "192.0.2.0/24", "protocol": "tcp", "port": 1, "endPort": 65535}); + let address_only = json!({"cidr": "192.0.2.0/24"}); + for rules in [ + [ranged.clone(), address_only.clone()], + [address_only.clone(), ranged.clone()], + ] { + let compiled = compile(json!({"allow": rules})).unwrap(); + assert_eq!(compiled.allow, ["192.0.2.0/24"]); + } +} + +#[test] +fn test_address_only_rule_prunes_redundant_port_events_before_sweep() { + let covering = json!({"cidr": "0.0.0.0/0"}); + let first = u32::from(std::net::Ipv4Addr::new(192, 0, 0, 0)); + let protocol_rules: Vec = (1..=1000u32) + .map(|start| { + json!({ + "cidr": format!("{}/32", std::net::Ipv4Addr::from(first + start)), + "protocol": "tcp", + "port": start, + "endPort": 2001 - start, + }) + }) + .collect(); + for category in ["allow", "deny"] { + for covering_first in [true, false] { + let mut rules = protocol_rules.clone(); + if covering_first { + rules.insert(0, covering.clone()); + } else { + rules.push(covering.clone()); + } + let policy = serde_json::to_vec(&json!({category: rules})).unwrap(); + // The Python test counts the intervals that `_merge_intervals` receives: only the + // address-only rule's, because its coverage drops every port rule before the sweep. + let (compiled, _, merged_intervals) = + probe::count(|| compile_policy_bytes(&policy, Path::new("policy.json"))); + assert_eq!(action(&compiled.unwrap(), category), ["0.0.0.0/0"]); + assert_eq!(merged_intervals, 1, "{category} {covering_first}"); + } + } +} + +#[test] +fn test_partial_address_only_coverage_keeps_protocol_union_compact() { + let address_only_rules: Vec = (0..128) + .map(|index| json!({"cidr": format!("192.0.0.{}/32", 2 * index + 1)})) + .collect(); + let protocol_rule = json!({"cidr": "192.0.0.0/16", "protocol": "tcp", "port": 443}); + for category in ["allow", "deny"] { + let mut compiled_orders = Vec::new(); + for protocol_first in [false, true] { + let mut rules = address_only_rules.clone(); + if protocol_first { + rules.insert(0, protocol_rule.clone()); + } else { + rules.push(protocol_rule.clone()); + } + let compiled = compile(json!({category: rules})).unwrap(); + let compiled_rules = action(&compiled, category).to_vec(); + assert_eq!(compiled_rules.len(), 129); + assert!(compiled_rules.contains(&"192.0.0.0/16:tcp:443".to_owned())); + let others: std::collections::BTreeSet<&str> = compiled_rules + .iter() + .filter(|rule| !rule.ends_with(":tcp:443")) + .map(String::as_str) + .collect(); + let hosts: std::collections::BTreeSet<&str> = address_only_rules + .iter() + .map(|rule| rule["cidr"].as_str().unwrap()) + .collect(); + assert_eq!(others, hosts); + compiled_orders.push(compiled_rules); + } + assert_eq!(compiled_orders[0], compiled_orders[1]); + } +} + +#[test] +fn test_partial_address_only_canonicalization_does_not_bridge_gaps() { + let compiled = compile(json!({ + "allow": [ + {"cidr": "192.0.2.1/32"}, + {"cidr": "192.0.2.0/32", "protocol": "tcp", "port": 443}, + {"cidr": "192.0.2.3/32", "protocol": "tcp", "port": 443}, + ] + })) + .unwrap(); + assert_eq!( + compiled.allow, + [ + "192.0.2.0/31:tcp:443", + "192.0.2.1/32", + "192.0.2.3/32:tcp:443", + ] + ); +} + +// Tests of the port that the Python tests do not have. + +#[test] +fn rules_share_their_destinations_among_their_selectors() { + // 8192 destinations, none adjacent to another, for 8192 selectors: copying the destinations + // for each selector would take 2 GiB. Python 3.14 compiles each policy as asserted here. + let first = u32::from(std::net::Ipv4Addr::new(10, 0, 0, 0)); + let destinations: Vec = (0..8192u32) + .map(|index| json!({"cidr": std::net::Ipv4Addr::from(first + 2 * index).to_string()})) + .collect(); + let tcp: Vec = (0..8192u32) + .map(|index| json!({"protocol": "tcp", "port": 2 * index + 1})) + .collect(); + let mixed: Vec = [ + vec![json!({"protocol": "any"}); 2048], + vec![json!({"protocol": "icmp"}); 2048], + tcp[..4096].to_vec(), + ] + .concat(); + let compiled = compile(json!({ + "allow": [ + {"to": destinations, "ports": mixed}, + {"to": [{"cidr": "0.0.0.0/0"}]}, + ] + })) + .unwrap(); + assert_eq!(compiled.allow, ["0.0.0.0/0"]); + assert!(compiled.deny.is_empty()); + assert_eq!( + message(compile( + json!({"deny": [{"to": destinations, "ports": tcp}]}) + )), + "deny emits at most 256 native rules" + ); + let any = vec![json!({"protocol": "any"}); 8192]; + assert_eq!( + message(compile( + json!({"allow": [{"to": destinations, "ports": any}]}) + )), + "allow emits at most 256 native rules" + ); +} diff --git a/nvx-rs/src/host/platform/linux.rs b/nvx-rs/src/host/platform/linux.rs index 83bd68ca0..f2eaaff52 100644 --- a/nvx-rs/src/host/platform/linux.rs +++ b/nvx-rs/src/host/platform/linux.rs @@ -11,6 +11,9 @@ use super::Transport; use crate::host::sys::{self, Errno}; use crate::hypervisor::Hypervisor; +#[cfg(feature = "cli")] +pub(crate) use crate::host::sys::{interrupted, watch_interrupts}; + /// Whether this host can run NVX microVMs. pub const SUPPORTED: bool = true; diff --git a/nvx-rs/src/host/platform/other.rs b/nvx-rs/src/host/platform/other.rs index 9af804e52..a29e79d3a 100644 --- a/nvx-rs/src/host/platform/other.rs +++ b/nvx-rs/src/host/platform/other.rs @@ -7,9 +7,24 @@ use std::time::Duration; use super::Transport; use crate::hypervisor::Hypervisor; +#[cfg(all(feature = "cli", unix))] +pub(crate) use crate::host::sys::{interrupted, watch_interrupts}; + /// Whether this host can run NVX microVMs. pub const SUPPORTED: bool = false; +/// Does nothing: this host has no console interrupts to watch. +#[cfg(all(feature = "cli", not(unix)))] +pub(crate) fn watch_interrupts() -> io::Result<()> { + Ok(()) +} + +/// Reports no interrupt: this host has no console interrupts to watch. +#[cfg(all(feature = "cli", not(unix)))] +pub(crate) fn interrupted() -> bool { + false +} + fn unsupported() -> io::Error { io::Error::new( io::ErrorKind::Unsupported, diff --git a/nvx-rs/src/host/platform/windows.rs b/nvx-rs/src/host/platform/windows.rs index 0c9596c42..67f71e202 100644 --- a/nvx-rs/src/host/platform/windows.rs +++ b/nvx-rs/src/host/platform/windows.rs @@ -20,11 +20,17 @@ use windows_sys::Win32::Storage::FileSystem::{ GetFileInformationByHandle, OPEN_EXISTING, ReadFile, SECURITY_IDENTIFICATION, SECURITY_SQOS_PRESENT, WriteFile, }; +#[cfg(feature = "cli")] +use windows_sys::Win32::Storage::FileSystem::{LockFile, UnlockFile}; +#[cfg(feature = "cli")] +use windows_sys::Win32::System::Console::{CTRL_C_EVENT, SetConsoleCtrlHandler}; use windows_sys::Win32::System::IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED}; use windows_sys::Win32::System::LibraryLoader::{ GetProcAddress, LOAD_LIBRARY_SEARCH_SYSTEM32, LoadLibraryExW, }; use windows_sys::Win32::System::Pipes::{GetNamedPipeServerProcessId, WaitNamedPipeW}; +#[cfg(feature = "cli")] +use windows_sys::Win32::System::Threading::GetExitCodeProcess; use windows_sys::Win32::System::Threading::{ CREATE_BREAKAWAY_FROM_JOB, CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, CreateEventW, CreateProcessW, DeleteProcThreadAttributeList, @@ -187,6 +193,40 @@ impl LaunchedProcess { // SAFETY: as above. unsafe { WaitForSingleObject(raw(&self.process), milliseconds) == WAIT_OBJECT_0 } } + + /// Waits up to `timeout` for the process to exit, and returns its exit status if it has, + /// as `Popen.wait` reads it with `GetExitCodeProcess`. + #[cfg(feature = "cli")] + pub(crate) fn wait_timeout(&self, timeout: Duration) -> io::Result> { + let milliseconds = u32::try_from(timeout.as_millis()).unwrap_or(INFINITE - 1); + // SAFETY: CreateProcessW returned the handle with full access. + match unsafe { WaitForSingleObject(raw(&self.process), milliseconds) } { + WAIT_OBJECT_0 => {} + WAIT_TIMEOUT => return Ok(None), + _ => return Err(io::Error::last_os_error()), + } + let mut status = 0u32; + // SAFETY: as above, and the output pointer is valid. + if unsafe { GetExitCodeProcess(raw(&self.process), &mut status) } == 0 { + return Err(io::Error::last_os_error()); + } + Ok(Some(status)) + } + + /// Asks the process to end with status 1 without waiting for it, as `Popen.terminate` and + /// `Popen.kill` do on Windows. + #[cfg(feature = "cli")] + pub(crate) fn request_termination(&self) -> io::Result<()> { + // SAFETY: CreateProcessW returned the handle with full access. + if unsafe { TerminateProcess(raw(&self.process), 1) } == 0 { + let error = io::Error::last_os_error(); + // A process that already exited cannot be terminated; Python ignores that too. + if self.wait_timeout(Duration::ZERO)?.is_none() { + return Err(error); + } + } + Ok(()) + } } /// Owns an initialized process-thread attribute list. @@ -651,6 +691,61 @@ impl Transport for PipeTransport { } } +/// Whether Ctrl-C reached the console since [`watch_interrupts`] installed its handler. +#[cfg(feature = "cli")] +static INTERRUPTED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + +/// The console control handler, which records Ctrl-C and leaves every other event, such as +/// Ctrl-Break, to the next handler, which terminates the process. +#[cfg(feature = "cli")] +unsafe extern "system" fn record_interrupt(control: u32) -> windows_sys::core::BOOL { + if control == CTRL_C_EVENT { + INTERRUPTED.store(true, std::sync::atomic::Ordering::SeqCst); + 1 + } else { + 0 + } +} + +/// Records each Ctrl-C for [`interrupted`] instead of letting it terminate the process, as +/// Python's `SIGINT` handler, which raises `KeyboardInterrupt`, does. Ctrl-Break still +/// terminates it, as it terminates Python. +#[cfg(feature = "cli")] +pub(crate) fn watch_interrupts() -> io::Result<()> { + // SAFETY: the handler only stores to an atomic, and stays valid for the process's lifetime. + if unsafe { SetConsoleCtrlHandler(Some(record_interrupt), 1) } == 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +/// Returns whether Ctrl-C reached the console since [`watch_interrupts`]. +#[cfg(feature = "cli")] +pub(crate) fn interrupted() -> bool { + INTERRUPTED.load(std::sync::atomic::Ordering::SeqCst) +} + +/// Locks the first byte of `file` unless another handle holds a lock on it, as +/// `msvcrt.locking(fd, LK_NBLCK, 1)` does at offset 0, and returns whether it took the lock. +/// +/// Like the C runtime, which reports every failure of `LockFile` as `EACCES` in this mode, any +/// failure counts as a lock that another handle holds. +#[cfg(feature = "cli")] +pub(crate) fn try_lock_first_byte(file: &fs::File) -> bool { + // SAFETY: the handle is open for as long as `file` is borrowed. + unsafe { LockFile(file.as_raw_handle() as HANDLE, 0, 0, 1, 0) != 0 } +} + +/// Unlocks the first byte of `file`, as `msvcrt.locking(fd, LK_UNLCK, 1)` does at offset 0. +#[cfg(feature = "cli")] +pub(crate) fn unlock_first_byte(file: &fs::File) -> io::Result<()> { + // SAFETY: the handle is open for as long as `file` is borrowed. + if unsafe { UnlockFile(file.as_raw_handle() as HANDLE, 0, 0, 1, 0) } == 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + #[cfg(test)] mod tests { use std::io::Read; diff --git a/nvx-rs/src/host/process.rs b/nvx-rs/src/host/process.rs index 23d2752ce..333917135 100644 --- a/nvx-rs/src/host/process.rs +++ b/nvx-rs/src/host/process.rs @@ -37,6 +37,76 @@ impl Launched { #[cfg(windows)] return self.process.id(); } + + /// Waits up to `timeout` for the process to exit, and returns its status if it has, as + /// `Popen.wait` and `Popen.poll` report it: the exit code, or on Unix the negated number of + /// the signal that ended the process. + #[cfg(feature = "cli")] + pub(crate) fn wait_timeout(&mut self, timeout: Duration) -> io::Result> { + #[cfg(windows)] + return Ok(self.process.wait_timeout(timeout)?.map(i64::from)); + #[cfg(not(windows))] + { + let deadline = Instant::now().checked_add(timeout); + loop { + if let Some(status) = self.child.try_wait()? { + return Ok(Some(status_value(status))); + } + let now = Instant::now(); + match deadline { + Some(deadline) if now >= deadline => return Ok(None), + Some(deadline) => thread::sleep((deadline - now).min(POLL_INTERVAL)), + None => thread::sleep(POLL_INTERVAL), + } + } + } + } + + /// Asks the process to end, as `Popen.terminate` does: with `SIGTERM` on Unix, and by + /// terminating it with status 1 on Windows. A process that has exited is left alone. + #[cfg(feature = "cli")] + pub(crate) fn terminate(&mut self) -> io::Result<()> { + #[cfg(windows)] + return self.process.request_termination(); + #[cfg(not(windows))] + self.signal(nix::sys::signal::Signal::SIGTERM) + } + + /// Ends the process, as `Popen.kill` does: with `SIGKILL` on Unix, and by terminating it with + /// status 1 on Windows. A process that has exited is left alone. + #[cfg(feature = "cli")] + pub(crate) fn kill(&mut self) -> io::Result<()> { + #[cfg(windows)] + return self.process.request_termination(); + #[cfg(not(windows))] + self.signal(nix::sys::signal::Signal::SIGKILL) + } + + /// Sends `signal` to the process unless it has exited. The child is not reaped until it is + /// waited for, so its process ID cannot name another process meanwhile. + #[cfg(all(feature = "cli", not(windows)))] + fn signal(&mut self, signal: nix::sys::signal::Signal) -> io::Result<()> { + if self.child.try_wait()?.is_some() { + return Ok(()); + } + let pid = i32::try_from(self.child.id()).map_err(io::Error::other)?; + match nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), signal) { + Ok(()) | Err(nix::errno::Errno::ESRCH) => Ok(()), + Err(error) => Err(error.into()), + } + } +} + +/// Returns `Popen.returncode` for `status`: the exit code, or the negated signal number. +#[cfg(all(feature = "cli", not(windows)))] +fn status_value(status: std::process::ExitStatus) -> i64 { + use std::os::unix::process::ExitStatusExt; + + match (status.code(), status.signal()) { + (Some(code), _) => i64::from(code), + (None, Some(signal)) => -i64::from(signal), + (None, None) => 0, + } } /// Starts OpenVMM detached from the caller. diff --git a/nvx-rs/src/host/python/builtins.rs b/nvx-rs/src/host/python/builtins.rs new file mode 100644 index 000000000..5e0789dd7 --- /dev/null +++ b/nvx-rs/src/host/python/builtins.rs @@ -0,0 +1,614 @@ +//! Python's built-in string and integer operations. +//! +//! - [`is_space`], [`split`], and [`strip`]: `str.isspace`, `str.split()`, and `str.strip()`. +//! Python's whitespace includes the ASCII separators U+001C through U+001F, which Rust's +//! [`char::is_whitespace`] does not. +//! - [`repr`]: `repr` of a `str`, with which messages quote values. +//! - [`Int`]: a Python `int` of any size, from `int(text)`, with its acceptance of signs, +//! underscores, Unicode decimal digits, and surrounding whitespace, and its limit of +//! [`MAX_INT_DIGITS`] decimal digits. + +use std::cmp::Ordering; +use std::fmt::{self, Write as _}; + +use super::unicode; + +/// The most decimal digits that `int(text)` converts: `sys.get_int_max_str_digits()`. +pub(crate) const MAX_INT_DIGITS: usize = 4300; + +/// Returns whether Python's `str.isspace` holds for `character`. +pub(crate) fn is_space(character: char) -> bool { + matches!( + u32::from(character), + 0x09..=0x0D + | 0x1C..=0x20 + | 0x85 + | 0xA0 + | 0x1680 + | 0x2000..=0x200A + | 0x2028 + | 0x2029 + | 0x202F + | 0x205F + | 0x3000 + ) +} + +/// Returns whether `str.isprintable` holds for `character`, so that `repr` shows it as is. +pub(crate) fn is_printable(character: char) -> bool { + let code = u32::from(character); + if code < 0x80 { + return (0x20..0x7F).contains(&code); + } + let ranges = unicode::NON_PRINTABLE; + // The ranges are sorted and disjoint: find the last one that starts at or before `code`. + let index = ranges.partition_point(|&(start, _)| start <= code); + index == 0 || ranges[index - 1].1 < code +} + +/// Returns the value of a Unicode decimal digit, of category Nd, as `int` reads it. +pub(crate) fn decimal_value(character: char) -> Option { + let code = u32::from(character); + let starts = unicode::DECIMAL_STARTS; + // Every run of decimal digits is ten consecutive code points, from zero to nine. + let index = starts.partition_point(|&start| start <= code); + let start = *starts.get(index.checked_sub(1)?)?; + (code - start < 10).then_some(code - start) +} + +/// Splits `text` at runs of Python whitespace and drops empty fields, as `str.split()` does. +pub(crate) fn split(text: &str) -> impl Iterator { + text.split(is_space).filter(|field| !field.is_empty()) +} + +/// Strips Python whitespace from both ends of `text`, as `str.strip()` does. +pub(crate) fn strip(text: &str) -> &str { + text.trim_matches(is_space) +} + +/// Returns `repr(text)`: `text` in quotes, with backslashes, the quote, and the characters that +/// are not printable escaped. +pub(crate) fn repr(text: &str) -> String { + // Single quotes, unless the text holds single quotes and no double quotes. + let quote = if text.contains('\'') && !text.contains('"') { + '"' + } else { + '\'' + }; + let mut quoted = String::with_capacity(text.len() + 2); + quoted.push(quote); + for character in text.chars() { + let code = u32::from(character); + match character { + _ if character == quote || character == '\\' => { + quoted.push('\\'); + quoted.push(character); + } + '\t' => quoted.push_str("\\t"), + '\n' => quoted.push_str("\\n"), + '\r' => quoted.push_str("\\r"), + _ if code < 0x20 || code == 0x7F => { + let _ = write!(quoted, "\\x{code:02x}"); + } + _ if code < 0x7F || is_printable(character) => quoted.push(character), + _ if code <= 0xFF => { + let _ = write!(quoted, "\\x{code:02x}"); + } + _ if code <= 0xFFFF => { + let _ = write!(quoted, "\\u{code:04x}"); + } + _ => { + let _ = write!(quoted, "\\U{code:08x}"); + } + } + } + quoted.push(quote); + quoted +} + +/// Why `int(text)` refuses `text`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IntError { + /// The text is not an integer literal. + Invalid, + /// The literal has this many decimal digits, more than `int` converts (4300). + TooManyDigits(usize), +} + +impl IntError { + /// Returns the message of the `ValueError` with which `int(text)` refuses `text`. + pub fn message(&self, text: &str) -> String { + match self { + Self::Invalid => { + // `%.200R` keeps the first 200 characters of the representation. + let quoted: String = repr(text).chars().take(200).collect(); + format!("invalid literal for int() with base 10: {quoted}") + } + Self::TooManyDigits(digits) => too_many_digits_message(*digits), + } + } +} + +/// Returns the message of the `ValueError` that refuses a decimal literal of `digits` digits, +/// more than [`MAX_INT_DIGITS`]. +pub(crate) fn too_many_digits_message(digits: usize) -> String { + format!( + "Exceeds the limit ({MAX_INT_DIGITS} digits) for integer string conversion: value has \ + {digits} digits; use sys.set_int_max_str_digits() to increase the limit" + ) +} + +/// A Python `int` of any size. +/// +/// [`Display`](fmt::Display) gives its decimal form, as `str()` does. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Int { + /// Whether the integer is below zero; zero is never negative. + negative: bool, + /// The decimal digits of the magnitude, without leading zeros: `"0"` for zero. + digits: String, +} + +impl Int { + /// Zero. + pub fn zero() -> Self { + Self { + negative: false, + digits: "0".to_owned(), + } + } + + /// Parses `text` as `int(text)` does. + /// + /// Python first maps Unicode whitespace to spaces and Unicode decimal digits to ASCII + /// digits, and refuses any other character outside ASCII. It then accepts surrounding ASCII + /// whitespace, a sign, leading zeros, and single underscores between digits. + pub fn parse(text: &str) -> Result { + let (negative, digits) = parse_digits(text, 10)?; + Ok(Self::from_digits(negative, &digits)) + } + + /// Builds the integer of decimal `digits`, most significant first. + fn from_digits(negative: bool, digits: &[u8]) -> Self { + let significant = digits + .iter() + .position(|&digit| digit != 0) + .map_or(&[][..], |first| &digits[first..]); + if significant.is_empty() { + return Self::zero(); + } + Self { + negative, + digits: significant + .iter() + .map(|&digit| char::from(b'0' + digit)) + .collect(), + } + } + + /// Returns whether the integer is below zero. + pub fn is_negative(&self) -> bool { + self.negative + } + + /// Returns whether the integer is above zero. + pub fn is_positive(&self) -> bool { + !self.negative && self.digits != "0" + } + + /// Returns the integer as a `u32`, if it is one. + pub fn to_u32(&self) -> Option { + self.to_u64().and_then(|value| u32::try_from(value).ok()) + } + + /// Returns the integer as a `u64`, if it is one. + pub fn to_u64(&self) -> Option { + if self.negative { + return None; + } + self.digits.parse().ok() + } + + /// Returns the integer plus one. + #[must_use] + pub fn plus_one(&self) -> Self { + if self.negative { + // -n + 1 = -(n - 1), and -1 + 1 = 0. + let magnitude = decrement(&self.digits); + return Self { + negative: magnitude != "0", + digits: magnitude, + }; + } + Self { + negative: false, + digits: increment(&self.digits), + } + } +} + +impl From for Int { + fn from(value: u64) -> Self { + Self { + negative: false, + digits: value.to_string(), + } + } +} + +impl From for Int { + fn from(value: u32) -> Self { + Self::from(u64::from(value)) + } +} + +impl From for Int { + fn from(value: i64) -> Self { + Self { + negative: value < 0, + digits: value.unsigned_abs().to_string(), + } + } +} + +impl PartialOrd for Int { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Int { + fn cmp(&self, other: &Self) -> Ordering { + let magnitude = || { + self.digits + .len() + .cmp(&other.digits.len()) + .then_with(|| self.digits.cmp(&other.digits)) + }; + match (self.negative, other.negative) { + (false, false) => magnitude(), + (true, true) => magnitude().reverse(), + (false, true) => Ordering::Greater, + (true, false) => Ordering::Less, + } + } +} + +impl fmt::Display for Int { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.negative { + formatter.write_str("-")?; + } + formatter.write_str(&self.digits) + } +} + +impl fmt::Debug for Int { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, formatter) + } +} + +/// Adds one to a decimal magnitude. +fn increment(digits: &str) -> String { + let mut bytes = digits.as_bytes().to_vec(); + for byte in bytes.iter_mut().rev() { + if *byte == b'9' { + *byte = b'0'; + } else { + *byte += 1; + return bytes.into_iter().map(char::from).collect(); + } + } + std::iter::once('1') + .chain(bytes.into_iter().map(char::from)) + .collect() +} + +/// Subtracts one from a positive decimal magnitude. +fn decrement(digits: &str) -> String { + let mut bytes = digits.as_bytes().to_vec(); + for byte in bytes.iter_mut().rev() { + if *byte == b'0' { + *byte = b'9'; + } else { + *byte -= 1; + break; + } + } + let text: String = bytes.into_iter().map(char::from).collect(); + match text.trim_start_matches('0') { + "" => "0".to_owned(), + trimmed => trimmed.to_owned(), + } +} + +/// Parses `text` as `int(text, base)` does for base 10 or 16, returning its sign and its digit +/// values, most significant first and with any leading zeros. See [`Int::parse`]; base 16 also +/// accepts a `0x` prefix, which a single underscore may follow. +pub(crate) fn parse_digits(text: &str, base: u32) -> Result<(bool, Vec), IntError> { + let mut ascii = Vec::with_capacity(text.len()); + for character in text.chars() { + let code = u32::from(character); + if code < 0x7F { + ascii.push(code as u8); + } else if is_space(character) { + ascii.push(b' '); + } else if let Some(value) = decimal_value(character) { + ascii.push(b'0' + value as u8); + } else { + return Err(IntError::Invalid); + } + } + let is_ascii_space = |byte: u8| matches!(byte, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r'); + let mut index = 0; + while ascii.get(index).copied().is_some_and(is_ascii_space) { + index += 1; + } + let mut negative = false; + match ascii.get(index) { + Some(b'+') => index += 1, + Some(b'-') => { + negative = true; + index += 1; + } + _ => {} + } + if base == 16 + && ascii.get(index) == Some(&b'0') + && matches!(ascii.get(index + 1), Some(b'x' | b'X')) + { + index += 2; + if ascii.get(index) == Some(&b'_') { + index += 1; + } + } + if ascii.get(index) == Some(&b'_') { + return Err(IntError::Invalid); + } + let mut digits = Vec::new(); + let mut previous = 0; + while let Some(&byte) = ascii.get(index) { + if byte == b'_' { + if previous == b'_' { + return Err(IntError::Invalid); + } + } else { + match char::from(byte).to_digit(base) { + Some(value) => digits.push(value as u8), + None => break, + } + } + previous = byte; + index += 1; + } + if previous == b'_' || digits.is_empty() { + return Err(IntError::Invalid); + } + while ascii.get(index).copied().is_some_and(is_ascii_space) { + index += 1; + } + // Anything else after the digits, including a NUL, makes the literal invalid. + if index != ascii.len() { + return Err(IntError::Invalid); + } + if base == 10 && digits.len() > MAX_INT_DIGITS { + return Err(IntError::TooManyDigits(digits.len())); + } + Ok((negative, digits)) +} + +/// Parses `text` as `float(text)` does, or returns `None` where it raises `ValueError`. +/// +/// Python maps Unicode whitespace to spaces and Unicode decimal digits to ASCII digits, and +/// refuses any other character outside ASCII. Single underscores may separate digits. It then +/// strips surrounding ASCII whitespace and reads a decimal number with an optional exponent, or +/// `inf`, `infinity`, or `nan` in any case, each with an optional sign; numbers beyond the range +/// of a float become infinities. +#[cfg(feature = "cli")] +pub(crate) fn parse_float(text: &str) -> Option { + let mut ascii = String::with_capacity(text.len()); + for character in text.chars() { + if character.is_ascii() { + ascii.push(character); + } else if is_space(character) { + ascii.push(' '); + } else { + ascii.push(char::from(b'0' + decimal_value(character)? as u8)); + } + } + let mut digits = String::with_capacity(ascii.len()); + let mut previous = '\0'; + for character in ascii.chars() { + if character == '_' { + if !previous.is_ascii_digit() { + return None; + } + } else { + if previous == '_' && !character.is_ascii_digit() { + return None; + } + digits.push(character); + } + previous = character; + } + if previous == '_' { + return None; + } + let number = digits.trim_matches(|c| matches!(c, ' ' | '\t' | '\n' | '\x0b' | '\x0c' | '\r')); + // Rust reads the grammar of `PyOS_string_to_double`: digits with an optional fraction and + // exponent, or `inf`, `infinity`, and `nan` in any case, after an optional sign. + number.parse().ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn python_whitespace_includes_the_ascii_separators() { + for space in [ + '\t', '\n', '\x0b', '\x0c', '\r', '\x1c', '\x1f', ' ', '\u{85}', '\u{3000}', + ] { + assert!(is_space(space), "{space:?}"); + } + for other in ['a', '\0', '\u{200b}', '\u{feff}', '\u{180e}'] { + assert!(!is_space(other), "{other:?}"); + } + assert_eq!( + split(" a\x1cb c\u{3000}").collect::>(), + ["a", "b", "c"] + ); + assert_eq!(strip("\x1f a b \u{85}"), "a b"); + } + + #[test] + fn repr_quotes_and_escapes_as_python() { + for (text, expected) in [ + ("abc", "'abc'"), + ("it's", "\"it's\""), + ("say \"hi\"", "'say \"hi\"'"), + ("both ' and \"", "'both \\' and \"'"), + ("a\\b", "'a\\\\b'"), + ("\t\n\r\0\x7f\x1b", "'\\t\\n\\r\\x00\\x7f\\x1b'"), + ("é\u{a0}\u{ad}", "'é\\xa0\\xad'"), + ("\u{2028}\u{200b}\u{378}", "'\\u2028\\u200b\\u0378'"), + ("\u{1f600}\u{e0001}", "'\u{1f600}\\U000e0001'"), + ] { + assert_eq!(repr(text), expected, "{text:?}"); + } + } + + #[test] + fn integers_parse_as_python_int() { + for (text, expected) in [ + ("007", "7"), + ("+5", "5"), + ("-5", "-5"), + (" 5 ", "5"), + ("5_000", "5000"), + ("\u{2003}5\u{3000}", "5"), + ("\u{ff15}", "5"), + ("\u{664}\u{662}", "42"), + ("4\u{660}2", "402"), + ("-0", "0"), + ("\x0b42\x0c", "42"), + ] { + assert_eq!(Int::parse(text).unwrap().to_string(), expected, "{text:?}"); + } + for text in [ + "", + " ", + "+", + "-", + "+-5", + "_5", + "5_", + "5__0", + "0x5", + "0d5", + "1e3", + "5.0", + "4\u{200b}2", + "\x1c42\x1f", + "1\x002", + "\u{7f}1", + "5 5", + ] { + assert_eq!(Int::parse(text), Err(IntError::Invalid), "{text:?}"); + } + assert!(Int::parse(&"1".repeat(MAX_INT_DIGITS)).is_ok()); + // Leading zeros count as digits. + assert_eq!( + Int::parse(&format!("-{}1", "0".repeat(MAX_INT_DIGITS))), + Err(IntError::TooManyDigits(MAX_INT_DIGITS + 1)) + ); + assert_eq!( + IntError::TooManyDigits(4301).message(""), + "Exceeds the limit (4300 digits) for integer string conversion: value has 4301 \ + digits; use sys.set_int_max_str_digits() to increase the limit" + ); + assert_eq!( + IntError::Invalid.message("x'y"), + "invalid literal for int() with base 10: \"x'y\"" + ); + } + + #[test] + fn integers_compare_and_increment_at_any_size() { + let parse = |text: &str| Int::parse(text).unwrap(); + assert_eq!( + parse("99999999999999999999").plus_one().to_string(), + "100000000000000000000" + ); + assert_eq!(parse("-1").plus_one(), Int::zero()); + assert_eq!(parse("-10").plus_one().to_string(), "-9"); + assert_eq!(parse("4294967295").to_u32(), Some(u32::MAX)); + assert_eq!(parse("4294967296").to_u32(), None); + assert_eq!(parse("-1").to_u64(), None); + assert!(parse("-3") < parse("-2")); + assert!(parse("-2") < Int::zero()); + assert!(parse("10") > parse("9")); + assert!(!Int::zero().is_positive()); + assert!(!Int::zero().is_negative()); + assert_eq!(Int::from(-7_i64).to_string(), "-7"); + } + + #[test] + #[cfg(feature = "cli")] + fn floats_parse_as_python_float() { + for (text, expected) in [ + ("60", 60.0), + (" 0.25 ", 0.25), + ("1_000.5", 1000.5), + ("1e1_0", 1e10), + (".5", 0.5), + ("5.", 5.0), + ("1.e2", 100.0), + ("+1E-2", 0.01), + ("\u{664}\u{662}", 42.0), + ("\u{3000}7\u{85}", 7.0), + ("1e400", f64::INFINITY), + ("-Infinity", f64::NEG_INFINITY), + ("iNf", f64::INFINITY), + ] { + assert_eq!(parse_float(text), Some(expected), "{text:?}"); + } + assert!(parse_float("-nan").is_some_and(f64::is_nan)); + for text in [ + "", + " ", + ".", + "e5", + "1e", + "_1", + "1_", + "1__0", + "1_.5", + "1._5", + "inf_", + "0x10", + "1 2", + "nan(1)", + "in", + "\u{200b}1", + "1\0", + "+", + "--1", + ] { + assert_eq!(parse_float(text), None, "{text:?}"); + } + } + + #[test] + fn unicode_tables_classify_digits_and_printable_characters() { + assert_eq!(decimal_value('7'), Some(7)); + assert_eq!(decimal_value('\u{663}'), Some(3)); + assert_eq!(decimal_value('\u{ff19}'), Some(9)); + assert_eq!(decimal_value('\u{2155}'), None); + assert_eq!(decimal_value('a'), None); + assert!(is_printable('é')); + assert!(!is_printable('\u{a0}')); + assert!(!is_printable('\u{10ffff}')); + } +} diff --git a/nvx-rs/src/host/python/dumps.rs b/nvx-rs/src/host/python/dumps.rs new file mode 100644 index 000000000..190fb6bc8 --- /dev/null +++ b/nvx-rs/src/host/python/dumps.rs @@ -0,0 +1,212 @@ +//! `json.dumps(value, indent=2, sort_keys=True)`, with which `nvx.py` writes its JSON files. +//! +//! [`Value`] holds what the ports write: Python's `None`, `bool`, `int` of any size, `str`, and +//! lists and dicts of them, and [`dumps`] writes it as Python does: ASCII text with one item or +//! member per line, each line indented by two spaces per level and ended by `,` where another +//! follows, members sorted by name and written as `"name": value`, and empty containers as `[]` +//! and `{}`. Like `ensure_ascii`, it escapes every character outside the printable ASCII range +//! `' '..='~'`, so `DEL` and anything beyond ASCII become `\uXXXX`, with surrogate pairs for +//! characters beyond the Basic Multilingual Plane. + +use std::collections::BTreeMap; +use std::fmt::Write as _; + +use super::builtins::Int; + +/// A value that [`dumps`] writes. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum Value { + /// `None`, which JSON writes as `null`. + Null, + /// `True` or `False`. + Bool(bool), + /// An `int` of any size. + Int(Int), + /// A `str`. + Str(String), + /// A `list`. + List(Vec), + /// A `dict` with string keys, which `sort_keys` writes in code point order, as a + /// `BTreeMap` iterates UTF-8 strings. + Dict(BTreeMap), +} + +impl From<&str> for Value { + fn from(text: &str) -> Self { + Self::Str(text.to_owned()) + } +} + +impl From for Value { + fn from(text: String) -> Self { + Self::Str(text) + } +} + +impl From for Value { + fn from(value: u64) -> Self { + Self::Int(Int::from(value)) + } +} + +impl From for Value { + fn from(value: u32) -> Self { + Self::Int(Int::from(value)) + } +} + +impl From for Value { + fn from(value: i64) -> Self { + Self::Int(Int::from(value)) + } +} + +impl From for Value { + fn from(value: Int) -> Self { + Self::Int(value) + } +} + +impl> From> for Value { + fn from(value: Option) -> Self { + value.map_or(Self::Null, Into::into) + } +} + +/// Builds a [`Value::Dict`] from its members. +pub(crate) fn dict(members: [(&str, Value); N]) -> Value { + Value::Dict( + members + .into_iter() + .map(|(name, value)| (name.to_owned(), value)) + .collect(), + ) +} + +/// Returns `json.dumps(value, indent=2, sort_keys=True)`, without a trailing newline. +pub(crate) fn dumps(value: &Value) -> String { + let mut output = String::new(); + write_value(&mut output, value, 0); + output +} + +/// Appends `value` at indentation `level`, whose first line the caller has already indented. +fn write_value(output: &mut String, value: &Value, level: usize) { + match value { + Value::Null => output.push_str("null"), + Value::Bool(true) => output.push_str("true"), + Value::Bool(false) => output.push_str("false"), + Value::Int(value) => { + let _ = write!(output, "{value}"); + } + Value::Str(text) => write_string(output, text), + Value::List(items) if items.is_empty() => output.push_str("[]"), + Value::List(items) => { + output.push('['); + for (index, item) in items.iter().enumerate() { + if index > 0 { + output.push(','); + } + newline(output, level + 1); + write_value(output, item, level + 1); + } + newline(output, level); + output.push(']'); + } + Value::Dict(members) if members.is_empty() => output.push_str("{}"), + Value::Dict(members) => { + output.push('{'); + for (index, (name, member)) in members.iter().enumerate() { + if index > 0 { + output.push(','); + } + newline(output, level + 1); + write_string(output, name); + output.push_str(": "); + write_value(output, member, level + 1); + } + newline(output, level); + output.push('}'); + } + } +} + +/// Starts a new line indented to `level`. +fn newline(output: &mut String, level: usize) { + output.push('\n'); + for _ in 0..level { + output.push_str(" "); + } +} + +/// Appends `text` as `json.dumps` quotes it with `ensure_ascii`. +fn write_string(output: &mut String, text: &str) { + output.push('"'); + for character in text.chars() { + match character { + '"' => output.push_str("\\\""), + '\\' => output.push_str("\\\\"), + '\n' => output.push_str("\\n"), + '\r' => output.push_str("\\r"), + '\t' => output.push_str("\\t"), + '\u{8}' => output.push_str("\\b"), + '\u{c}' => output.push_str("\\f"), + ' '..='~' => output.push(character), + _ => { + let mut units = [0u16; 2]; + for unit in character.encode_utf16(&mut units) { + let _ = write!(output, "\\u{unit:04x}"); + } + } + } + } + output.push('"'); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn values_are_written_as_json_dumps_writes_them() { + let value = dict([ + ("zeta", Value::List(vec![Value::from(1u64), Value::Null])), + ("alpha", Value::Bool(true)), + ("empty", Value::List(Vec::new())), + ( + "nested", + dict([("b", Value::from("x")), ("a", Value::Bool(false))]), + ), + ("none", Value::Dict(BTreeMap::new())), + ( + "big", + Value::Int(Int::parse("-123456789012345678901234567890").unwrap()), + ), + ]); + assert_eq!( + dumps(&value), + "{\n \"alpha\": true,\n \"big\": -123456789012345678901234567890,\n \ + \"empty\": [],\n \"nested\": {\n \"a\": false,\n \"b\": \"x\"\n },\n \ + \"none\": {},\n \"zeta\": [\n 1,\n null\n ]\n}" + ); + } + + #[test] + fn strings_are_escaped_to_ascii() { + assert_eq!( + dumps(&Value::from( + "a\"b\\c\n\r\t\u{8}\u{c}\u{0}\u{1f} ~\u{7f}é\u{20ac}\u{1f600}" + )), + "\"a\\\"b\\\\c\\n\\r\\t\\b\\f\\u0000\\u001f ~\\u007f\\u00e9\\u20ac\\ud83d\\ude00\"" + ); + } + + #[test] + fn keys_sort_by_code_point() { + let value = dict([("é", Value::Null), ("z", Value::Null), ("Z", Value::Null)]); + assert_eq!( + dumps(&value), + "{\n \"Z\": null,\n \"z\": null,\n \"\\u00e9\": null\n}" + ); + } +} diff --git a/nvx-rs/src/host/python/fs.rs b/nvx-rs/src/host/python/fs.rs new file mode 100644 index 000000000..e6be18b36 --- /dev/null +++ b/nvx-rs/src/host/python/fs.rs @@ -0,0 +1,656 @@ +//! The filesystem queries of Python's `os`, `os.path`, and `pathlib.Path`, as CPython 3.14 +//! performs them on Linux and Windows. +//! +//! - [`cwd`]: the current directory, as `Path.cwd()` gives it. +//! - [`is_file`], [`is_dir`], [`is_symlink`], and [`exists`]: `os.path.isfile`, `isdir`, `islink`, +//! and `exists`, which follow symbolic links, except `islink`, and are false on any error. On +//! Windows, only `IO_REPARSE_TAG_SYMLINK` reparse points are links, not junctions. +//! - [`stat`], [`lstat`], and [`fstat`]: the identity and owner that `os.stat` and `os.lstat` +//! report for a path, and the identity, type, and link count that `os.fstat` reports for an +//! open file. On Windows, the identity is the 64-bit volume serial number and the 128-bit file +//! ID of `FILE_ID_INFO`, as Python 3.12 and later report them, which also tell apart the files +//! of ReFS volumes. [`lexists`] is `os.path.lexists`. +//! - [`resolve`]: `Path.resolve()`, which follows `os.path.realpath`: it resolves symbolic links +//! component by component on POSIX, keeping the components that do not exist, and asks Windows +//! for the final path of the longest existing prefix, dropping the `\\?\` prefix again. +//! - [`os_error_message`]: the text of an `OSError`. +//! +//! Paths must be valid Unicode: a link target or working directory that is not is an error, +//! where Python would decode it into surrogates. + +use std::io; +use std::path::Path; + +use super::builtins::repr; +use super::not_unicode; +use super::path::PurePath; + +/// The fields of `os.stat(path)` that NVX reads. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct Stat { + /// `st_dev`: the device, or on Windows the volume serial number. + pub(crate) device: u64, + /// `st_ino`: the inode, or on Windows the file ID. + pub(crate) inode: u128, + /// `st_uid`: the owner, or zero on Windows. + pub(crate) uid: u32, + /// `st_gid`: the group, or zero on Windows. + pub(crate) gid: u32, +} + +impl Stat { + /// Returns `(st_dev, st_ino)`, the identity of the file. + pub(crate) fn identity(&self) -> (u64, u128) { + (self.device, self.inode) + } +} + +/// Returns `Path.cwd()`. +pub(crate) fn cwd() -> io::Result { + let directory = std::env::current_dir()?; + let text = directory + .to_str() + .ok_or_else(|| not_unicode("the current directory"))?; + Ok(PurePath::native(text)) +} + +/// Returns `os.path.isfile(path)`: whether `path` is a regular file, following links. +pub(crate) fn is_file(path: &Path) -> bool { + imp::is_file(path) +} + +/// Returns `os.path.isdir(path)`: whether `path` is a directory, following links. +pub(crate) fn is_dir(path: &Path) -> bool { + imp::is_dir(path) +} + +/// Returns `os.path.islink(path)`: whether `path` is a symbolic link. +pub(crate) fn is_symlink(path: &Path) -> bool { + imp::is_symlink(path) +} + +/// Returns `os.path.exists(path)`: whether `path` exists, following links. +pub(crate) fn exists(path: &Path) -> bool { + imp::exists(path) +} + +/// Returns `os.stat(path)`, following links. +pub(crate) fn stat(path: &Path) -> io::Result { + imp::stat(path) +} + +/// Returns `os.lstat(path)`, which does not follow a link that `path` names. +#[cfg(feature = "cli")] +pub(crate) fn lstat(path: &Path) -> io::Result { + imp::lstat(path) +} + +/// Returns `os.path.lexists(path)`: whether `os.lstat(path)` succeeds, so a link counts even if +/// its target is missing. +#[cfg(feature = "cli")] +pub(crate) fn lexists(path: &Path) -> bool { + lstat(path).is_ok() +} + +/// The fields of `os.fstat` on an open file that NVX reads. +#[cfg(all(feature = "cli", any(unix, windows)))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct FileStat { + /// `st_dev` and `st_ino`, the identity that [`Stat::identity`] gives for the same file. + pub(crate) identity: (u64, u128), + /// Whether `st_mode` is a regular file. + pub(crate) regular: bool, + /// `st_nlink`: the number of names that link the file. + pub(crate) links: u64, + /// Whether the file is a reparse point, from `st_file_attributes` on Windows; never on POSIX. + pub(crate) reparse_point: bool, +} + +/// Returns `os.fstat` of an open file, as CPython's `_Py_fstat_noraise` computes it. +#[cfg(all(feature = "cli", any(unix, windows)))] +pub(crate) fn fstat(file: &std::fs::File) -> io::Result { + imp::fstat(file) +} + +/// Returns `path.resolve()`: the absolute path of `path`, with symbolic links resolved and `..` +/// components removed, as `os.path.realpath(path)` gives it. +pub(crate) fn resolve(path: &PurePath) -> io::Result { + imp::realpath(path.as_str()).map(|resolved| PurePath::native(&resolved)) +} + +/// Returns `str(error)` for an `OSError` that an operation on `filename` raised: `[Errno N] +/// text: 'filename'` on POSIX, and `[WinError N] text: 'filename'` on Windows, with the text +/// that the system gives for the error. +pub(crate) fn os_error_message(error: &io::Error, filename: Option<&str>) -> String { + let Some(code) = error.raw_os_error() else { + return error.to_string(); + }; + // Rust appends ` (os error N)` to the system's text, and Python drops the trailing period + // and whitespace of Windows messages. + let text = error.to_string(); + let text = text + .strip_suffix(&format!(" (os error {code})")) + .unwrap_or(&text); + let (label, text) = if cfg!(windows) { + ( + "WinError", + text.trim_end_matches(|c: char| c <= ' ' || c == '.'), + ) + } else { + ("Errno", text) + }; + match filename { + Some(filename) => format!("[{label} {code}] {text}: {}", repr(filename)), + None => format!("[{label} {code}] {text}"), + } +} + +#[cfg(unix)] +/// The POSIX implementation, on the standard library. +mod imp { + use std::fs; + use std::io; + use std::os::unix::fs::MetadataExt; + use std::path::Path; + + use super::{Stat, not_unicode}; + + /// See [`super::is_file`]. + pub(super) fn is_file(path: &Path) -> bool { + fs::metadata(path).is_ok_and(|metadata| metadata.is_file()) + } + + /// See [`super::is_dir`]. + pub(super) fn is_dir(path: &Path) -> bool { + fs::metadata(path).is_ok_and(|metadata| metadata.is_dir()) + } + + /// See [`super::is_symlink`]. + pub(super) fn is_symlink(path: &Path) -> bool { + fs::symlink_metadata(path).is_ok_and(|metadata| metadata.file_type().is_symlink()) + } + + /// See [`super::exists`]. + pub(super) fn exists(path: &Path) -> bool { + fs::metadata(path).is_ok() + } + + /// See [`super::stat`]. + pub(super) fn stat(path: &Path) -> io::Result { + status(fs::metadata(path)?) + } + + /// See [`super::lstat`]. + #[cfg(feature = "cli")] + pub(super) fn lstat(path: &Path) -> io::Result { + status(fs::symlink_metadata(path)?) + } + + /// Returns the fields of [`Stat`] that `metadata` holds. + fn status(metadata: fs::Metadata) -> io::Result { + Ok(Stat { + device: metadata.dev(), + inode: u128::from(metadata.ino()), + uid: metadata.uid(), + gid: metadata.gid(), + }) + } + + /// See [`super::fstat`]. + #[cfg(feature = "cli")] + pub(super) fn fstat(file: &fs::File) -> io::Result { + let metadata = file.metadata()?; + Ok(super::FileStat { + identity: (metadata.dev(), u128::from(metadata.ino())), + regular: metadata.file_type().is_file(), + links: metadata.nlink(), + reparse_point: false, + }) + } + + /// Returns `posixpath.realpath(path)` in its non-strict mode: each component is resolved in + /// turn, a symbolic link is replaced by its target, and a component that cannot be examined + /// is kept as it is. A symbolic-link loop stops resolution at the link. + pub(super) fn realpath(path: &str) -> io::Result { + // The unresolved components, last first. `None` marks the end of a link's target, and + // is followed by the link's own path. + let mut rest: Vec> = path + .split('/') + .rev() + .map(|part| Some(part.to_owned())) + .collect(); + let mut part_count = rest.len(); + let mut resolved = if path.starts_with('/') { + "/".to_owned() + } else { + let directory = std::env::current_dir()?; + directory + .to_str() + .ok_or_else(|| not_unicode("the current directory"))? + .to_owned() + }; + // Each link that has been seen, with its resolved target once known. + let mut seen: std::collections::HashMap> = + std::collections::HashMap::new(); + while part_count > 0 { + let Some(name) = rest.pop() else { + break; + }; + let Some(name) = name else { + // The target of a link is resolved. + if let Some(Some(link)) = rest.pop() { + seen.insert(link, Some(resolved.clone())); + } + continue; + }; + part_count -= 1; + if name.is_empty() || name == "." { + continue; + } + if name == ".." { + let end = resolved.rfind('/').unwrap_or(0); + resolved.truncate(end); + if resolved.is_empty() { + resolved.push('/'); + } + continue; + } + let candidate = if resolved == "/" { + format!("/{name}") + } else { + format!("{resolved}/{name}") + }; + if candidate.contains('\0') { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "embedded null byte", + )); + } + let target = match fs::symlink_metadata(&candidate) { + Ok(metadata) if !metadata.file_type().is_symlink() => { + resolved = candidate; + continue; + } + Ok(_) => match seen.get(&candidate) { + Some(Some(target)) => { + resolved = target.clone(); + continue; + } + // A link whose resolution is in progress: a loop. + Some(None) => { + resolved = candidate; + continue; + } + None => fs::read_link(&candidate), + }, + Err(error) => Err(error), + }; + let Ok(target) = target else { + // Errors are ignored: the component is kept as it is. + resolved = candidate; + continue; + }; + let target = target + .into_os_string() + .into_string() + .map_err(|_| not_unicode("a symbolic link's target"))?; + if target.starts_with('/') { + "/".clone_into(&mut resolved); + } + seen.insert(candidate.clone(), None); + rest.push(Some(candidate)); + rest.push(None); + let parts: Vec<&str> = target.split('/').collect(); + part_count += parts.len(); + rest.extend(parts.into_iter().rev().map(|part| Some(part.to_owned()))); + } + Ok(resolved) + } +} + +#[cfg(windows)] +/// The Windows implementation, on the Win32 calls that CPython makes. +mod imp { + use std::io; + use std::path::Path; + + use super::super::path::{Flavor, windows_normpath}; + use super::super::windows::{self as win32, StatMode}; + use super::Stat; + + /// See [`super::is_file`]. + pub(super) fn is_file(path: &Path) -> bool { + win32::stat(path, true).is_ok_and(|status| status.mode == StatMode::Regular) + } + + /// See [`super::is_dir`]. + pub(super) fn is_dir(path: &Path) -> bool { + win32::stat(path, true).is_ok_and(|status| status.mode == StatMode::Directory) + } + + /// See [`super::is_symlink`]. + pub(super) fn is_symlink(path: &Path) -> bool { + win32::stat(path, false).is_ok_and(|status| status.mode == StatMode::Link) + } + + /// See [`super::exists`]. + pub(super) fn exists(path: &Path) -> bool { + win32::stat(path, true).is_ok() + } + + /// See [`super::stat`]. + pub(super) fn stat(path: &Path) -> io::Result { + let status = win32::stat(path, true)?; + Ok(Stat { + device: status.device, + inode: status.inode, + uid: 0, + gid: 0, + }) + } + + /// See [`super::lstat`]. + #[cfg(feature = "cli")] + pub(super) fn lstat(path: &Path) -> io::Result { + let status = win32::stat(path, false)?; + Ok(Stat { + device: status.device, + inode: status.inode, + uid: 0, + gid: 0, + }) + } + + /// See [`super::fstat`]. + #[cfg(feature = "cli")] + pub(super) fn fstat(file: &std::fs::File) -> io::Result { + let status = win32::fstat(file)?; + Ok(super::FileStat { + identity: (status.device, status.inode), + regular: status.mode == StatMode::Regular, + links: u64::from(status.links), + reparse_point: status.reparse_point, + }) + } + + /// Returns `ntpath.normcase(path)`: lowercase, with `\` separators. + fn normcase(path: &str) -> String { + path.replace('/', "\\").to_lowercase() + } + + /// Returns `ntpath.dirname(path)`. + fn dirname(path: &str) -> String { + Flavor::Windows.split(path).0 + } + + /// Returns whether `error` is one of the Win32 errors in `codes`. + fn is_error(error: &io::Error, codes: &[i32]) -> bool { + error + .raw_os_error() + .is_some_and(|code| codes.contains(&code)) + } + + /// Follows the links at `path` as `ntpath._readlink_deep` does, stopping at a path that is + /// not a link, at a loop, or at an error that ends resolution. + fn readlink_deep(path: &str) -> io::Result { + const STOP: [i32; 12] = [1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393]; + let mut path = path.to_owned(); + let mut seen = std::collections::HashSet::new(); + while seen.insert(normcase(&path)) { + let old = path.clone(); + match win32::read_link(Path::new(&path)) { + Ok(Some(target)) => { + path = target; + if !Flavor::Windows.is_absolute(&path) { + // A relative target resolves against the link's own directory, but + // only a symbolic link has one. + if !super::is_symlink(Path::new(&old)) { + path = old; + break; + } + path = windows_normpath(&Flavor::Windows.join(&dirname(&old), &path)); + } + } + // A reparse point that is not a link. + Ok(None) => break, + Err(error) if is_error(&error, &STOP) => break, + Err(error) => return Err(error), + } + } + Ok(path) + } + + /// Resolves as much of `path` as exists and appends the rest, as + /// `ntpath._getfinalpathname_nonstrict` does. + fn final_path_name_nonstrict(path: &str) -> io::Result { + const STOP: [i32; 16] = [ + 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1005, 1920, 1921, + ]; + const BY_NAME: [i32; 7] = [1, 5, 32, 50, 87, 1920, 1921]; + let mut path = path.to_owned(); + let mut tail = String::new(); + while !path.is_empty() { + let error = match win32::final_path_name(Path::new(&path)) { + Ok(resolved) => { + return Ok(if tail.is_empty() { + resolved + } else { + Flavor::Windows.join(&resolved, &tail) + }); + } + Err(error) if is_error(&error, &STOP) => error, + Err(error) => return Err(error), + }; + // Windows could not resolve the path: follow its links ourselves. + match readlink_deep(&path) { + Ok(target) if target != path => { + return Ok(if tail.is_empty() { + target + } else { + Flavor::Windows.join(&target, &tail) + }); + } + _ => {} + } + let (head, name) = if is_error(&error, &BY_NAME) { + // Read the real name of the file without opening it. + match win32::find_first_file_name(Path::new(&path)) { + Ok(name) => (Flavor::Windows.split(&path).0, name), + Err(_) => Flavor::Windows.split(&path), + } + } else { + Flavor::Windows.split(&path) + }; + path = head; + if !path.is_empty() && name.is_empty() { + return Ok(format!("{path}{tail}")); + } + tail = if tail.is_empty() { + name + } else { + Flavor::Windows.join(&name, &tail) + }; + } + Ok(tail) + } + + /// Returns `ntpath.realpath(path)` in its non-strict mode. + pub(super) fn realpath(path: &str) -> io::Result { + const PREFIX: &str = "\\\\?\\"; + const UNC_PREFIX: &str = "\\\\?\\UNC\\"; + let mut path = windows_normpath(path); + if normcase(&path) == "nul" { + return Ok("\\\\.\\NUL".to_owned()); + } + let had_prefix = path.starts_with(PREFIX); + if !had_prefix && !Flavor::Windows.is_absolute(&path) { + let directory = std::env::current_dir()?; + let directory = directory + .to_str() + .ok_or_else(|| super::not_unicode("the current directory"))?; + path = Flavor::Windows.join(directory, &path); + } + if path.contains('\0') { + // Python cannot pass the path to Windows and returns it as it is. + return Ok(windows_normpath(&path)); + } + let initial_error = match win32::final_path_name(Path::new(&path)) { + Ok(resolved) => { + path = resolved; + None + } + Err(error) => { + let code = error.raw_os_error(); + path = final_path_name_nonstrict(&path)?; + code + } + }; + if !had_prefix && path.starts_with(PREFIX) { + let stripped = match path.strip_prefix(UNC_PREFIX) { + Some(rest) => format!("\\\\{rest}"), + None => path[PREFIX.len()..].to_owned(), + }; + // Drop the prefix if the path without it resolves to the same path, or if neither + // exists for the same reason. + match win32::final_path_name(Path::new(&stripped)) { + Ok(resolved) if resolved == path => path = stripped, + Ok(_) => {} + Err(error) if error.raw_os_error() == initial_error.or(Some(0)) => { + path = stripped; + } + Err(_) => {} + } + } + Ok(path) + } +} + +#[cfg(not(any(unix, windows)))] +/// Hosts other than POSIX and Windows have no filesystem queries. +mod imp { + use std::io; + use std::path::Path; + + use super::Stat; + + fn unsupported() -> io::Error { + io::Error::new(io::ErrorKind::Unsupported, "unsupported host") + } + + pub(super) fn is_file(_path: &Path) -> bool { + false + } + + pub(super) fn is_dir(_path: &Path) -> bool { + false + } + + pub(super) fn is_symlink(_path: &Path) -> bool { + false + } + + pub(super) fn exists(_path: &Path) -> bool { + false + } + + pub(super) fn stat(_path: &Path) -> io::Result { + Err(unsupported()) + } + + #[cfg(feature = "cli")] + pub(super) fn lstat(_path: &Path) -> io::Result { + Err(unsupported()) + } + + pub(super) fn realpath(_path: &str) -> io::Result { + Err(unsupported()) + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use super::*; + + #[test] + fn queries_follow_links_except_islink() { + let directory = tempfile::tempdir().unwrap(); + let root = directory.path(); + let file = root.join("file"); + let folder = root.join("folder"); + fs::write(&file, b"x").unwrap(); + fs::create_dir(&folder).unwrap(); + assert!(is_file(&file) && !is_dir(&file) && !is_symlink(&file)); + assert!(is_dir(&folder) && !is_file(&folder) && !is_symlink(&folder)); + assert!(!exists(&root.join("missing")) && !is_file(&root.join("missing"))); + assert!(!is_file(Path::new("nul\0byte"))); + let link = root.join("link"); + #[cfg(unix)] + let linked = std::os::unix::fs::symlink(&file, &link); + #[cfg(windows)] + let linked = std::os::windows::fs::symlink_file(&file, &link); + if linked.is_ok() { + assert!(is_symlink(&link) && is_file(&link)); + assert_eq!( + stat(&link).unwrap().identity(), + stat(&file).unwrap().identity() + ); + } + assert_ne!( + stat(&file).unwrap().identity(), + stat(&folder).unwrap().identity() + ); + } + + #[test] + fn hard_links_share_an_identity() { + let directory = tempfile::tempdir().unwrap(); + let file = directory.path().join("file"); + let link = directory.path().join("hard"); + fs::write(&file, b"x").unwrap(); + fs::hard_link(&file, &link).unwrap(); + assert_eq!( + stat(&file).unwrap().identity(), + stat(&link).unwrap().identity() + ); + } + + #[test] + fn resolve_keeps_missing_components_and_follows_links() { + let directory = tempfile::tempdir().unwrap(); + let root = resolve(&PurePath::native(directory.path().to_str().unwrap())).unwrap(); + assert!(root.is_absolute()); + let target = root.join("target"); + fs::create_dir(target.as_str()).unwrap(); + assert_eq!(resolve(&root.join("target")).unwrap(), target); + assert_eq!( + resolve(&root.join("missing/deeper")).unwrap(), + root.join("missing").join("deeper") + ); + assert_eq!(resolve(&root.join("target/../target")).unwrap(), target); + let link = root.join("link"); + #[cfg(unix)] + let linked = std::os::unix::fs::symlink(target.as_str(), link.as_str()); + #[cfg(windows)] + let linked = std::os::windows::fs::symlink_dir(target.as_str(), link.as_str()); + if linked.is_ok() { + assert_eq!(resolve(&link).unwrap(), target); + assert_eq!(resolve(&link.join("child")).unwrap(), target.join("child")); + } + } + + #[test] + fn os_errors_read_as_python_formats_them() { + let error = fs::metadata("definitely/missing/path").unwrap_err(); + let message = os_error_message(&error, Some("missing")); + let label = if cfg!(windows) { + "[WinError 3]" + } else { + "[Errno 2]" + }; + assert!(message.starts_with(label), "{message}"); + assert!(message.ends_with(": 'missing'"), "{message}"); + assert!(!message.contains("os error"), "{message}"); + } +} diff --git a/nvx-rs/src/host/python/golden.rs b/nvx-rs/src/host/python/golden.rs new file mode 100644 index 000000000..45d73781e --- /dev/null +++ b/nvx-rs/src/host/python/golden.rs @@ -0,0 +1,314 @@ +//! Checks the Python semantics against `tests/fixtures/python.json`, which +//! `scripts/nvx_rs_fixtures.py` records from Python, and the helpers of the other golden tests. + +use std::fmt::Debug; + +use serde_json::{Value, json}; + +use super::builtins::{Int, is_space, repr, split, strip}; +use super::json::{Document, Loads, Node, NodeId, ReadError, read, syntax_error}; +use super::path::{Flavor, PurePath, windows_normpath}; +use super::text::{PyText, decode_utf8}; +use super::unicode::{DECIMAL_STARTS, NON_PRINTABLE}; +use super::uuid::parse_uuid; + +/// Returns the fixture `name` of `tests/fixtures`. +pub(crate) fn fixture(name: &str) -> Value { + let path = format!("{}/tests/fixtures/{name}", env!("CARGO_MANIFEST_DIR")); + let text = std::fs::read_to_string(&path).unwrap_or_else(|error| panic!("{path}: {error}")); + serde_json::from_str(&text).unwrap_or_else(|error| panic!("{path}: {error}")) +} + +/// Returns the cases of a fixture's section. +pub(crate) fn cases<'a>(fixture: &'a Value, section: &str) -> &'a [Value] { + let cases = fixture[section] + .as_array() + .unwrap_or_else(|| panic!("the fixture has no section {section}")); + assert!(!cases.is_empty(), "section {section} has no cases"); + cases +} + +/// Returns a string of a case. +pub(crate) fn text<'a>(case: &'a Value, key: &str) -> &'a str { + case[key] + .as_str() + .unwrap_or_else(|| panic!("{key} is not a string in {case}")) +} + +/// Returns a list of strings of a case. +pub(crate) fn texts(case: &Value, key: &str) -> Vec { + case[key] + .as_array() + .unwrap_or_else(|| panic!("{key} is not a list in {case}")) + .iter() + .map(|value| value.as_str().expect("a string").to_owned()) + .collect() +} + +/// Returns the flavor of a case. +pub(crate) fn flavor(case: &Value) -> Flavor { + match text(case, "flavor") { + "posix" => Flavor::Posix, + "windows" => Flavor::Windows, + other => panic!("unknown flavor {other}"), + } +} + +/// Returns the bytes of a hex string. +pub(crate) fn hex(text: &str) -> Vec { + (0..text.len()) + .step_by(2) + .map(|index| u8::from_str_radix(&text[index..index + 2], 16).expect("hex")) + .collect() +} + +/// Collects the cases on which the port and Python differ, to report them together. +pub(crate) struct Mismatches { + /// The section being checked. + section: String, + /// The number of cases checked. + checked: usize, + /// A description of each mismatch. + found: Vec, +} + +impl Mismatches { + /// Starts checking a section. + pub(crate) fn new(section: &str) -> Self { + Self { + section: section.to_owned(), + checked: 0, + found: Vec::new(), + } + } + + /// Records whether the port's `actual` value for `case` equals Python's `expected` value. + pub(crate) fn check(&mut self, case: &Value, expected: T, actual: T) { + self.checked += 1; + if expected != actual { + self.found.push(format!( + "{case}\n python: {expected:?}\n rust: {actual:?}" + )); + } + } + + /// Fails with the mismatches, if any. + pub(crate) fn finish(self) { + assert!(self.checked > 0, "{} checked no cases", self.section); + assert!( + self.found.is_empty(), + "{} of {} {} cases differ from Python:\n{}", + self.found.len(), + self.checked, + self.section, + self.found + .iter() + .take(20) + .cloned() + .collect::>() + .join("\n") + ); + } +} + +/// Returns the ranges of code points from `start` that satisfy `predicate`, as the generator +/// lists them. +fn ranges(start: u32, predicate: impl Fn(char) -> bool) -> Vec<(u32, u32)> { + let mut ranges: Vec<(u32, u32)> = Vec::new(); + for code in start..=0x10_FFFF { + // Python's predicates are false for surrogates, which no `char` holds. + if char::from_u32(code).is_some_and(&predicate) { + match ranges.last_mut() { + Some((_, last)) if *last + 1 == code => *last = code, + _ => ranges.push((code, code)), + } + } + } + ranges +} + +/// Returns a fixture's list of inclusive ranges. +fn fixture_ranges(value: &Value) -> Vec<(u32, u32)> { + serde_json::from_value(value.clone()).expect("ranges") +} + +#[test] +fn unicode_tables_match_python() { + let fixture = fixture("python.json"); + assert_eq!(ranges(0, is_space), fixture_ranges(&fixture["space"])); + let starts: Vec = serde_json::from_value(fixture["decimal_starts"].clone()).unwrap(); + assert_eq!(DECIMAL_STARTS, starts.as_slice()); + assert_eq!( + NON_PRINTABLE, + fixture_ranges(&fixture["non_printable"]).as_slice() + ); +} + +#[test] +fn strings_integers_and_uuids_match_python() { + let fixture = fixture("python.json"); + let mut mismatches = Mismatches::new("strings"); + for case in cases(&fixture, "strings") { + let value = text(case, "text"); + mismatches.check(case, text(case, "repr"), repr(value).as_str()); + mismatches.check( + case, + texts(case, "split"), + split(value).map(str::to_owned).collect(), + ); + mismatches.check(case, text(case, "strip"), strip(value)); + mismatches.check( + case, + case["uuid"].as_str().map(str::to_owned), + parse_uuid(value), + ); + let expected = match case.get("int") { + Some(int) => Ok(int.as_str().unwrap().to_owned()), + None => Err(text(case, "int_error").to_owned()), + }; + let actual = Int::parse(value) + .map(|int| int.to_string()) + .map_err(|error| error.message(value)); + mismatches.check(case, expected, actual); + } + mismatches.finish(); +} + +#[test] +fn pure_paths_match_pathlib() { + let fixture = fixture("python.json"); + let mut mismatches = Mismatches::new("paths"); + for case in cases(&fixture, "paths") { + let path = PurePath::new(flavor(case), text(case, "text")); + let parents: Vec = path + .parents() + .map(|parent| parent.as_str().to_owned()) + .collect(); + mismatches.check( + case, + ( + text(case, "str"), + texts(case, "parts"), + case["absolute"].as_bool().unwrap(), + text(case, "name"), + texts(case, "parents"), + ), + ( + path.as_str(), + path.parts(), + path.is_absolute(), + path.name(), + parents, + ), + ); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("joins"); + for case in cases(&fixture, "joins") { + let path = PurePath::new(flavor(case), text(case, "left")).join(text(case, "right")); + mismatches.check(case, text(case, "str"), path.as_str()); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("equality"); + for case in cases(&fixture, "equality") { + let left = PurePath::new(Flavor::Windows, text(case, "left")); + let right = PurePath::new(Flavor::Windows, text(case, "right")); + mismatches.check(case, case["equal"].as_bool().unwrap(), left == right); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("normpath"); + for case in cases(&fixture, "normpath") { + mismatches.check( + case, + text(case, "normpath"), + windows_normpath(text(case, "text")).as_str(), + ); + } + mismatches.finish(); +} + +/// Returns a string in the generator's tagged form. +fn tagged_string(string: &PyText) -> Value { + match string.as_str() { + Some(text) => json!(["str", text]), + None => json!(["code_points", string.code_points().collect::>()]), + } +} + +/// Returns a decoded value in the generator's tagged form, with the members of an object as a +/// Python `dict` holds them: in the order of their first keys, with the last values. +fn tagged(document: &Document, id: NodeId) -> Value { + match document.node(id) { + Node::Null => json!(["null"]), + Node::Bool(value) => json!(["bool", value]), + // `str(int(literal))`, which drops the sign of `-0`. + Node::Integer(literal) => json!(["int", Int::parse(literal).unwrap().to_string()]), + // Python's `NaN` is a positive quiet NaN. + Node::Float(value) if value.is_nan() && value.is_sign_positive() => { + json!(["float", "7ff8000000000000"]) + } + Node::Float(value) => json!(["float", format!("{:016x}", value.to_bits())]), + Node::String(string) => tagged_string(string), + Node::Array(items) => json!([ + "list", + items + .iter() + .map(|&item| tagged(document, item)) + .collect::>() + ]), + Node::Object(members) => { + let mut entries: Vec<(&PyText, NodeId)> = Vec::new(); + for (key, value) in members { + match entries.iter().position(|&(name, _)| name == key) { + Some(position) => entries[position].1 = *value, + None => entries.push((key, *value)), + } + } + json!([ + "dict", + entries + .iter() + .map(|&(key, value)| json!([tagged_string(key), tagged(document, value)])) + .collect::>() + ]) + } + } +} + +#[test] +fn json_documents_decode_as_python_decodes_them() { + let fixture = fixture("python.json"); + let mut mismatches = Mismatches::new("json"); + for case in cases(&fixture, "json") { + let expected = if let Some(value) = case.get("value") { + Ok(value.clone()) + } else if let Some(message) = case.get("decode_error") { + Err(("decode", message.as_str().unwrap().to_owned())) + } else { + Err(("value", text(case, "value_error").to_owned())) + }; + let source = text(case, "text"); + let actual = match read(source, &mut Loads) { + Ok(document) => Ok(tagged(&document, document.root())), + Err(ReadError::Syntax { message, offset }) => { + Err(("decode", syntax_error(source, message, offset))) + } + Err(ReadError::Hook(error)) => Err(("value", error.message(source))), + }; + mismatches.check(case, expected, actual); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("utf8"); + for case in cases(&fixture, "utf8") { + mismatches.check( + case, + case["error"].as_str().map(str::to_owned), + decode_utf8(&hex(text(case, "bytes"))).err(), + ); + } + mismatches.finish(); +} diff --git a/nvx-rs/src/host/python/json.rs b/nvx-rs/src/host/python/json.rs new file mode 100644 index 000000000..1bf82f765 --- /dev/null +++ b/nvx-rs/src/host/python/json.rs @@ -0,0 +1,845 @@ +//! A JSON reader that accepts exactly what Python's `json.loads` accepts, and refuses the rest as +//! Python refuses it. +//! +//! Python's decoder differs from strict JSON parsers such as `serde_json`: +//! +//! - it reads the constants `NaN`, `Infinity`, and `-Infinity` as floats, and numbers beyond the +//! range of a float as infinities; +//! - it reads integers of any length, which `parse_int` receives as written, although `int`, its +//! default, refuses more than 4300 digits; +//! - it keeps a `\ud800` escape without its pair as an unpaired surrogate; and +//! - it hands each object's members, duplicates included, to `object_pairs_hook` when the object +//! closes, and each integer to `parse_int` when it reads the integer, so the errors that these +//! hooks raise surface in document order. [`Hooks`] are their equivalents, and [`Loads`] are +//! those of `json.loads` without arguments. +//! +//! [`read`] ports the C scanner of CPython 3.14, `scan_once_unicode` and the functions that it +//! calls in `Modules/_json.c`, with the checks of `JSONDecoder.decode` around it, so a document +//! that it refuses fails with the message and at the position of the `JSONDecodeError` that +//! Python raises, which [`syntax_error`] formats as Python does. +//! +//! The reader keeps values in an arena and never recurses, so it reads documents nested to any +//! depth. Python's decoder recurses and raises `RecursionError` at a depth that depends on the +//! Python version and the stack, so documents nested that deep are the one input on which the two +//! differ. + +use super::builtins::{Int, IntError}; +use super::text::PyText; + +/// Index of a value in a [`Document`]. +pub(crate) type NodeId = usize; + +/// A JSON value, as Python's decoder represents it. +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum Node { + /// `null`, Python's `None`. + Null, + /// `true` or `false`. + Bool(bool), + /// An integer, as written, which is what `parse_int` receives. + Integer(Box), + /// A number with a fraction or an exponent, or one of `NaN`, `Infinity`, and `-Infinity`. + Float(f64), + /// A string. + String(PyText), + /// An array of values. + Array(Vec), + /// An object's members, in document order, including any duplicate names. + Object(Vec<(PyText, NodeId)>), +} + +/// A JSON document: its values and its top-level value. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct Document { + /// Every value of the document; containers refer to their elements by index. + nodes: Vec, + /// Index of the top-level value. + root: NodeId, +} + +impl Document { + /// Returns the top-level value. + pub(crate) fn root(&self) -> NodeId { + self.root + } + + /// Returns the value at `id`, which a container of this document holds or [`root`](Self::root) + /// returned. + pub(crate) fn node(&self, id: NodeId) -> &Node { + &self.nodes[id] + } + + /// Returns `value.get(name)` for the `dict` that Python builds from the object at `id`, in + /// which the last of repeated names wins, or `None` if the value at `id` is not an object. + pub(crate) fn get(&self, id: NodeId, name: &str) -> Option<&Node> { + let Node::Object(members) = self.node(id) else { + return None; + }; + members + .iter() + .rev() + .find(|(member, _)| member.as_bytes() == name.as_bytes()) + .map(|&(_, value)| self.node(value)) + } + + /// Returns the document that Python's decoder reads from the JSON text of `value`. + /// + /// A number with a fraction or an exponent is a float, and any other an integer, as in that + /// text. The value has no repeated field to check, but its integers keep any length. + pub(crate) fn from_value(value: &serde_json::Value) -> Self { + use serde_json::Value; + + let mut nodes = vec![Node::Null]; + // Each value and the index reserved for it, which its container already holds. + let mut pending = vec![(value, 0)]; + while let Some((value, id)) = pending.pop() { + let mut reserve = |value| { + nodes.push(Node::Null); + let id = nodes.len() - 1; + pending.push((value, id)); + id + }; + let node = match value { + Value::Null => Node::Null, + Value::Bool(value) => Node::Bool(*value), + Value::Number(number) => { + // `serde_json` writes integers as digits, and other numbers with a fraction + // or an exponent, also with its `arbitrary_precision` feature. + let literal = number.to_string(); + if literal + .bytes() + .all(|byte| byte.is_ascii_digit() || byte == b'-') + { + Node::Integer(literal.into()) + } else { + Node::Float(number.as_f64().unwrap_or(f64::NAN)) + } + } + Value::String(text) => Node::String(PyText::from(text.as_str())), + Value::Array(elements) => Node::Array(elements.iter().map(&mut reserve).collect()), + Value::Object(members) => Node::Object( + members + .iter() + .map(|(name, value)| (PyText::from(name.as_str()), reserve(value))) + .collect(), + ), + }; + nodes[id] = node; + } + Self { nodes, root: 0 } + } +} + +/// Checks that run while a document is read, as Python's `object_pairs_hook` and `parse_int` +/// do. Their errors end the read. +pub(crate) trait Hooks { + /// Error of a failed check. + type Error; + + /// Runs when an object closes, with its members in document order. + fn object(&mut self, members: &[(PyText, NodeId)]) -> Result<(), Self::Error> { + let _ = members; + Ok(()) + } + + /// Runs when an integer is read, with the integer as written, such as `-12`. + fn integer(&mut self, literal: &str) -> Result<(), Self::Error> { + let _ = literal; + Ok(()) + } +} + +/// The hooks of `json.loads` without arguments: `int`, the default `parse_int`, refuses an +/// integer of more than 4300 digits with a `ValueError` that [`IntError::message`] gives. +pub(crate) struct Loads; + +impl Hooks for Loads { + type Error = IntError; + + fn integer(&mut self, literal: &str) -> Result<(), IntError> { + Int::parse(literal).map(drop) + } +} + +/// Why [`read`] failed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum ReadError { + /// The text is not a document that Python's decoder accepts: Python raises a + /// `JSONDecodeError` with this message, at the character at this byte offset. + Syntax { + /// The message of the `JSONDecodeError`, such as `Expecting value`. + message: &'static str, + /// Byte offset in the text of the character at which Python reports the error. + offset: usize, + }, + /// A hook failed. + Hook(E), +} + +/// Returns `str()` of the `JSONDecodeError` that Python raises with `message` at byte `offset` +/// of `text`: the message, the line and column of the position, and the position itself, in +/// characters, such as `Expecting value: line 1 column 2 (char 1)`. +pub(crate) fn syntax_error(text: &str, message: &str, offset: usize) -> String { + let before = &text[..offset]; + let position = before.chars().count(); + let line = before.matches('\n').count() + 1; + let column = match before.rfind('\n') { + Some(newline) => before[newline + 1..].chars().count() + 1, + None => position + 1, + }; + format!("{message}: line {line} column {column} (char {position})") +} + +/// Reads `text` as `json.loads(text)` does, running `hooks` as the document is read. +pub(crate) fn read(text: &str, hooks: &mut H) -> Result> { + // Python refuses a string that starts with a byte-order mark before decoding it. + if text.starts_with('\u{FEFF}') { + return Err(ReadError::Syntax { + message: "Unexpected UTF-8 BOM (decode using utf-8-sig)", + offset: 0, + }); + } + let mut scanner = Scanner { + text, + bytes: text.as_bytes(), + nodes: Vec::new(), + hooks, + }; + let start = scanner.skip_whitespace(0); + let (root, end) = scanner.scan(start).map_err(|failure| match failure { + Failure::Stop(offset) => ReadError::Syntax { + message: "Expecting value", + offset, + }, + Failure::Decode(message, offset) => ReadError::Syntax { message, offset }, + Failure::Hook(error) => ReadError::Hook(error), + })?; + let end = scanner.skip_whitespace(end); + if end != text.len() { + return Err(ReadError::Syntax { + message: "Extra data", + offset: end, + }); + } + Ok(Document { + nodes: scanner.nodes, + root, + }) +} + +/// Why scanning stopped. +enum Failure { + /// No value starts at this byte offset: the scanner's `StopIteration`. + Stop(usize), + /// A `JSONDecodeError` with this message at this byte offset. + Decode(&'static str, usize), + /// A hook failed. + Hook(E), +} + +/// An array or object whose values are being scanned. +enum Frame { + /// An array, by its node. + Array(NodeId), + /// An object, by its node, with the name of the member whose value is being scanned. + Object(NodeId, PyText), +} + +/// The state of [`read`]. +struct Scanner<'a, H> { + /// The text being read. + text: &'a str, + /// The bytes of `text`. + bytes: &'a [u8], + /// The values read so far. + nodes: Vec, + /// The checks to run. + hooks: &'a mut H, +} + +impl Scanner<'_, H> { + /// Returns the byte at `index`, if any. + fn at(&self, index: usize) -> Option { + self.bytes.get(index).copied() + } + + /// Returns the offset of the first byte from `index` that is not whitespace that Python's + /// decoder skips: a space, a tab, a line feed, or a carriage return. + fn skip_whitespace(&self, mut index: usize) -> usize { + while matches!(self.at(index), Some(b' ' | b'\t' | b'\n' | b'\r')) { + index += 1; + } + index + } + + /// Adds a value to the document and returns its index. + fn push(&mut self, node: Node) -> NodeId { + self.nodes.push(node); + self.nodes.len() - 1 + } + + /// Runs the object hook on the members of the object at `node`, which has just closed. + fn close_object(&mut self, node: NodeId) -> Result<(), Failure> { + if let Node::Object(members) = &self.nodes[node] { + self.hooks.object(members).map_err(Failure::Hook)?; + } + Ok(()) + } + + /// Scans the value at `index`, returning its node and the offset after it, as `scan_once` + /// does, without recursion. + fn scan(&mut self, mut index: usize) -> Result<(NodeId, usize), Failure> { + let mut frames: Vec = Vec::new(); + loop { + // Scan one value, or open an array or object. + let mut value = None; + let Some(first) = self.at(index) else { + return Err(Failure::Stop(index)); + }; + match first { + b'"' => { + let (string, next) = self.scan_string(index + 1)?; + value = Some(self.push(Node::String(string))); + index = next; + } + b'[' => { + let node = self.push(Node::Array(Vec::new())); + index = self.skip_whitespace(index + 1); + if self.at(index) == Some(b']') { + value = Some(node); + index += 1; + } else { + frames.push(Frame::Array(node)); + } + } + b'{' => { + let node = self.push(Node::Object(Vec::new())); + index = self.skip_whitespace(index + 1); + if self.at(index) == Some(b'}') { + self.close_object(node)?; + value = Some(node); + index += 1; + } else { + let (name, next) = self.scan_name(index)?; + frames.push(Frame::Object(node, name)); + index = next; + } + } + _ => { + let (node, next) = self.scan_scalar(index)?; + value = Some(self.push(node)); + index = next; + } + } + // Attach finished values to their arrays and objects. + while let Some(finished) = value.take() { + let Some(frame) = frames.last_mut() else { + return Ok((finished, index)); + }; + index = self.skip_whitespace(index); + let (node, closing) = match frame { + Frame::Array(node) => (*node, b']'), + Frame::Object(node, _) => (*node, b'}'), + }; + match (&mut self.nodes[node], frame) { + (Node::Array(values), Frame::Array(_)) => values.push(finished), + (Node::Object(members), Frame::Object(_, name)) => { + members.push((std::mem::take(name), finished)); + } + _ => unreachable!("frames refer to their own arrays and objects"), + } + if self.at(index) == Some(closing) { + frames.pop(); + if closing == b'}' { + self.close_object(node)?; + } + value = Some(node); + index += 1; + continue; + } + if self.at(index) != Some(b',') { + return Err(Failure::Decode("Expecting ',' delimiter", index)); + } + let comma = index; + index = self.skip_whitespace(index + 1); + if self.at(index) == Some(closing) { + return Err(Failure::Decode( + if closing == b']' { + "Illegal trailing comma before end of array" + } else { + "Illegal trailing comma before end of object" + }, + comma, + )); + } + if let Some(Frame::Object(_, name)) = frames.last_mut() { + let (next_name, next) = self.scan_name(index)?; + *name = next_name; + index = next; + } + } + } + } + + /// Scans a member's name at `index`, its colon, and the whitespace after them, returning the + /// name and the offset of its value. + fn scan_name(&self, index: usize) -> Result<(PyText, usize), Failure> { + if self.at(index) != Some(b'"') { + return Err(Failure::Decode( + "Expecting property name enclosed in double quotes", + index, + )); + } + let (name, next) = self.scan_string(index + 1)?; + let index = self.skip_whitespace(next); + if self.at(index) != Some(b':') { + return Err(Failure::Decode("Expecting ':' delimiter", index)); + } + Ok((name, self.skip_whitespace(index + 1))) + } + + /// Scans a constant or a number at `index`. Python matches the constants as prefixes, so + /// whatever follows one is scanned separately. + fn scan_scalar(&mut self, index: usize) -> Result<(Node, usize), Failure> { + for (literal, node) in [ + ("null", Node::Null), + ("true", Node::Bool(true)), + ("false", Node::Bool(false)), + ("NaN", Node::Float(f64::NAN)), + ("Infinity", Node::Float(f64::INFINITY)), + ("-Infinity", Node::Float(f64::NEG_INFINITY)), + ] { + if self.bytes[index..].starts_with(literal.as_bytes()) { + return Ok((node, index + literal.len())); + } + } + self.scan_number(index) + } + + /// Scans a number at `start` as `_match_number_unicode` does: an optional `-`, `0` or digits + /// without a leading zero, a fraction if a digit follows the point, and an exponent if a + /// digit follows the `e` and its optional sign. A number with a fraction or an exponent is a + /// float, and any other is an integer, which the integer hook receives. + fn scan_number(&mut self, start: usize) -> Result<(Node, usize), Failure> { + let digit = |index: usize| self.at(index).is_some_and(|byte| byte.is_ascii_digit()); + let last = self.bytes.len().saturating_sub(1); + let mut index = start; + if self.at(index) == Some(b'-') { + index += 1; + if index > last { + return Err(Failure::Stop(start)); + } + } + match self.at(index) { + Some(b'1'..=b'9') => { + index += 1; + while digit(index) { + index += 1; + } + } + Some(b'0') => index += 1, + _ => return Err(Failure::Stop(start)), + } + let mut float = false; + if index < last && self.at(index) == Some(b'.') && digit(index + 1) { + float = true; + index += 2; + while digit(index) { + index += 1; + } + } + if index < last && matches!(self.at(index), Some(b'e' | b'E')) { + let exponent = index; + index += 1; + if index < last && matches!(self.at(index), Some(b'-' | b'+')) { + index += 1; + } + while digit(index) { + index += 1; + } + if digit(index - 1) { + float = true; + } else { + index = exponent; + } + } + let literal = &self.text[start..index]; + if float { + // Like Python's `float`, Rust rounds correctly and overflows to an infinity, so any + // literal of this form parses. + return Ok((Node::Float(literal.parse().unwrap_or(f64::NAN)), index)); + } + self.hooks.integer(literal).map_err(Failure::Hook)?; + Ok((Node::Integer(literal.into()), index)) + } + + /// Scans a string whose opening quote precedes `end`, as `scanstring_unicode` does in strict + /// mode, returning it and the offset after its closing quote: control characters are refused, + /// and a `\u` escape of a high surrogate followed by a `\u` escape of a low surrogate is one + /// character, while any other surrogate escape stays an unpaired surrogate. + fn scan_string(&self, mut end: usize) -> Result<(PyText, usize), Failure> { + let length = self.bytes.len(); + let begin = end - 1; + let mut decoded = PyText::new(); + loop { + // Find the end of the string or the next escape. + let mut next = end; + let mut found = None; + while next < length { + let byte = self.bytes[next]; + if byte == b'"' || byte == b'\\' { + found = Some(byte); + break; + } + if byte <= 0x1F { + return Err(Failure::Decode("Invalid control character at", next)); + } + next += 1; + } + let Some(terminator) = found else { + return Err(Failure::Decode("Unterminated string starting at", begin)); + }; + decoded.push_str(&self.text[end..next]); + next += 1; + if terminator == b'"' { + return Ok((decoded, next)); + } + let Some(escape) = self.at(next) else { + return Err(Failure::Decode("Unterminated string starting at", begin)); + }; + if escape != b'u' { + end = next + 1; + let character = match escape { + b'"' | b'\\' | b'/' => char::from(escape), + b'b' => '\u{8}', + b'f' => '\u{c}', + b'n' => '\n', + b'r' => '\r', + b't' => '\t', + _ => return Err(Failure::Decode("Invalid \\escape", end - 2)), + }; + decoded.push_code_point(u32::from(character)); + continue; + } + next += 1; + end = next + 4; + if end > length { + return Err(Failure::Decode("Invalid \\uXXXX escape", next - 1)); + } + let mut code_point = self + .hex4(next) + .ok_or(Failure::Decode("Invalid \\uXXXX escape", end - 5))?; + // A high surrogate and a low-surrogate escape after it form one character. + if (0xD800..=0xDBFF).contains(&code_point) + && end + 6 < length + && self.at(end) == Some(b'\\') + && self.at(end + 1) == Some(b'u') + { + let low = self + .hex4(end + 2) + .ok_or(Failure::Decode("Invalid \\uXXXX escape", end + 1))?; + if (0xDC00..=0xDFFF).contains(&low) { + code_point = 0x10000 + (((code_point - 0xD800) << 10) | (low - 0xDC00)); + end += 6; + } + } + decoded.push_code_point(code_point); + } + } + + /// Returns the value of the four hexadecimal digits at `index`, of either case. + fn hex4(&self, index: usize) -> Option { + self.bytes + .get(index..index + 4)? + .iter() + .try_fold(0, |value, &digit| { + Some((value << 4) | char::from(digit).to_digit(16)?) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Hooks that refuse duplicate names and integers of more than 64 digits, as the egress + /// policy reader does. + struct Strict; + + impl Hooks for Strict { + type Error = String; + + fn object(&mut self, members: &[(PyText, NodeId)]) -> Result<(), String> { + let mut seen = std::collections::HashSet::new(); + for (name, _) in members { + if !seen.insert(name) { + return Err(format!("duplicate {}", name.render())); + } + } + Ok(()) + } + + fn integer(&mut self, literal: &str) -> Result<(), String> { + if literal.trim_start_matches('-').len() > 64 { + return Err("long integer".to_owned()); + } + Ok(()) + } + } + + /// Reads `text` with [`Strict`] hooks and describes the result. + fn describe(text: &str) -> String { + match read(text, &mut Strict) { + Ok(document) => render(&document, document.root()), + Err(ReadError::Syntax { .. }) => "syntax".to_owned(), + Err(ReadError::Hook(message)) => message, + } + } + + /// Renders a value compactly, with the type of each scalar. + fn render(document: &Document, id: NodeId) -> String { + match document.node(id) { + Node::Null => "null".to_owned(), + Node::Bool(value) => value.to_string(), + Node::Integer(literal) => format!("int:{literal}"), + Node::Float(value) => format!("float:{value:?}"), + Node::String(text) => format!("str:{}", text.render()), + Node::Array(elements) => format!( + "[{}]", + elements + .iter() + .map(|&element| render(document, element)) + .collect::>() + .join(",") + ), + Node::Object(members) => format!( + "{{{}}}", + members + .iter() + .map(|(name, value)| format!("{}={}", name.render(), render(document, *value))) + .collect::>() + .join(",") + ), + } + } + + #[test] + fn values_read_as_python_reads_them() { + for (text, expected) in [ + ( + " \t\n\r{\"a\": [1, -0, 2.5, 1e5, 1E+5, 1e-400, 1e400]}\n", + "{a=[int:1,int:-0,float:2.5,float:100000.0,float:100000.0,float:0.0,float:inf]}", + ), + ( + "[NaN,Infinity,-Infinity]", + "[float:NaN,float:inf,float:-inf]", + ), + ("[true,false,null]", "[true,false,null]"), + ( + "\"\\u0031\\/\\b\\f\\n\\r\\t\\\"\\\\\"", + "str:1/\u{8}\u{c}\n\r\t\"\\", + ), + ("\"\\ud83d\\ude00\"", "str:\u{1F600}"), + ("\"\\ud800\"", "str:\\ud800"), + ("\"\\udc00\\ud800\"", "str:\\udc00\\ud800"), + ("\"\\ud800\\u0041\"", "str:\\ud800A"), + ("\"\\ud800x\\udc00\"", "str:\\ud800x\\udc00"), + ("\"\u{7F}é\u{2028}\"", "str:\u{7F}é\u{2028}"), + ("{}", "{}"), + ("[]", "[]"), + ("[[[]]]", "[[[]]]"), + ("{\"b\":1,\"a\":{\"c\":[]}}", "{b=int:1,a={c=[]}}"), + ("-0.0", "float:-0.0"), + ("0", "int:0"), + ] { + assert_eq!(describe(text), expected, "{text:?}"); + } + } + + #[test] + fn malformed_documents_are_refused() { + for text in [ + "", + " ", + "\u{FEFF}{}", + "{} x", + "{}\u{C}", + "[1,]", + "{\"a\":1,}", + "[01]", + "[1.]", + "[.5]", + "[1e]", + "[1e+]", + "[-]", + "[-a]", + "[+1]", + "[nul]", + "[Nan]", + "[-Inf]", + "[inf]", + "\"a", + "\"a\\", + "\"\\x\"", + "\"\\u12\"", + "\"\\u12g4\"", + "\"\\ud800\\u12g4\"", + "\"tab\there\"", + "\"\u{0}\"", + "{\"a\" 1}", + "{a:1}", + "{'a':1}", + "[1 2]", + "nullx", + "truefalse", + "[\"a\"\"b\"]", + "{\"a\":1 \"b\":2}", + "[", + "{", + "{\"a\":", + "[1,", + ] { + assert_eq!(describe(text), "syntax", "{text:?}"); + } + } + + #[test] + fn hooks_run_in_document_order() { + let long = "9".repeat(65); + // The duplicate name is found when its object closes, after the long integer. + assert_eq!( + describe(&format!("{{\"a\":1,\"a\":2,\"b\":{long}}}")), + "long integer" + ); + // An inner object closes before the integer that follows it. + assert_eq!( + describe(&format!("{{\"a\":{{\"x\":1,\"x\":2}},\"b\":{long}}}")), + "duplicate x" + ); + // The object hook runs before trailing text is refused, and a syntax error before the + // object closes prevails. + assert_eq!(describe("{\"a\":1,\"a\":2} x"), "duplicate a"); + assert_eq!(describe("{\"a\":1,\"a\":2,}"), "syntax"); + // The first duplicate in document order is reported. + assert_eq!(describe("{\"b\":1,\"a\":2,\"a\":3,\"b\":4}"), "duplicate a"); + // The integer hook runs before what follows the integer is checked. + assert_eq!(describe(&format!("[{long}x]")), "long integer"); + assert_eq!( + describe(&format!("[-{}]", "9".repeat(64))), + format!("[int:-{}]", "9".repeat(64)) + ); + // Escaped names are compared after decoding. + assert_eq!(describe("{\"a\":1,\"\\u0061\":2}"), "duplicate a"); + assert_eq!( + describe("{\"\\ud800\":1,\"\\ud800\":2}"), + "duplicate \\ud800" + ); + } + + #[test] + fn deep_nesting_needs_no_recursion() { + let depth = 500_000; + let text = format!("{}{}", "[".repeat(depth), "]".repeat(depth)); + let document = read(&text, &mut Strict).unwrap(); + assert!( + matches!(document.node(document.root()), Node::Array(elements) if elements.len() == 1) + ); + let text = format!("{}1{}", "{\"a\":".repeat(depth), "}".repeat(depth)); + read(&text, &mut Strict).unwrap(); + } + + /// Reads `text` as `json.loads(text)` does and describes its error as Python prints it. + fn error(text: &str) -> String { + match read(text, &mut Loads) { + Ok(_) => panic!("{text:?} was read"), + Err(ReadError::Syntax { message, offset }) => syntax_error(text, message, offset), + Err(ReadError::Hook(error)) => error.message(text), + } + } + + #[test] + fn documents_read_as_json_loads_reads_them() { + let text = " [\"a\", 1, -2.5e1, NaN, -Infinity, null, true, {\"k\": [], \"k\": {}}] "; + let document = read(text, &mut Loads).unwrap(); + let Node::Array(values) = document.node(document.root()) else { + panic!("not an array"); + }; + let nodes: Vec<&Node> = values.iter().map(|&id| document.node(id)).collect(); + assert_eq!(nodes[0], &Node::String(PyText::from("a"))); + assert_eq!(nodes[1], &Node::Integer("1".into())); + assert_eq!(nodes[2], &Node::Float(-25.0)); + assert!(matches!(nodes[3], Node::Float(value) if value.is_nan())); + assert_eq!(nodes[4], &Node::Float(f64::NEG_INFINITY)); + assert_eq!(nodes[5], &Node::Null); + assert_eq!(nodes[6], &Node::Bool(true)); + // The last of repeated names wins, as in the `dict` that Python builds. + assert_eq!( + document.get(values[7], "k"), + Some(&Node::Object(Vec::new())) + ); + assert_eq!(document.get(values[7], "missing"), None); + assert_eq!(document.get(values[0], "k"), None); + } + + #[test] + fn errors_read_as_python_reports_them() { + for (text, expected) in [ + ("", "Expecting value: line 1 column 1 (char 0)"), + ("[", "Expecting value: line 1 column 2 (char 1)"), + ("[1", "Expecting ',' delimiter: line 1 column 3 (char 2)"), + ( + "[1,]", + "Illegal trailing comma before end of array: line 1 column 3 (char 2)", + ), + ( + "{\"a\":1,}", + "Illegal trailing comma before end of object: line 1 column 7 (char 6)", + ), + ( + "{1:2}", + "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)", + ), + ( + "{\"a\" 1}", + "Expecting ':' delimiter: line 1 column 6 (char 5)", + ), + ( + "\"abc", + "Unterminated string starting at: line 1 column 1 (char 0)", + ), + ( + "\"a\tb\"", + "Invalid control character at: line 1 column 3 (char 2)", + ), + ("\"\\x\"", "Invalid \\escape: line 1 column 2 (char 1)"), + ( + "\"\\u12\"", + "Invalid \\uXXXX escape: line 1 column 3 (char 2)", + ), + ("[]\n x", "Extra data: line 2 column 2 (char 4)"), + ( + "\u{feff}[]", + "Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)", + ), + ("-", "Expecting value: line 1 column 1 (char 0)"), + ("01", "Extra data: line 1 column 2 (char 1)"), + // Positions count characters, not bytes. + ( + "[\"é\" x]", + "Expecting ',' delimiter: line 1 column 6 (char 5)", + ), + ( + "{\"\u{1F600}\":\n 1 2}", + "Expecting ',' delimiter: line 2 column 4 (char 9)", + ), + ( + &format!("[{}]", "1".repeat(4301)), + "Exceeds the limit (4300 digits) for integer string conversion: value has 4301 \ + digits; use sys.set_int_max_str_digits() to increase the limit", + ), + ] { + assert_eq!(error(text), expected, "{text:?}"); + } + assert!(read(&format!("[-{}]", "1".repeat(4300)), &mut Loads).is_ok()); + } +} diff --git a/nvx-rs/src/host/python/mod.rs b/nvx-rs/src/host/python/mod.rs new file mode 100644 index 000000000..e015a561b --- /dev/null +++ b/nvx-rs/src/host/python/mod.rs @@ -0,0 +1,57 @@ +//! Python semantics that the faithful ports of `scripts/nvx.py` tools need. +//! +//! Commands that `nvx.py` delegates to `nvx-rs` keep the behavior of the Python code they +//! replace, which depends on how Python reads JSON and integers, holds and prints strings and +//! paths, and queries the filesystem. These modules implement what the ports rely on, after +//! CPython 3.14: +//! +//! - [`text`]: Python strings, which may hold unpaired surrogates, how Python prints them, and +//! the errors of its UTF-8 codec. +//! - [`builtins`]: `str.isspace`, `str.split()`, `str.strip()`, `repr` of a `str`, `int`, and +//! `float`. +//! - [`json`]: a JSON reader that accepts what `json.loads` accepts, with the equivalents of its +//! `object_pairs_hook` and `parse_int` hooks, and that fails with its messages. +//! - [`dumps`]: `json.dumps(value, indent=2, sort_keys=True)`, with which `nvx.py` writes its +//! state files. +//! - [`path`]: `str(pathlib.Path(...))`, which Python messages show, the path that Python opens +//! for it, and the pure paths of `pathlib` with the `os.path` operations that they use. +//! - [`uuid`]: the UUIDs that `uuid.UUID` reads. +//! - [`fs`]: the filesystem queries of `os`, `os.path`, and `pathlib.Path`, among them +//! `Path.resolve()` and the file identities of `os.stat`, `os.lstat`, and `os.fstat`. +//! - [`shell`]: `subprocess.list2cmdline` and `shlex.join`, with which `nvx.py` prints the +//! commands that it runs. +//! - [`value`]: `str()`, `int()`, and `==` of the values that `json.loads` returns, as `nvx.py` +//! applies them to its state files. +//! +//! Character properties come from the Unicode 16.0.0 database of Python 3.14, in [`unicode`], +//! which `scripts/nvx_rs_fixtures.py` generates. The same script records what Python does with +//! fixed and seeded random inputs in `tests/fixtures`, and the golden tests compare these +//! implementations, and the ports that use them, with those records. Python decodes +//! operating-system strings that are not valid Unicode into surrogates: [`path`] prints such +//! paths as Python does, and the filesystem queries refuse them. + +pub(crate) mod builtins; +#[cfg(feature = "cli")] +pub(crate) mod dumps; +pub(crate) mod fs; +#[cfg(test)] +pub(crate) mod golden; +pub(crate) mod json; +pub(crate) mod path; +#[cfg(feature = "cli")] +pub(crate) mod shell; +pub(crate) mod text; +pub(crate) mod unicode; +pub(crate) mod uuid; +#[cfg(feature = "cli")] +pub(crate) mod value; +#[cfg(windows)] +mod windows; + +/// Returns the error for an operating-system string that is not valid Unicode. +pub(crate) fn not_unicode(what: &str) -> std::io::Error { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("{what} is not valid Unicode"), + ) +} diff --git a/nvx-rs/src/host/python/path.rs b/nvx-rs/src/host/python/path.rs new file mode 100644 index 000000000..19ed8ceea --- /dev/null +++ b/nvx-rs/src/host/python/path.rs @@ -0,0 +1,925 @@ +//! Paths as Python's `pathlib` and `os.path` hold, print, and open them. +//! +//! Python messages name files by `str(path)` for a `pathlib.Path`, which normalizes the path it +//! was given: it drops empty and `.` components, and on Windows turns `/` into `\` and gives UNC +//! shares a root. Python also opens that string, not the one it was given, so `config.json/` +//! opens `config.json`. [`python_path`] reproduces both for this host's flavor of paths, for any +//! host path, Unicode or not. +//! +//! A [`PurePath`] holds a Unicode path of either [`Flavor`], as `PurePosixPath` or +//! `PureWindowsPath` does, with its parts, parents, joins, and case-insensitive comparison on +//! Windows. [`Flavor`] has the `os.path` operations of `posixpath` and `ntpath` that the ports +//! use, `splitroot`, `join`, `isabs`, and `split`, and [`windows_normpath`] is `ntpath.normpath`. +//! One parser serves both: [`python_path`] and [`PurePath`] read paths alike. +//! +//! Windows paths follow CPython 3.14 on Windows, whose `ntpath.splitroot` and `ntpath.normpath` +//! are C functions that index UTF-16 code units: a character outside the Basic Multilingual Plane +//! followed by `:` is no drive there. Python's own `ntpath`, which `PureWindowsPath` uses on other +//! hosts, indexes code points instead, which differs only for such a character. + +use std::fmt; +use std::path::{Path, PathBuf}; + +/// A path as a `pathlib.Path` of this host holds it. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct PythonPath { + /// `str(path)` as Python prints it to a UTF-8 stream, which escapes surrogates as `\udXXX`. + pub(crate) display: String, + /// The path that Python opens: `os.fspath(path)`. + pub(crate) native: PathBuf, +} + +/// Returns `path` as Python's `pathlib.Path(path)` prints and opens it. +/// +/// On Unix, Python decodes bytes that are not UTF-8 into surrogates and prints each as `\udcXX`; +/// on Windows, unpaired surrogates print as `\udXXX`. +pub(crate) fn python_path(path: &Path) -> PythonPath { + #[cfg(windows)] + { + use std::ffi::OsString; + use std::os::windows::ffi::{OsStrExt, OsStringExt}; + + let units: Vec = path.as_os_str().encode_wide().collect(); + let normalized = windows_str(&units); + PythonPath { + display: render_utf16(&normalized), + native: PathBuf::from(OsString::from_wide(&normalized)), + } + } + #[cfg(unix)] + { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let normalized = posix_str(path.as_os_str().as_bytes()); + PythonPath { + display: render_surrogateescape(&normalized), + native: PathBuf::from(OsStr::from_bytes(&normalized)), + } + } + #[cfg(not(any(unix, windows)))] + { + let normalized = posix_str(path.to_string_lossy().as_bytes()); + let display = String::from_utf8_lossy(&normalized).into_owned(); + PythonPath { + native: PathBuf::from(&display), + display, + } + } +} + +/// Returns `str(PurePosixPath(path))` for a path given as bytes, which Python sees as the +/// characters that they decode to. +/// +/// One leading `/`, or three or more, are a root of `/`, and exactly two are a root of `//`; +/// empty and `.` components are dropped, and an empty result is `.`. +#[cfg(any(not(windows), test))] +pub(crate) fn posix_str(path: &[u8]) -> Vec { + let (root, tail) = parse_posix(path); + format_posix(root, &tail) +} + +/// Returns `str(PureWindowsPath(path))` on Windows for a path given as UTF-16 code units, as +/// Python 3.14 computes it with `nt._path_splitroot_ex`, which indexes UTF-16 code units. +/// +/// `/` becomes `\`; a drive is a letter or other code unit and a colon, a UNC share +/// (`\\server\share`, `\\?\UNC\server\share`), or a device (`\\.\device`, `\\?\device`); UNC +/// shares gain a root; empty and `.` components are dropped; a relative path whose first +/// component looks like a drive gains a `.\` prefix; and an empty result is `.`. +#[cfg(any(windows, test))] +pub(crate) fn windows_str(path: &[u16]) -> Vec { + let (drive, root, tail) = parse_windows(path); + format_windows(&drive, &root, &tail) +} + +/// The separator of Windows paths, `\`. +const SEPARATOR: u16 = b'\\' as u16; + +/// Returns whether a code unit separates the components of a Windows path: `\` or `/`. +fn is_windows_separator(unit: u16) -> bool { + unit == SEPARATOR || unit == u16::from(b'/') +} + +/// Returns the length of the root of a POSIX path, as `posixpath.splitroot` finds it: exactly two +/// leading slashes are a root of their own, and one, or three or more, a root of `/`. +fn posix_root_length(path: &[u8]) -> usize { + match path { + [b'/', b'/', b'/', ..] => 1, + [b'/', b'/', ..] => 2, + [b'/', ..] => 1, + _ => 0, + } +} + +/// Returns the root and the components of a POSIX path, as `pathlib` parses it: empty and `.` +/// components are dropped. +fn parse_posix(path: &[u8]) -> (&[u8], Vec<&[u8]>) { + let root = posix_root_length(path); + let tail = path[root..] + .split(|&byte| byte == b'/') + .filter(|part| !part.is_empty() && *part != b".") + .collect(); + (&path[..root], tail) +} + +/// Returns `str()` of the POSIX path of a root and components, as `pathlib` formats it. +fn format_posix(root: &[u8], tail: &[&[u8]]) -> Vec { + let mut result = root.to_vec(); + for (index, part) in tail.iter().enumerate() { + if index > 0 { + result.push(b'/'); + } + result.extend_from_slice(part); + } + if result.is_empty() { + result.push(b'.'); + } + result +} + +/// Returns the drive, the root, and the components of a Windows path given as UTF-16 code units, +/// as `pathlib` parses it: `/` becomes `\`, a UNC share gains a root, and empty and `.` +/// components are dropped. +fn parse_windows(path: &[u16]) -> (Vec, Vec, Vec>) { + let path: Vec = path + .iter() + .map(|&unit| { + if is_windows_separator(unit) { + SEPARATOR + } else { + unit + } + }) + .collect(); + let (drive_length, root_length) = windows_skip_root(&path); + let drive = &path[..drive_length]; + let mut root = path[drive_length..drive_length + root_length].to_vec(); + if root.is_empty() && drive.first() == Some(&SEPARATOR) && drive.last() != Some(&SEPARATOR) { + // `pathlib` gives a share, `\\server\share` or `\\?\UNC\server\share`, a root, but not a + // device such as `\\.\device` or `\\?\device`. Its `not in '?.'` test is a substring + // test, which the empty string also passes. + let parts: Vec<&[u16]> = drive.split(|&unit| unit == SEPARATOR).collect(); + let device = |part: &[u16]| { + part.is_empty() + || part == [u16::from(b'?')] + || part == [u16::from(b'.')] + || part == [u16::from(b'?'), u16::from(b'.')] + }; + if (parts.len() == 4 && !device(parts[2])) || parts.len() == 6 { + root = vec![SEPARATOR]; + } + } + let tail = path[drive_length + root_length..] + .split(|&unit| unit == SEPARATOR) + .filter(|part| !part.is_empty() && *part != [u16::from(b'.')]) + .map(<[u16]>::to_vec) + .collect(); + (drive.to_vec(), root, tail) +} + +/// Returns `str()` of the Windows path of a drive, a root, and components, as `pathlib` formats +/// it: a relative path whose first component would read as a drive gains a `.\` prefix. +fn format_windows(drive: &[u16], root: &[u16], tail: &[Vec]) -> Vec { + let mut result = Vec::new(); + if !drive.is_empty() || !root.is_empty() { + result.extend_from_slice(drive); + result.extend_from_slice(root); + } else if tail + .first() + .is_some_and(|first| windows_skip_root(first).0 > 0) + { + // A first component such as `c:foo` would read as a drive. + result.extend_from_slice(&[u16::from(b'.'), SEPARATOR]); + } + for (index, part) in tail.iter().enumerate() { + if index > 0 { + result.push(SEPARATOR); + } + result.extend_from_slice(part); + } + if result.is_empty() { + result.push(u16::from(b'.')); + } + result +} + +/// Returns the lengths in code units of the drive and the root of a Windows path, as CPython's +/// `_Py_skiproot` finds them on Windows, with `\` and `/` as separators. +fn windows_skip_root(path: &[u16]) -> (usize, usize) { + let unit = |index: usize| path.get(index).copied(); + let separator = |index: usize| unit(index).is_some_and(is_windows_separator); + let either = |index: usize, upper: u8, lower: u8| { + unit(index) == Some(u16::from(upper)) || unit(index) == Some(u16::from(lower)) + }; + if separator(0) { + if !separator(1) { + // A root without a drive, such as `\Windows`. + return (0, 1); + } + // A UNC share, such as `\\server\share` or `\\?\UNC\server\share`, or a device, such as + // `\\.\device` or `\\?\device`. + let unc = unit(2) == Some(u16::from(b'?')) + && separator(3) + && either(4, b'U', b'u') + && either(5, b'N', b'n') + && either(6, b'C', b'c') + && separator(7); + let mut index = if unc { 8 } else { 2 }; + while index < path.len() && !separator(index) { + index += 1; + } + if index >= path.len() { + return (path.len(), 0); + } + index += 1; + while index < path.len() && !separator(index) { + index += 1; + } + if index >= path.len() { + (path.len(), 0) + } else { + (index, 1) + } + } else if !path.is_empty() && unit(1) == Some(u16::from(b':')) { + // A drive, such as `X:`, and a root if a separator follows it. + (2, usize::from(separator(2))) + } else { + (0, 0) + } +} + +/// Returns UTF-16 code units as Python prints them to a UTF-8 stream: unpaired surrogates as +/// `\udXXX`. +#[cfg(any(windows, test))] +fn render_utf16(units: &[u16]) -> String { + char::decode_utf16(units.iter().copied()) + .map(|unit| match unit { + Ok(character) => character.to_string(), + Err(error) => format!("\\u{:04x}", error.unpaired_surrogate()), + }) + .collect() +} + +/// Returns bytes as Python prints the string that it decodes them to with the `surrogateescape` +/// error handler: valid UTF-8 as is, and each other byte as `\udcXX`. +#[cfg(any(unix, test))] +fn render_surrogateescape(bytes: &[u8]) -> String { + let mut rendered = String::with_capacity(bytes.len()); + for chunk in bytes.utf8_chunks() { + rendered.push_str(chunk.valid()); + for byte in chunk.invalid() { + rendered.push_str(&format!("\\udc{byte:02x}")); + } + } + rendered +} + +/// Returns the string of bytes that come from a Unicode path cut at ASCII separators. +fn from_utf8(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).into_owned() +} + +/// Returns the string of code units that come from a Unicode path cut at ASCII separators. +fn from_utf16(units: &[u16]) -> String { + String::from_utf16_lossy(units) +} + +/// Returns the byte offset in `text` of the character that starts at UTF-16 code unit `units`. +fn utf16_offset(text: &str, units: usize) -> usize { + let mut counted = 0; + for (offset, character) in text.char_indices() { + if counted >= units { + return offset; + } + counted += character.len_utf16(); + } + text.len() +} + +/// The two flavors of `pathlib` paths: `PurePosixPath` and `PureWindowsPath`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) enum Flavor { + /// `posixpath`, with `/` as its only separator. + Posix, + /// `ntpath`, with `\` as its separator and `/` as an alternative. + Windows, +} + +impl Flavor { + /// The flavor of `pathlib.Path` on this host. + pub(crate) const NATIVE: Self = if cfg!(windows) { + Self::Windows + } else { + Self::Posix + }; + + /// Returns whether `character` separates components: `os.sep` or `os.altsep`. + pub(crate) fn is_separator(self, character: char) -> bool { + character == '/' || (self == Self::Windows && character == '\\') + } + + /// Returns `os.path.splitroot(path)`: its drive, root, and the rest, as slices of `path`. + pub(crate) fn split_root(self, path: &str) -> (&str, &str, &str) { + let (drive, root) = match self { + Self::Posix => (0, posix_root_length(path.as_bytes())), + Self::Windows => { + let units: Vec = path.encode_utf16().collect(); + let (drive, root) = windows_skip_root(&units); + let drive_end = utf16_offset(path, drive); + (drive_end, utf16_offset(path, drive + root) - drive_end) + } + }; + ( + &path[..drive], + &path[drive..drive + root], + &path[drive + root..], + ) + } + + /// Returns `os.path.join(first, second)`. + pub(crate) fn join(self, first: &str, second: &str) -> String { + match self { + Self::Posix => { + if second.starts_with('/') || first.is_empty() { + second.to_owned() + } else if first.ends_with('/') { + format!("{first}{second}") + } else { + format!("{first}/{second}") + } + } + Self::Windows => windows_join(first, second), + } + } + + /// Returns `os.path.isabs(path)`. A Windows path is absolute with a drive and a root, or as + /// a UNC or device path; `ntpath.isabs` tests the code points of the path, even on Windows. + pub(crate) fn is_absolute(self, path: &str) -> bool { + match self { + Self::Posix => path.starts_with('/'), + Self::Windows => { + let start: String = path + .chars() + .take(3) + .map(|character| if character == '/' { '\\' } else { character }) + .collect(); + start.starts_with("\\\\") || start.chars().skip(1).eq([':', '\\']) + } + } + } + + /// Returns `os.path.split(path)`: the path without its last component, and that component. + #[cfg(any(windows, test))] + pub(crate) fn split(self, path: &str) -> (String, String) { + match self { + Self::Posix => { + let index = path.rfind('/').map_or(0, |index| index + 1); + let (head, tail) = path.split_at(index); + // A head of slashes only is kept whole. + let head = if head.chars().all(|character| character == '/') { + head + } else { + head.trim_end_matches('/') + }; + (head.to_owned(), tail.to_owned()) + } + Self::Windows => { + let (drive, root, rest) = self.split_root(path); + let index = rest.rfind(['\\', '/']).map_or(0, |index| index + 1); + let (head, tail) = rest.split_at(index); + ( + format!("{drive}{root}{}", head.trim_end_matches(['\\', '/'])), + tail.to_owned(), + ) + } + } + } +} + +/// Returns `ntpath.join(first, second)`. +fn windows_join(first: &str, second: &str) -> String { + let is_separator = |character: char| character == '\\' || character == '/'; + let (drive, root, path) = Flavor::Windows.split_root(first); + let (mut drive, mut root, mut path) = (drive.to_owned(), root.to_owned(), path.to_owned()); + let (second_drive, second_root, second_path) = Flavor::Windows.split_root(second); + if !second_root.is_empty() { + // The second path is absolute. + if !second_drive.is_empty() || drive.is_empty() { + second_drive.clone_into(&mut drive); + } + second_root.clone_into(&mut root); + second_path.clone_into(&mut path); + } else if !second_drive.is_empty() + && second_drive != drive + && second_drive.to_lowercase() != drive.to_lowercase() + { + // Another drive: the first path is dropped. + second_drive.clone_into(&mut drive); + second_root.clone_into(&mut root); + second_path.clone_into(&mut path); + } else { + if !second_drive.is_empty() { + // The same drive, in other case. + second_drive.clone_into(&mut drive); + } + if path.chars().last().is_some_and(|last| !is_separator(last)) { + path.push('\\'); + } + path.push_str(second_path); + } + // A UNC drive and a relative path need a separator between them. + if !path.is_empty() + && root.is_empty() + && drive + .chars() + .last() + .is_some_and(|last| !matches!(last, ':' | '\\' | '/')) + { + return format!("{drive}\\{path}"); + } + format!("{drive}{root}{path}") +} + +/// Returns `ntpath.normpath(path)`, as CPython's `_Py_normpath_and_size` computes it on Windows: +/// separators become `\`, empty and `.` components disappear, and `..` removes the component +/// before it, except at the root or after another `..`. +#[cfg(any(windows, test))] +pub(crate) fn windows_normpath(path: &str) -> String { + let input: Vec = path.chars().collect(); + if input.is_empty() { + return ".".to_owned(); + } + let separator = |character: char| character == '\\' || character == '/'; + let is_separator_or_end = |index: usize| input.get(index).is_none_or(|&c| separator(c)); + let (drive, root, _) = Flavor::Windows.split_root(path); + let prefix_length = path[..drive.len() + root.len()].chars().count(); + let mut output: Vec = Vec::with_capacity(input.len()); + let mut read = 0; + // The output index that `..` never removes, and the last character that was copied. + let mut minimum = 0; + let mut last = '\0'; + if prefix_length > 0 { + output.extend( + input[..prefix_length] + .iter() + .map(|&character| if character == '/' { '\\' } else { character }), + ); + read = prefix_length; + minimum = output.len() - 1; + last = output[minimum]; + if last != '\\' { + minimum += 1; + } + } + if input.get(read) == Some(&'.') && is_separator_or_end(read + 1) { + // Skip a leading `.\`. + read += 1; + last = match input.get(read) { + Some('/') => '\\', + Some(&character) => character, + None => '\0', + }; + while input + .get(read) + .is_some_and(|&character| separator(character)) + { + read += 1; + } + } + while read < input.len() { + let character = match input[read] { + '/' => '\\', + character => character, + }; + if last == '\\' { + if character == '.' { + let separator_at_1 = is_separator_or_end(read + 1); + let separator_at_2 = !separator_at_1 && is_separator_or_end(read + 2); + if separator_at_2 && input[read + 1] == '.' { + let mut start = output.len(); + while start != minimum { + start -= 1; + if output[start] != '\\' { + break; + } + } + while start != minimum && output[start - 1] != '\\' { + start -= 1; + } + let previous_is_parent = output.get(start) == Some(&'.') + && output.get(start + 1) == Some(&'.') + && output.get(start + 2).is_some_and(|&c| separator(c)); + if output.len() == minimum || previous_is_parent { + // Keep a `..` that has nothing to remove, or that follows another. + output.extend(['.', '.']); + last = '.'; + } else if output.get(start) == Some(&'\\') { + // At the root of an absolute path, `..` removes the component. + output.truncate(start + 1); + } else { + output.truncate(start); + } + read += 1; + } else if !separator_at_1 { + output.push(character); + last = character; + } + } else if character != '\\' { + output.push(character); + last = character; + } + } else { + output.push(character); + last = character; + } + read += 1; + } + // Drop trailing separators, but not the root. + let mut length = output.len(); + if length != minimum { + loop { + length -= 1; + if length == minimum || output[length] != '\\' { + break; + } + } + length += 1; + } + if length == 0 { + return ".".to_owned(); + } + output[..length].iter().collect() +} + +/// A pure path of one [`Flavor`], as `PurePosixPath` or `PureWindowsPath` holds it. +#[derive(Clone)] +pub(crate) struct PurePath { + /// The flavor of the path. + flavor: Flavor, + /// The drive: a drive letter and colon, or a UNC or device drive, on Windows; empty on POSIX. + drive: String, + /// The root: one separator, two slashes on POSIX, or empty for a relative path. + root: String, + /// The components after the anchor. + tail: Vec, + /// `str(path)`. + text: String, +} + +impl PurePath { + /// Returns the path of `text` in `flavor`, as `PurePosixPath(text)` or + /// `PureWindowsPath(text)` does. + pub(crate) fn new(flavor: Flavor, text: &str) -> Self { + let (drive, root, tail) = match flavor { + Flavor::Posix => { + let (root, tail) = parse_posix(text.as_bytes()); + ( + String::new(), + from_utf8(root), + tail.into_iter().map(from_utf8).collect(), + ) + } + Flavor::Windows => { + let units: Vec = text.encode_utf16().collect(); + let (drive, root, tail) = parse_windows(&units); + ( + from_utf16(&drive), + from_utf16(&root), + tail.iter().map(|part| from_utf16(part)).collect(), + ) + } + }; + Self::from_parts(flavor, drive, root, tail) + } + + /// Returns `pathlib.Path(text)` on this host. + pub(crate) fn native(text: &str) -> Self { + Self::new(Flavor::NATIVE, text) + } + + /// Returns the path of these parts, formatted as `pathlib` formats them. + fn from_parts(flavor: Flavor, drive: String, root: String, tail: Vec) -> Self { + let text = match flavor { + Flavor::Posix => { + let parts: Vec<&[u8]> = tail.iter().map(String::as_bytes).collect(); + from_utf8(&format_posix(root.as_bytes(), &parts)) + } + Flavor::Windows => { + let units = |text: &str| text.encode_utf16().collect::>(); + let parts: Vec> = tail.iter().map(|part| units(part)).collect(); + from_utf16(&format_windows(&units(&drive), &units(&root), &parts)) + } + }; + Self { + flavor, + drive, + root, + tail, + text, + } + } + + /// Returns the flavor of the path. + pub(crate) fn flavor(&self) -> Flavor { + self.flavor + } + + /// Returns `str(path)`. + pub(crate) fn as_str(&self) -> &str { + &self.text + } + + /// Returns `str(path)` as a host path. + pub(crate) fn as_path(&self) -> &Path { + Path::new(&self.text) + } + + /// Returns `path.parts`: the drive and root together, then each component. + pub(crate) fn parts(&self) -> Vec { + let anchor = format!("{}{}", self.drive, self.root); + (!anchor.is_empty()) + .then_some(anchor) + .into_iter() + .chain(self.tail.iter().cloned()) + .collect() + } + + /// Returns `path.name`: the last component, or an empty string. + pub(crate) fn name(&self) -> &str { + self.tail.last().map_or("", String::as_str) + } + + /// Returns `path.parents`, from the parent to the anchor. + pub(crate) fn parents(&self) -> impl Iterator + '_ { + (0..self.tail.len()).rev().map(|length| { + Self::from_parts( + self.flavor, + self.drive.clone(), + self.root.clone(), + self.tail[..length].to_vec(), + ) + }) + } + + /// Returns `path.is_absolute()`. + pub(crate) fn is_absolute(&self) -> bool { + self.flavor.is_absolute(&self.text) + } + + /// Returns `path / segment`. + #[must_use] + pub(crate) fn join(&self, segment: &str) -> Self { + Self::new(self.flavor, &self.flavor.join(&self.text, segment)) + } + + /// Returns `path.with_name(name)`, or `None` where it raises `ValueError`: for a name that is + /// empty, `.`, or holds a separator, and for a path without components. + pub(crate) fn with_name(&self, name: &str) -> Option { + if name.is_empty() || name == "." || name.chars().any(|c| self.flavor.is_separator(c)) { + return None; + } + let (_, rest) = self.tail.split_last()?; + let mut tail = rest.to_vec(); + tail.push(name.to_owned()); + Some(Self::from_parts( + self.flavor, + self.drive.clone(), + self.root.clone(), + tail, + )) + } + + /// Returns the string by which `pathlib` compares the path: `str(path)`, in lowercase on + /// Windows. + fn case_normalized(&self) -> std::borrow::Cow<'_, str> { + match self.flavor { + Flavor::Posix => self.text.as_str().into(), + Flavor::Windows => self.text.to_lowercase().into(), + } + } +} + +impl PartialEq for PurePath { + fn eq(&self, other: &Self) -> bool { + self.flavor == other.flavor && self.case_normalized() == other.case_normalized() + } +} + +impl Eq for PurePath {} + +impl fmt::Display for PurePath { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.text) + } +} + +impl fmt::Debug for PurePath { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let class = match self.flavor { + Flavor::Posix => "PurePosixPath", + Flavor::Windows => "PureWindowsPath", + }; + write!(formatter, "{class}({:?})", self.text) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn posix(path: &str) -> String { + String::from_utf8(posix_str(path.as_bytes())).unwrap() + } + + fn windows(path: &str) -> String { + String::from_utf16(&windows_str(&path.encode_utf16().collect::>())).unwrap() + } + + fn pure_posix(text: &str) -> PurePath { + PurePath::new(Flavor::Posix, text) + } + + fn pure_windows(text: &str) -> PurePath { + PurePath::new(Flavor::Windows, text) + } + + #[test] + fn paths_normalize_as_pathlib_does() { + // Expected values from Python 3.14's `PurePosixPath` and `PureWindowsPath` on Windows. + for (path, expected_posix, expected_windows) in [ + ("", ".", "."), + ("./", ".", "."), + ("a//b", "a/b", "a\\b"), + ("a/./b/.", "a/b", "a\\b"), + ("../a", "../a", "..\\a"), + ("/", "/", "\\"), + ("//", "//", "\\\\"), + ("///", "/", "\\\\\\"), + ("////a", "/a", "\\\\\\\\a"), + ("//a/b", "//a/b", "\\\\a\\b\\"), + ("c:/a", "c:/a", "c:\\a"), + ("c:\\a\\", "c:\\a\\", "c:\\a"), + ("C:/", "C:", "C:\\"), + ( + "\\\\server\\share", + "\\\\server\\share", + "\\\\server\\share\\", + ), + ("//?/UNC/a/b", "//?/UNC/a/b", "\\\\?\\UNC\\a\\b\\"), + ( + "\\\\?\\unc\\server\\share", + "\\\\?\\unc\\server\\share", + "\\\\?\\unc\\server\\share\\", + ), + ("\\\\.\\device\\x", "\\\\.\\device\\x", "\\\\.\\device\\x"), + ("\\\\\\share", "\\\\\\share", "\\\\\\share"), + ("./c:foo", "c:foo", ".\\c:foo"), + ("é:x", "é:x", "é:x"), + (".\\.\\", ".\\.\\", "."), + ("a /b", "a /b", "a \\b"), + // On Windows, a character beyond the Basic Multilingual Plane is two code units, so + // the colon after it makes no drive, and a NUL before a colon makes one. + ("\u{1F600}:x", "\u{1F600}:x", "\u{1F600}:x"), + ("./\u{1F600}:x", "\u{1F600}:x", "\u{1F600}:x"), + ("./\0:x", "\0:x", ".\\\0:x"), + ] { + assert_eq!(posix(path), expected_posix, "{path:?}"); + assert_eq!(windows(path), expected_windows, "{path:?}"); + assert_eq!(pure_posix(path).as_str(), expected_posix, "{path:?}"); + assert_eq!(pure_windows(path).as_str(), expected_windows, "{path:?}"); + } + } + + #[test] + fn unprintable_paths_render_as_python_prints_them() { + assert_eq!( + render_surrogateescape(b"a\xffb\xe2\x82"), + "a\\udcffb\\udce2\\udc82" + ); + assert_eq!(render_surrogateescape("é".as_bytes()), "é"); + assert_eq!(render_utf16(&[0x61, 0xD800, 0x62]), "a\\ud800b"); + assert_eq!(render_utf16(&[0xD83D, 0xDE00]), "\u{1F600}"); + } + + #[test] + fn hosts_open_the_normalized_path() { + let path = python_path(Path::new("a//b/./policy.json/")); + if cfg!(windows) { + assert_eq!(path.display, "a\\b\\policy.json"); + } else { + assert_eq!(path.display, "a/b/policy.json"); + } + assert_eq!(path.native, PathBuf::from(&path.display)); + assert_eq!( + path.display, + PurePath::native("a//b/./policy.json/").as_str() + ); + } + + #[test] + fn posix_paths_hold_their_parts() { + assert!(pure_posix("//a").is_absolute()); + assert!(!pure_posix("a/b").is_absolute()); + assert_eq!(pure_posix("/a/b").parts(), ["/", "a", "b"]); + assert_eq!(pure_posix("a/b").name(), "b"); + assert_eq!( + pure_posix("/a/b/c") + .parents() + .map(|p| p.text) + .collect::>(), + ["/a/b", "/a", "/"] + ); + assert_eq!(pure_posix("/a/b").join("c").as_str(), "/a/b/c"); + assert_eq!(pure_posix("/a/b").join("/c").as_str(), "/c"); + assert_ne!(pure_posix("/A"), pure_posix("/a")); + assert_eq!( + pure_posix("/a/b.erofs") + .with_name("b.erofs.manifest.json") + .unwrap() + .as_str(), + "/a/b.erofs.manifest.json" + ); + assert!(pure_posix("/").with_name("x").is_none()); + } + + #[test] + fn windows_paths_hold_their_parts() { + assert!(pure_windows("C:\\a").is_absolute()); + assert!(pure_windows("//server/share").is_absolute()); + assert!(!pure_windows("\\a").is_absolute()); + assert!(!pure_windows("C:a").is_absolute()); + // `ntpath.isabs` tests code points, even where `splitroot` finds no drive. + assert!(pure_windows("\u{1F600}:\\x").is_absolute()); + assert_eq!(pure_windows("\u{1F600}:\\x").parts(), ["\u{1F600}:", "x"]); + assert_eq!(pure_windows("C:\\A"), pure_windows("c:/a")); + assert_eq!(pure_windows("C:\\a\\b").parts(), ["C:\\", "a", "b"]); + assert_eq!( + pure_windows("C:\\cwd").join("a/b").as_str(), + "C:\\cwd\\a\\b" + ); + assert_eq!(pure_windows("C:\\cwd").join("C:x").as_str(), "C:\\cwd\\x"); + assert_eq!(pure_windows("C:\\cwd").join("D:x").as_str(), "D:x"); + assert_eq!(pure_windows("C:\\cwd").join("\\x").as_str(), "C:\\x"); + assert_eq!( + Flavor::Windows.split_root("//server/share/x"), + ("//server/share", "/", "x") + ); + assert_eq!( + Flavor::Windows.split_root("\u{1F600}:x"), + ("", "", "\u{1F600}:x") + ); + assert_eq!(Flavor::Windows.split_root("é:\\x"), ("é:", "\\", "x")); + } + + #[test] + fn windows_normpath_follows_cpython() { + for (text, expected) in [ + ("", "."), + (".", "."), + ("a", "a"), + ("a\\", "a"), + ("A//B", "A\\B"), + ("A/./B", "A\\B"), + ("A/foo/../B", "A\\B"), + ("C:\\", "C:\\"), + ("C:", "C:"), + ("C:a\\..\\..", "C:.."), + ("C:\\..\\a", "C:\\a"), + ("..\\..\\a", "..\\..\\a"), + ("a\\..\\..\\b", "..\\b"), + ("\\\\server\\share\\", "\\\\server\\share\\"), + ("\\\\server\\share\\a\\..", "\\\\server\\share\\"), + (".\\a\\.\\b\\", "a\\b"), + ("\\", "\\"), + ("\u{1F600}:x\\..\\y", "y"), + ("\0:x\\..\\y", "\0:y"), + ] { + assert_eq!(windows_normpath(text), expected, "{text:?}"); + } + } + + #[test] + fn split_returns_the_head_and_the_last_component() { + assert_eq!( + Flavor::Windows.split("C:\\a\\b"), + ("C:\\a".to_owned(), "b".to_owned()) + ); + assert_eq!( + Flavor::Windows.split("C:\\a"), + ("C:\\".to_owned(), "a".to_owned()) + ); + assert_eq!( + Flavor::Windows.split("C:\\"), + ("C:\\".to_owned(), String::new()) + ); + assert_eq!( + Flavor::Posix.split("/a/b"), + ("/a".to_owned(), "b".to_owned()) + ); + assert_eq!(Flavor::Posix.split("/a"), ("/".to_owned(), "a".to_owned())); + assert_eq!( + Flavor::Posix.split("//a"), + ("//".to_owned(), "a".to_owned()) + ); + } +} diff --git a/nvx-rs/src/host/python/shell.rs b/nvx-rs/src/host/python/shell.rs new file mode 100644 index 000000000..66716524f --- /dev/null +++ b/nvx-rs/src/host/python/shell.rs @@ -0,0 +1,121 @@ +//! How Python quotes command lines for display: `subprocess.list2cmdline` on Windows, and +//! `shlex.join` elsewhere. +//! +//! `nvx.py` prints the OpenVMM command that it runs as `>> COMMAND`, quoted with +//! [`list2cmdline`] on Windows and [`shlex_join`] on other hosts; [`format_command`] picks the +//! one of this host. + +/// Returns `subprocess.list2cmdline(arguments)`: the arguments joined by spaces, as the +/// Microsoft C runtime splits them again. +/// +/// An argument that is empty or holds a space or a tab is quoted. Backslashes are literal except +/// before a quote, where they are doubled and the quote escaped, and before the closing quote of +/// a quoted argument, where they are doubled. +pub(crate) fn list2cmdline>(arguments: &[S]) -> String { + let mut result = String::new(); + for argument in arguments { + let argument = argument.as_ref(); + if !result.is_empty() { + result.push(' '); + } + let quoted = argument.is_empty() || argument.contains([' ', '\t']); + if quoted { + result.push('"'); + } + let mut backslashes = 0; + for character in argument.chars() { + match character { + '\\' => backslashes += 1, + '"' => { + result.extend(std::iter::repeat_n('\\', backslashes * 2)); + backslashes = 0; + result.push_str("\\\""); + } + _ => { + result.extend(std::iter::repeat_n('\\', backslashes)); + backslashes = 0; + result.push(character); + } + } + } + result.extend(std::iter::repeat_n('\\', backslashes)); + if quoted { + result.extend(std::iter::repeat_n('\\', backslashes)); + result.push('"'); + } + } + result +} + +/// Returns `shlex.quote(text)`: `text` itself if it is a nonempty run of the characters that a +/// POSIX shell keeps as they are, `[A-Za-z0-9_@%+=:,./-]`, and otherwise `text` in single +/// quotes, with each single quote written as `'"'"'`. +pub(crate) fn shlex_quote(text: &str) -> String { + if text.is_empty() { + return "''".to_owned(); + } + let safe = |byte: u8| byte.is_ascii_alphanumeric() || b"_@%+=:,./-".contains(&byte); + if text.bytes().all(safe) { + return text.to_owned(); + } + format!("'{}'", text.replace('\'', "'\"'\"'")) +} + +/// Returns `shlex.join(arguments)`: each argument quoted with [`shlex_quote`], joined by spaces. +pub(crate) fn shlex_join>(arguments: &[S]) -> String { + arguments + .iter() + .map(|argument| shlex_quote(argument.as_ref())) + .collect::>() + .join(" ") +} + +/// Returns `_format_command(arguments)` of `nvx.py`: [`list2cmdline`] on Windows, and +/// [`shlex_join`] on other hosts. +pub(crate) fn format_command>(arguments: &[S]) -> String { + if cfg!(windows) { + list2cmdline(arguments) + } else { + shlex_join(arguments) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn list2cmdline_quotes_as_the_c_runtime_reads() { + let cases: [(&[&str], &str); 9] = [ + (&["a", "b c", ""], "a \"b c\" \"\""), + (&["a\tb"], "\"a\tb\""), + (&[r#"say "hi""#], r#""say \"hi\"""#), + (&[r#"a"b"#], r#"a\"b"#), + (&[r"a\\b"], r"a\\b"), + (&[r#"a\"b"#], r#"a\\\"b"#), + (&[r"dir\ with\"], r#""dir\ with\\""#), + (&[r"C:\path\"], r"C:\path\"), + (&[r#"x\\" y"#], r#""x\\\\\" y""#), + ]; + for (arguments, expected) in cases { + assert_eq!(list2cmdline(arguments), expected, "{arguments:?}"); + } + } + + #[test] + fn shlex_quotes_what_a_shell_would_expand() { + let cases: [(&str, &str); 7] = [ + ("", "''"), + ("plain-_@%+=:,./0", "plain-_@%+=:,./0"), + ("a b", "'a b'"), + ("it's", "'it'\"'\"'s'"), + ("$HOME", "'$HOME'"), + ("é", "'é'"), + ("a\nb", "'a\nb'"), + ]; + for (text, expected) in cases { + assert_eq!(shlex_quote(text), expected, "{text:?}"); + } + assert_eq!(shlex_join(&["/bin/echo", "a b", ""]), "/bin/echo 'a b' ''"); + } +} diff --git a/nvx-rs/src/host/python/text.rs b/nvx-rs/src/host/python/text.rs new file mode 100644 index 000000000..9d4dbf851 --- /dev/null +++ b/nvx-rs/src/host/python/text.rs @@ -0,0 +1,287 @@ +//! Strings as Python holds them. +//! +//! A Python `str` is a sequence of code points, and unlike a Rust `str` it may hold unpaired +//! surrogates: Python's JSON decoder keeps a lone `\ud800` escape as is, and Python decodes +//! command-line paths that are not valid Unicode into surrogates. [`PyText`] keeps such strings +//! exactly, so the egress policy compiler compares and orders them as Python does, and +//! [`PyText::render`] shows them the way Python prints them. +//! +//! Python's UTF-8 codec refuses what Rust's would, with messages of its own: +//! [`PyText::encode_utf8`] and [`decode_utf8`] give them. + +use std::fmt; + +/// Returns `bytes` as a string, or the message of the `UnicodeDecodeError` with which Python's +/// UTF-8 codec refuses them, such as `'utf-8' codec can't decode byte 0xff in position 0: invalid +/// start byte`. +pub(crate) fn decode_utf8(bytes: &[u8]) -> Result<&str, String> { + std::str::from_utf8(bytes).map_err(|error| { + let start = error.valid_up_to(); + // Rust and Python agree on the longest invalid sequence, so only the reason differs. + let (end, reason) = match error.error_len() { + None => (bytes.len(), "unexpected end of data"), + Some(length) => match bytes[start] { + 0x80..=0xC1 | 0xF5..=0xFF => (start + 1, "invalid start byte"), + _ => (start + length, "invalid continuation byte"), + }, + }; + if end == start + 1 { + format!( + "'utf-8' codec can't decode byte 0x{:02x} in position {start}: {reason}", + bytes[start] + ) + } else { + format!( + "'utf-8' codec can't decode bytes in position {start}-{}: {reason}", + end - 1 + ) + } + }) +} + +/// A Python `str`: Unicode scalar values and unpaired surrogates. +/// +/// Each code point is held in its UTF-8 form, a surrogate in the three-byte form that UTF-8 would +/// give it. Strings without surrogates are therefore plain UTF-8, and byte order is code-point +/// order, which is how Python orders strings. +#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct PyText(Vec); + +impl PyText { + /// Returns an empty string. + pub(crate) fn new() -> Self { + Self(Vec::new()) + } + + /// Returns the bytes of the string: UTF-8, with surrogates in their three-byte form. + pub(crate) fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Returns the string as a Rust string, or `None` if it holds a surrogate. + pub(crate) fn as_str(&self) -> Option<&str> { + std::str::from_utf8(&self.0).ok() + } + + /// Returns the string as `str.encode('utf-8')` encodes it, or the message of the + /// `UnicodeEncodeError` with which Python refuses its first run of surrogates, such as + /// `'utf-8' codec can't encode character '\ud800' in position 2: surrogates not allowed`. + pub(crate) fn encode_utf8(&self) -> Result<&str, String> { + if let Some(text) = self.as_str() { + return Ok(text); + } + let code_points: Vec = self.code_points().collect(); + let surrogate = |code_point: &u32| (0xD800..=0xDFFF).contains(code_point); + let start = code_points.iter().position(surrogate).unwrap_or_default(); + let end = code_points[start..] + .iter() + .position(|code_point| !surrogate(code_point)) + .map_or(code_points.len(), |length| start + length); + Err(if end == start + 1 { + format!( + "'utf-8' codec can't encode character '\\u{:04x}' in position {start}: \ + surrogates not allowed", + code_points[start] + ) + } else { + format!( + "'utf-8' codec can't encode characters in position {start}-{}: surrogates not \ + allowed", + end - 1 + ) + }) + } + + /// Appends a Rust string. + pub(crate) fn push_str(&mut self, text: &str) { + self.0.extend_from_slice(text.as_bytes()); + } + + /// Appends a code point, which may be a surrogate. `code_point` must not exceed `0x10FFFF`. + pub(crate) fn push_code_point(&mut self, code_point: u32) { + match char::from_u32(code_point) { + Some(character) => { + let mut buffer = [0; 4]; + self.push_str(character.encode_utf8(&mut buffer)); + } + // Only surrogates, 0xD800 through 0xDFFF, are code points but not characters. + None => self.0.extend_from_slice(&[ + 0xE0 | (code_point >> 12) as u8, + 0x80 | ((code_point >> 6) & 0x3F) as u8, + 0x80 | (code_point & 0x3F) as u8, + ]), + } + } + + /// Returns the code points of the string. + pub(crate) fn code_points(&self) -> CodePoints<'_> { + CodePoints(&self.0) + } + + /// Returns the string as Python prints it to a UTF-8 stream, which escapes each surrogate as + /// `\udXXX` with its `backslashreplace` error handler. Strings without surrogates are + /// returned unchanged. + pub(crate) fn render(&self) -> String { + if let Some(text) = self.as_str() { + return text.to_owned(); + } + let mut rendered = String::with_capacity(self.0.len() + 8); + for code_point in self.code_points() { + match char::from_u32(code_point) { + Some(character) => rendered.push(character), + None => rendered.push_str(&format!("\\u{code_point:04x}")), + } + } + rendered + } +} + +impl From<&str> for PyText { + fn from(text: &str) -> Self { + Self(text.as_bytes().to_vec()) + } +} + +impl fmt::Debug for PyText { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.render(), formatter) + } +} + +/// The code points of a [`PyText`], from first to last. +pub(crate) struct CodePoints<'a>(&'a [u8]); + +impl Iterator for CodePoints<'_> { + type Item = u32; + + fn next(&mut self) -> Option { + let (&lead, rest) = self.0.split_first()?; + // A `PyText` holds well-formed sequences only, so the lead byte gives the length. + let (length, initial) = match lead { + 0x00..=0x7F => (1, u32::from(lead)), + 0xC0..=0xDF => (2, u32::from(lead & 0x1F)), + 0xE0..=0xEF => (3, u32::from(lead & 0x0F)), + _ => (4, u32::from(lead & 0x07)), + }; + let continuation = &rest[..length - 1]; + self.0 = &rest[length - 1..]; + Some(continuation.iter().fold(initial, |code_point, byte| { + (code_point << 6) | u32::from(byte & 0x3F) + })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn surrogates_render_as_python_escapes() { + let mut text = PyText::from("a"); + text.push_code_point(0xD800); + text.push_code_point(0x1F600); + text.push_code_point(0xDFFF); + text.push_code_point(u32::from('é')); + assert_eq!(text.as_str(), None); + assert_eq!(text.render(), "a\\ud800\u{1F600}\\udfffé"); + assert_eq!( + text.code_points().collect::>(), + [0x61, 0xD800, 0x1F600, 0xDFFF, 0xE9] + ); + assert_eq!(PyText::from("plain").render(), "plain"); + assert_eq!(PyText::from("plain").as_str(), Some("plain")); + } + + #[test] + fn strings_order_by_code_point() { + let surrogate = { + let mut text = PyText::new(); + text.push_code_point(0xD800); + text + }; + // Python orders `'\ud7ff' < '\ud800' < '\ue000' < '\U00010000'`. + let mut texts = vec![ + PyText::from("\u{10000}"), + PyText::from("\u{E000}"), + surrogate.clone(), + PyText::from("\u{D7FF}"), + PyText::from("b"), + PyText::from("ab"), + PyText::from("a"), + ]; + texts.sort(); + assert_eq!( + texts, + [ + PyText::from("a"), + PyText::from("ab"), + PyText::from("b"), + PyText::from("\u{D7FF}"), + surrogate, + PyText::from("\u{E000}"), + PyText::from("\u{10000}"), + ] + ); + } + + #[test] + fn encoding_fails_at_the_first_run_of_surrogates() { + assert_eq!(PyText::from("plain é").encode_utf8(), Ok("plain é")); + let mut text = PyText::from("ab"); + text.push_code_point(0xD800); + text.push_str("c"); + text.push_code_point(0xDC00); + text.push_code_point(0xD800); + assert_eq!( + text.encode_utf8(), + Err( + "'utf-8' codec can't encode character '\\ud800' in position 2: surrogates not \ + allowed" + .to_owned() + ) + ); + let mut text = PyText::from("\u{1F600}"); + text.push_code_point(0xDC00); + text.push_code_point(0xD800); + assert_eq!( + text.encode_utf8(), + Err( + "'utf-8' codec can't encode characters in position 1-2: surrogates not allowed" + .to_owned() + ) + ); + } + + #[test] + fn decoding_fails_as_python_reports_it() { + assert_eq!(decode_utf8(b"ok \xc3\xa9"), Ok("ok é")); + for (bytes, expected) in [ + ( + &b"\xff"[..], + "'utf-8' codec can't decode byte 0xff in position 0: invalid start byte", + ), + ( + b"a\xe2\x82", + "'utf-8' codec can't decode bytes in position 1-2: unexpected end of data", + ), + ( + b"\xe2(", + "'utf-8' codec can't decode byte 0xe2 in position 0: invalid continuation byte", + ), + ( + b"\xf0\x90(", + "'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte", + ), + ( + b"\xed\xa0\x80", + "'utf-8' codec can't decode byte 0xed in position 0: invalid continuation byte", + ), + ( + b"\xc0\x80", + "'utf-8' codec can't decode byte 0xc0 in position 0: invalid start byte", + ), + ] { + assert_eq!(decode_utf8(bytes), Err(expected.to_owned()), "{bytes:?}"); + } + } +} diff --git a/nvx-rs/src/host/python/unicode.rs b/nvx-rs/src/host/python/unicode.rs new file mode 100644 index 000000000..988b2bcd2 --- /dev/null +++ b/nvx-rs/src/host/python/unicode.rs @@ -0,0 +1,210 @@ +//! Unicode properties of Python's `str`, from the Unicode 16.0.0 database +//! of Python 3.14. +//! +//! Generated by `scripts/nvx_rs_fixtures.py`; do not edit. + +/// The first code point of each run of ten decimal digits, of category Nd, which `int` +/// reads as the digits zero to nine. +#[rustfmt::skip] +pub(crate) const DECIMAL_STARTS: &[u32] = &[ + 0x00030, 0x00660, 0x006F0, 0x007C0, 0x00966, 0x009E6, 0x00A66, 0x00AE6, + 0x00B66, 0x00BE6, 0x00C66, 0x00CE6, 0x00D66, 0x00DE6, 0x00E50, 0x00ED0, + 0x00F20, 0x01040, 0x01090, 0x017E0, 0x01810, 0x01946, 0x019D0, 0x01A80, + 0x01A90, 0x01B50, 0x01BB0, 0x01C40, 0x01C50, 0x0A620, 0x0A8D0, 0x0A900, + 0x0A9D0, 0x0A9F0, 0x0AA50, 0x0ABF0, 0x0FF10, 0x104A0, 0x10D30, 0x10D40, + 0x11066, 0x110F0, 0x11136, 0x111D0, 0x112F0, 0x11450, 0x114D0, 0x11650, + 0x116C0, 0x116D0, 0x116DA, 0x11730, 0x118E0, 0x11950, 0x11BF0, 0x11C50, + 0x11D50, 0x11DA0, 0x11F50, 0x16130, 0x16A60, 0x16AC0, 0x16B50, 0x16D70, + 0x1CCF0, 0x1D7CE, 0x1D7D8, 0x1D7E2, 0x1D7EC, 0x1D7F6, 0x1E140, 0x1E2F0, + 0x1E4F0, 0x1E5F1, 0x1E950, 0x1FBF0, +]; + +/// The inclusive ranges of the code points from U+0080 for which `str.isprintable` is false, +/// in order: `repr` escapes them. +#[rustfmt::skip] +pub(crate) const NON_PRINTABLE: &[(u32, u32)] = &[ + (0x00080, 0x000A0), (0x000AD, 0x000AD), (0x00378, 0x00379), (0x00380, 0x00383), + (0x0038B, 0x0038B), (0x0038D, 0x0038D), (0x003A2, 0x003A2), (0x00530, 0x00530), + (0x00557, 0x00558), (0x0058B, 0x0058C), (0x00590, 0x00590), (0x005C8, 0x005CF), + (0x005EB, 0x005EE), (0x005F5, 0x00605), (0x0061C, 0x0061C), (0x006DD, 0x006DD), + (0x0070E, 0x0070F), (0x0074B, 0x0074C), (0x007B2, 0x007BF), (0x007FB, 0x007FC), + (0x0082E, 0x0082F), (0x0083F, 0x0083F), (0x0085C, 0x0085D), (0x0085F, 0x0085F), + (0x0086B, 0x0086F), (0x0088F, 0x00896), (0x008E2, 0x008E2), (0x00984, 0x00984), + (0x0098D, 0x0098E), (0x00991, 0x00992), (0x009A9, 0x009A9), (0x009B1, 0x009B1), + (0x009B3, 0x009B5), (0x009BA, 0x009BB), (0x009C5, 0x009C6), (0x009C9, 0x009CA), + (0x009CF, 0x009D6), (0x009D8, 0x009DB), (0x009DE, 0x009DE), (0x009E4, 0x009E5), + (0x009FF, 0x00A00), (0x00A04, 0x00A04), (0x00A0B, 0x00A0E), (0x00A11, 0x00A12), + (0x00A29, 0x00A29), (0x00A31, 0x00A31), (0x00A34, 0x00A34), (0x00A37, 0x00A37), + (0x00A3A, 0x00A3B), (0x00A3D, 0x00A3D), (0x00A43, 0x00A46), (0x00A49, 0x00A4A), + (0x00A4E, 0x00A50), (0x00A52, 0x00A58), (0x00A5D, 0x00A5D), (0x00A5F, 0x00A65), + (0x00A77, 0x00A80), (0x00A84, 0x00A84), (0x00A8E, 0x00A8E), (0x00A92, 0x00A92), + (0x00AA9, 0x00AA9), (0x00AB1, 0x00AB1), (0x00AB4, 0x00AB4), (0x00ABA, 0x00ABB), + (0x00AC6, 0x00AC6), (0x00ACA, 0x00ACA), (0x00ACE, 0x00ACF), (0x00AD1, 0x00ADF), + (0x00AE4, 0x00AE5), (0x00AF2, 0x00AF8), (0x00B00, 0x00B00), (0x00B04, 0x00B04), + (0x00B0D, 0x00B0E), (0x00B11, 0x00B12), (0x00B29, 0x00B29), (0x00B31, 0x00B31), + (0x00B34, 0x00B34), (0x00B3A, 0x00B3B), (0x00B45, 0x00B46), (0x00B49, 0x00B4A), + (0x00B4E, 0x00B54), (0x00B58, 0x00B5B), (0x00B5E, 0x00B5E), (0x00B64, 0x00B65), + (0x00B78, 0x00B81), (0x00B84, 0x00B84), (0x00B8B, 0x00B8D), (0x00B91, 0x00B91), + (0x00B96, 0x00B98), (0x00B9B, 0x00B9B), (0x00B9D, 0x00B9D), (0x00BA0, 0x00BA2), + (0x00BA5, 0x00BA7), (0x00BAB, 0x00BAD), (0x00BBA, 0x00BBD), (0x00BC3, 0x00BC5), + (0x00BC9, 0x00BC9), (0x00BCE, 0x00BCF), (0x00BD1, 0x00BD6), (0x00BD8, 0x00BE5), + (0x00BFB, 0x00BFF), (0x00C0D, 0x00C0D), (0x00C11, 0x00C11), (0x00C29, 0x00C29), + (0x00C3A, 0x00C3B), (0x00C45, 0x00C45), (0x00C49, 0x00C49), (0x00C4E, 0x00C54), + (0x00C57, 0x00C57), (0x00C5B, 0x00C5C), (0x00C5E, 0x00C5F), (0x00C64, 0x00C65), + (0x00C70, 0x00C76), (0x00C8D, 0x00C8D), (0x00C91, 0x00C91), (0x00CA9, 0x00CA9), + (0x00CB4, 0x00CB4), (0x00CBA, 0x00CBB), (0x00CC5, 0x00CC5), (0x00CC9, 0x00CC9), + (0x00CCE, 0x00CD4), (0x00CD7, 0x00CDC), (0x00CDF, 0x00CDF), (0x00CE4, 0x00CE5), + (0x00CF0, 0x00CF0), (0x00CF4, 0x00CFF), (0x00D0D, 0x00D0D), (0x00D11, 0x00D11), + (0x00D45, 0x00D45), (0x00D49, 0x00D49), (0x00D50, 0x00D53), (0x00D64, 0x00D65), + (0x00D80, 0x00D80), (0x00D84, 0x00D84), (0x00D97, 0x00D99), (0x00DB2, 0x00DB2), + (0x00DBC, 0x00DBC), (0x00DBE, 0x00DBF), (0x00DC7, 0x00DC9), (0x00DCB, 0x00DCE), + (0x00DD5, 0x00DD5), (0x00DD7, 0x00DD7), (0x00DE0, 0x00DE5), (0x00DF0, 0x00DF1), + (0x00DF5, 0x00E00), (0x00E3B, 0x00E3E), (0x00E5C, 0x00E80), (0x00E83, 0x00E83), + (0x00E85, 0x00E85), (0x00E8B, 0x00E8B), (0x00EA4, 0x00EA4), (0x00EA6, 0x00EA6), + (0x00EBE, 0x00EBF), (0x00EC5, 0x00EC5), (0x00EC7, 0x00EC7), (0x00ECF, 0x00ECF), + (0x00EDA, 0x00EDB), (0x00EE0, 0x00EFF), (0x00F48, 0x00F48), (0x00F6D, 0x00F70), + (0x00F98, 0x00F98), (0x00FBD, 0x00FBD), (0x00FCD, 0x00FCD), (0x00FDB, 0x00FFF), + (0x010C6, 0x010C6), (0x010C8, 0x010CC), (0x010CE, 0x010CF), (0x01249, 0x01249), + (0x0124E, 0x0124F), (0x01257, 0x01257), (0x01259, 0x01259), (0x0125E, 0x0125F), + (0x01289, 0x01289), (0x0128E, 0x0128F), (0x012B1, 0x012B1), (0x012B6, 0x012B7), + (0x012BF, 0x012BF), (0x012C1, 0x012C1), (0x012C6, 0x012C7), (0x012D7, 0x012D7), + (0x01311, 0x01311), (0x01316, 0x01317), (0x0135B, 0x0135C), (0x0137D, 0x0137F), + (0x0139A, 0x0139F), (0x013F6, 0x013F7), (0x013FE, 0x013FF), (0x01680, 0x01680), + (0x0169D, 0x0169F), (0x016F9, 0x016FF), (0x01716, 0x0171E), (0x01737, 0x0173F), + (0x01754, 0x0175F), (0x0176D, 0x0176D), (0x01771, 0x01771), (0x01774, 0x0177F), + (0x017DE, 0x017DF), (0x017EA, 0x017EF), (0x017FA, 0x017FF), (0x0180E, 0x0180E), + (0x0181A, 0x0181F), (0x01879, 0x0187F), (0x018AB, 0x018AF), (0x018F6, 0x018FF), + (0x0191F, 0x0191F), (0x0192C, 0x0192F), (0x0193C, 0x0193F), (0x01941, 0x01943), + (0x0196E, 0x0196F), (0x01975, 0x0197F), (0x019AC, 0x019AF), (0x019CA, 0x019CF), + (0x019DB, 0x019DD), (0x01A1C, 0x01A1D), (0x01A5F, 0x01A5F), (0x01A7D, 0x01A7E), + (0x01A8A, 0x01A8F), (0x01A9A, 0x01A9F), (0x01AAE, 0x01AAF), (0x01ACF, 0x01AFF), + (0x01B4D, 0x01B4D), (0x01BF4, 0x01BFB), (0x01C38, 0x01C3A), (0x01C4A, 0x01C4C), + (0x01C8B, 0x01C8F), (0x01CBB, 0x01CBC), (0x01CC8, 0x01CCF), (0x01CFB, 0x01CFF), + (0x01F16, 0x01F17), (0x01F1E, 0x01F1F), (0x01F46, 0x01F47), (0x01F4E, 0x01F4F), + (0x01F58, 0x01F58), (0x01F5A, 0x01F5A), (0x01F5C, 0x01F5C), (0x01F5E, 0x01F5E), + (0x01F7E, 0x01F7F), (0x01FB5, 0x01FB5), (0x01FC5, 0x01FC5), (0x01FD4, 0x01FD5), + (0x01FDC, 0x01FDC), (0x01FF0, 0x01FF1), (0x01FF5, 0x01FF5), (0x01FFF, 0x0200F), + (0x02028, 0x0202F), (0x0205F, 0x0206F), (0x02072, 0x02073), (0x0208F, 0x0208F), + (0x0209D, 0x0209F), (0x020C1, 0x020CF), (0x020F1, 0x020FF), (0x0218C, 0x0218F), + (0x0242A, 0x0243F), (0x0244B, 0x0245F), (0x02B74, 0x02B75), (0x02B96, 0x02B96), + (0x02CF4, 0x02CF8), (0x02D26, 0x02D26), (0x02D28, 0x02D2C), (0x02D2E, 0x02D2F), + (0x02D68, 0x02D6E), (0x02D71, 0x02D7E), (0x02D97, 0x02D9F), (0x02DA7, 0x02DA7), + (0x02DAF, 0x02DAF), (0x02DB7, 0x02DB7), (0x02DBF, 0x02DBF), (0x02DC7, 0x02DC7), + (0x02DCF, 0x02DCF), (0x02DD7, 0x02DD7), (0x02DDF, 0x02DDF), (0x02E5E, 0x02E7F), + (0x02E9A, 0x02E9A), (0x02EF4, 0x02EFF), (0x02FD6, 0x02FEF), (0x03000, 0x03000), + (0x03040, 0x03040), (0x03097, 0x03098), (0x03100, 0x03104), (0x03130, 0x03130), + (0x0318F, 0x0318F), (0x031E6, 0x031EE), (0x0321F, 0x0321F), (0x0A48D, 0x0A48F), + (0x0A4C7, 0x0A4CF), (0x0A62C, 0x0A63F), (0x0A6F8, 0x0A6FF), (0x0A7CE, 0x0A7CF), + (0x0A7D2, 0x0A7D2), (0x0A7D4, 0x0A7D4), (0x0A7DD, 0x0A7F1), (0x0A82D, 0x0A82F), + (0x0A83A, 0x0A83F), (0x0A878, 0x0A87F), (0x0A8C6, 0x0A8CD), (0x0A8DA, 0x0A8DF), + (0x0A954, 0x0A95E), (0x0A97D, 0x0A97F), (0x0A9CE, 0x0A9CE), (0x0A9DA, 0x0A9DD), + (0x0A9FF, 0x0A9FF), (0x0AA37, 0x0AA3F), (0x0AA4E, 0x0AA4F), (0x0AA5A, 0x0AA5B), + (0x0AAC3, 0x0AADA), (0x0AAF7, 0x0AB00), (0x0AB07, 0x0AB08), (0x0AB0F, 0x0AB10), + (0x0AB17, 0x0AB1F), (0x0AB27, 0x0AB27), (0x0AB2F, 0x0AB2F), (0x0AB6C, 0x0AB6F), + (0x0ABEE, 0x0ABEF), (0x0ABFA, 0x0ABFF), (0x0D7A4, 0x0D7AF), (0x0D7C7, 0x0D7CA), + (0x0D7FC, 0x0F8FF), (0x0FA6E, 0x0FA6F), (0x0FADA, 0x0FAFF), (0x0FB07, 0x0FB12), + (0x0FB18, 0x0FB1C), (0x0FB37, 0x0FB37), (0x0FB3D, 0x0FB3D), (0x0FB3F, 0x0FB3F), + (0x0FB42, 0x0FB42), (0x0FB45, 0x0FB45), (0x0FBC3, 0x0FBD2), (0x0FD90, 0x0FD91), + (0x0FDC8, 0x0FDCE), (0x0FDD0, 0x0FDEF), (0x0FE1A, 0x0FE1F), (0x0FE53, 0x0FE53), + (0x0FE67, 0x0FE67), (0x0FE6C, 0x0FE6F), (0x0FE75, 0x0FE75), (0x0FEFD, 0x0FF00), + (0x0FFBF, 0x0FFC1), (0x0FFC8, 0x0FFC9), (0x0FFD0, 0x0FFD1), (0x0FFD8, 0x0FFD9), + (0x0FFDD, 0x0FFDF), (0x0FFE7, 0x0FFE7), (0x0FFEF, 0x0FFFB), (0x0FFFE, 0x0FFFF), + (0x1000C, 0x1000C), (0x10027, 0x10027), (0x1003B, 0x1003B), (0x1003E, 0x1003E), + (0x1004E, 0x1004F), (0x1005E, 0x1007F), (0x100FB, 0x100FF), (0x10103, 0x10106), + (0x10134, 0x10136), (0x1018F, 0x1018F), (0x1019D, 0x1019F), (0x101A1, 0x101CF), + (0x101FE, 0x1027F), (0x1029D, 0x1029F), (0x102D1, 0x102DF), (0x102FC, 0x102FF), + (0x10324, 0x1032C), (0x1034B, 0x1034F), (0x1037B, 0x1037F), (0x1039E, 0x1039E), + (0x103C4, 0x103C7), (0x103D6, 0x103FF), (0x1049E, 0x1049F), (0x104AA, 0x104AF), + (0x104D4, 0x104D7), (0x104FC, 0x104FF), (0x10528, 0x1052F), (0x10564, 0x1056E), + (0x1057B, 0x1057B), (0x1058B, 0x1058B), (0x10593, 0x10593), (0x10596, 0x10596), + (0x105A2, 0x105A2), (0x105B2, 0x105B2), (0x105BA, 0x105BA), (0x105BD, 0x105BF), + (0x105F4, 0x105FF), (0x10737, 0x1073F), (0x10756, 0x1075F), (0x10768, 0x1077F), + (0x10786, 0x10786), (0x107B1, 0x107B1), (0x107BB, 0x107FF), (0x10806, 0x10807), + (0x10809, 0x10809), (0x10836, 0x10836), (0x10839, 0x1083B), (0x1083D, 0x1083E), + (0x10856, 0x10856), (0x1089F, 0x108A6), (0x108B0, 0x108DF), (0x108F3, 0x108F3), + (0x108F6, 0x108FA), (0x1091C, 0x1091E), (0x1093A, 0x1093E), (0x10940, 0x1097F), + (0x109B8, 0x109BB), (0x109D0, 0x109D1), (0x10A04, 0x10A04), (0x10A07, 0x10A0B), + (0x10A14, 0x10A14), (0x10A18, 0x10A18), (0x10A36, 0x10A37), (0x10A3B, 0x10A3E), + (0x10A49, 0x10A4F), (0x10A59, 0x10A5F), (0x10AA0, 0x10ABF), (0x10AE7, 0x10AEA), + (0x10AF7, 0x10AFF), (0x10B36, 0x10B38), (0x10B56, 0x10B57), (0x10B73, 0x10B77), + (0x10B92, 0x10B98), (0x10B9D, 0x10BA8), (0x10BB0, 0x10BFF), (0x10C49, 0x10C7F), + (0x10CB3, 0x10CBF), (0x10CF3, 0x10CF9), (0x10D28, 0x10D2F), (0x10D3A, 0x10D3F), + (0x10D66, 0x10D68), (0x10D86, 0x10D8D), (0x10D90, 0x10E5F), (0x10E7F, 0x10E7F), + (0x10EAA, 0x10EAA), (0x10EAE, 0x10EAF), (0x10EB2, 0x10EC1), (0x10EC5, 0x10EFB), + (0x10F28, 0x10F2F), (0x10F5A, 0x10F6F), (0x10F8A, 0x10FAF), (0x10FCC, 0x10FDF), + (0x10FF7, 0x10FFF), (0x1104E, 0x11051), (0x11076, 0x1107E), (0x110BD, 0x110BD), + (0x110C3, 0x110CF), (0x110E9, 0x110EF), (0x110FA, 0x110FF), (0x11135, 0x11135), + (0x11148, 0x1114F), (0x11177, 0x1117F), (0x111E0, 0x111E0), (0x111F5, 0x111FF), + (0x11212, 0x11212), (0x11242, 0x1127F), (0x11287, 0x11287), (0x11289, 0x11289), + (0x1128E, 0x1128E), (0x1129E, 0x1129E), (0x112AA, 0x112AF), (0x112EB, 0x112EF), + (0x112FA, 0x112FF), (0x11304, 0x11304), (0x1130D, 0x1130E), (0x11311, 0x11312), + (0x11329, 0x11329), (0x11331, 0x11331), (0x11334, 0x11334), (0x1133A, 0x1133A), + (0x11345, 0x11346), (0x11349, 0x1134A), (0x1134E, 0x1134F), (0x11351, 0x11356), + (0x11358, 0x1135C), (0x11364, 0x11365), (0x1136D, 0x1136F), (0x11375, 0x1137F), + (0x1138A, 0x1138A), (0x1138C, 0x1138D), (0x1138F, 0x1138F), (0x113B6, 0x113B6), + (0x113C1, 0x113C1), (0x113C3, 0x113C4), (0x113C6, 0x113C6), (0x113CB, 0x113CB), + (0x113D6, 0x113D6), (0x113D9, 0x113E0), (0x113E3, 0x113FF), (0x1145C, 0x1145C), + (0x11462, 0x1147F), (0x114C8, 0x114CF), (0x114DA, 0x1157F), (0x115B6, 0x115B7), + (0x115DE, 0x115FF), (0x11645, 0x1164F), (0x1165A, 0x1165F), (0x1166D, 0x1167F), + (0x116BA, 0x116BF), (0x116CA, 0x116CF), (0x116E4, 0x116FF), (0x1171B, 0x1171C), + (0x1172C, 0x1172F), (0x11747, 0x117FF), (0x1183C, 0x1189F), (0x118F3, 0x118FE), + (0x11907, 0x11908), (0x1190A, 0x1190B), (0x11914, 0x11914), (0x11917, 0x11917), + (0x11936, 0x11936), (0x11939, 0x1193A), (0x11947, 0x1194F), (0x1195A, 0x1199F), + (0x119A8, 0x119A9), (0x119D8, 0x119D9), (0x119E5, 0x119FF), (0x11A48, 0x11A4F), + (0x11AA3, 0x11AAF), (0x11AF9, 0x11AFF), (0x11B0A, 0x11BBF), (0x11BE2, 0x11BEF), + (0x11BFA, 0x11BFF), (0x11C09, 0x11C09), (0x11C37, 0x11C37), (0x11C46, 0x11C4F), + (0x11C6D, 0x11C6F), (0x11C90, 0x11C91), (0x11CA8, 0x11CA8), (0x11CB7, 0x11CFF), + (0x11D07, 0x11D07), (0x11D0A, 0x11D0A), (0x11D37, 0x11D39), (0x11D3B, 0x11D3B), + (0x11D3E, 0x11D3E), (0x11D48, 0x11D4F), (0x11D5A, 0x11D5F), (0x11D66, 0x11D66), + (0x11D69, 0x11D69), (0x11D8F, 0x11D8F), (0x11D92, 0x11D92), (0x11D99, 0x11D9F), + (0x11DAA, 0x11EDF), (0x11EF9, 0x11EFF), (0x11F11, 0x11F11), (0x11F3B, 0x11F3D), + (0x11F5B, 0x11FAF), (0x11FB1, 0x11FBF), (0x11FF2, 0x11FFE), (0x1239A, 0x123FF), + (0x1246F, 0x1246F), (0x12475, 0x1247F), (0x12544, 0x12F8F), (0x12FF3, 0x12FFF), + (0x13430, 0x1343F), (0x13456, 0x1345F), (0x143FB, 0x143FF), (0x14647, 0x160FF), + (0x1613A, 0x167FF), (0x16A39, 0x16A3F), (0x16A5F, 0x16A5F), (0x16A6A, 0x16A6D), + (0x16ABF, 0x16ABF), (0x16ACA, 0x16ACF), (0x16AEE, 0x16AEF), (0x16AF6, 0x16AFF), + (0x16B46, 0x16B4F), (0x16B5A, 0x16B5A), (0x16B62, 0x16B62), (0x16B78, 0x16B7C), + (0x16B90, 0x16D3F), (0x16D7A, 0x16E3F), (0x16E9B, 0x16EFF), (0x16F4B, 0x16F4E), + (0x16F88, 0x16F8E), (0x16FA0, 0x16FDF), (0x16FE5, 0x16FEF), (0x16FF2, 0x16FFF), + (0x187F8, 0x187FF), (0x18CD6, 0x18CFE), (0x18D09, 0x1AFEF), (0x1AFF4, 0x1AFF4), + (0x1AFFC, 0x1AFFC), (0x1AFFF, 0x1AFFF), (0x1B123, 0x1B131), (0x1B133, 0x1B14F), + (0x1B153, 0x1B154), (0x1B156, 0x1B163), (0x1B168, 0x1B16F), (0x1B2FC, 0x1BBFF), + (0x1BC6B, 0x1BC6F), (0x1BC7D, 0x1BC7F), (0x1BC89, 0x1BC8F), (0x1BC9A, 0x1BC9B), + (0x1BCA0, 0x1CBFF), (0x1CCFA, 0x1CCFF), (0x1CEB4, 0x1CEFF), (0x1CF2E, 0x1CF2F), + (0x1CF47, 0x1CF4F), (0x1CFC4, 0x1CFFF), (0x1D0F6, 0x1D0FF), (0x1D127, 0x1D128), + (0x1D173, 0x1D17A), (0x1D1EB, 0x1D1FF), (0x1D246, 0x1D2BF), (0x1D2D4, 0x1D2DF), + (0x1D2F4, 0x1D2FF), (0x1D357, 0x1D35F), (0x1D379, 0x1D3FF), (0x1D455, 0x1D455), + (0x1D49D, 0x1D49D), (0x1D4A0, 0x1D4A1), (0x1D4A3, 0x1D4A4), (0x1D4A7, 0x1D4A8), + (0x1D4AD, 0x1D4AD), (0x1D4BA, 0x1D4BA), (0x1D4BC, 0x1D4BC), (0x1D4C4, 0x1D4C4), + (0x1D506, 0x1D506), (0x1D50B, 0x1D50C), (0x1D515, 0x1D515), (0x1D51D, 0x1D51D), + (0x1D53A, 0x1D53A), (0x1D53F, 0x1D53F), (0x1D545, 0x1D545), (0x1D547, 0x1D549), + (0x1D551, 0x1D551), (0x1D6A6, 0x1D6A7), (0x1D7CC, 0x1D7CD), (0x1DA8C, 0x1DA9A), + (0x1DAA0, 0x1DAA0), (0x1DAB0, 0x1DEFF), (0x1DF1F, 0x1DF24), (0x1DF2B, 0x1DFFF), + (0x1E007, 0x1E007), (0x1E019, 0x1E01A), (0x1E022, 0x1E022), (0x1E025, 0x1E025), + (0x1E02B, 0x1E02F), (0x1E06E, 0x1E08E), (0x1E090, 0x1E0FF), (0x1E12D, 0x1E12F), + (0x1E13E, 0x1E13F), (0x1E14A, 0x1E14D), (0x1E150, 0x1E28F), (0x1E2AF, 0x1E2BF), + (0x1E2FA, 0x1E2FE), (0x1E300, 0x1E4CF), (0x1E4FA, 0x1E5CF), (0x1E5FB, 0x1E5FE), + (0x1E600, 0x1E7DF), (0x1E7E7, 0x1E7E7), (0x1E7EC, 0x1E7EC), (0x1E7EF, 0x1E7EF), + (0x1E7FF, 0x1E7FF), (0x1E8C5, 0x1E8C6), (0x1E8D7, 0x1E8FF), (0x1E94C, 0x1E94F), + (0x1E95A, 0x1E95D), (0x1E960, 0x1EC70), (0x1ECB5, 0x1ED00), (0x1ED3E, 0x1EDFF), + (0x1EE04, 0x1EE04), (0x1EE20, 0x1EE20), (0x1EE23, 0x1EE23), (0x1EE25, 0x1EE26), + (0x1EE28, 0x1EE28), (0x1EE33, 0x1EE33), (0x1EE38, 0x1EE38), (0x1EE3A, 0x1EE3A), + (0x1EE3C, 0x1EE41), (0x1EE43, 0x1EE46), (0x1EE48, 0x1EE48), (0x1EE4A, 0x1EE4A), + (0x1EE4C, 0x1EE4C), (0x1EE50, 0x1EE50), (0x1EE53, 0x1EE53), (0x1EE55, 0x1EE56), + (0x1EE58, 0x1EE58), (0x1EE5A, 0x1EE5A), (0x1EE5C, 0x1EE5C), (0x1EE5E, 0x1EE5E), + (0x1EE60, 0x1EE60), (0x1EE63, 0x1EE63), (0x1EE65, 0x1EE66), (0x1EE6B, 0x1EE6B), + (0x1EE73, 0x1EE73), (0x1EE78, 0x1EE78), (0x1EE7D, 0x1EE7D), (0x1EE7F, 0x1EE7F), + (0x1EE8A, 0x1EE8A), (0x1EE9C, 0x1EEA0), (0x1EEA4, 0x1EEA4), (0x1EEAA, 0x1EEAA), + (0x1EEBC, 0x1EEEF), (0x1EEF2, 0x1EFFF), (0x1F02C, 0x1F02F), (0x1F094, 0x1F09F), + (0x1F0AF, 0x1F0B0), (0x1F0C0, 0x1F0C0), (0x1F0D0, 0x1F0D0), (0x1F0F6, 0x1F0FF), + (0x1F1AE, 0x1F1E5), (0x1F203, 0x1F20F), (0x1F23C, 0x1F23F), (0x1F249, 0x1F24F), + (0x1F252, 0x1F25F), (0x1F266, 0x1F2FF), (0x1F6D8, 0x1F6DB), (0x1F6ED, 0x1F6EF), + (0x1F6FD, 0x1F6FF), (0x1F777, 0x1F77A), (0x1F7DA, 0x1F7DF), (0x1F7EC, 0x1F7EF), + (0x1F7F1, 0x1F7FF), (0x1F80C, 0x1F80F), (0x1F848, 0x1F84F), (0x1F85A, 0x1F85F), + (0x1F888, 0x1F88F), (0x1F8AE, 0x1F8AF), (0x1F8BC, 0x1F8BF), (0x1F8C2, 0x1F8FF), + (0x1FA54, 0x1FA5F), (0x1FA6E, 0x1FA6F), (0x1FA7D, 0x1FA7F), (0x1FA8A, 0x1FA8E), + (0x1FAC7, 0x1FACD), (0x1FADD, 0x1FADE), (0x1FAEA, 0x1FAEF), (0x1FAF9, 0x1FAFF), + (0x1FB93, 0x1FB93), (0x1FBFA, 0x1FFFF), (0x2A6E0, 0x2A6FF), (0x2B73A, 0x2B73F), + (0x2B81E, 0x2B81F), (0x2CEA2, 0x2CEAF), (0x2EBE1, 0x2EBEF), (0x2EE5E, 0x2F7FF), + (0x2FA1E, 0x2FFFF), (0x3134B, 0x3134F), (0x323B0, 0xE00FF), (0xE01F0, 0x10FFFF), +]; diff --git a/nvx-rs/src/host/python/uuid.rs b/nvx-rs/src/host/python/uuid.rs new file mode 100644 index 000000000..46bff1d6e --- /dev/null +++ b/nvx-rs/src/host/python/uuid.rs @@ -0,0 +1,98 @@ +//! Python's `uuid.UUID` parser. + +use super::builtins::parse_digits; + +/// Returns `str(uuid.UUID(text))`, the lowercase hyphenated form of the UUID in `text`, or `None` +/// if `uuid.UUID` refuses `text`. +/// +/// `uuid.UUID` removes every `urn:` and then every `uuid:`, strips braces from both ends, removes +/// every hyphen, and requires 32 characters to remain, which it reads with `int(text, 16)`. It +/// therefore accepts braces, a `urn:uuid:` prefix, the form without hyphens, and uppercase +/// digits, and also Unicode decimal digits, a `0x` prefix, a plus sign, underscores, and +/// surrounding whitespace, as long as 32 characters remain. A minus sign is a hyphen, which is +/// removed. +pub(crate) fn parse_uuid(text: &str) -> Option { + let hex = text.replace("urn:", "").replace("uuid:", ""); + let hex = hex.trim_matches(['{', '}']).replace('-', ""); + if hex.chars().count() != 32 { + return None; + } + let (_, digits) = parse_digits(&hex, 16).ok()?; + // At most 32 hexadecimal digits remain, so the value fits in 128 bits. + let value = digits + .iter() + .fold(0u128, |value, &digit| (value << 4) | u128::from(digit)); + let hex = format!("{value:032x}"); + Some(format!( + "{}-{}-{}-{}-{}", + &hex[..8], + &hex[8..12], + &hex[12..16], + &hex[16..20], + &hex[20..] + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn uuids_take_every_form_that_python_accepts() { + let canonical = "12345678-1234-5678-1234-567812345678"; + for text in [ + "12345678-1234-5678-1234-567812345678", + "{12345678-1234-5678-1234-567812345678}", + "urn:uuid:12345678-1234-5678-1234-567812345678", + "12345678123456781234567812345678", + "12345678-1234-5678-1234-567812345678" + .to_uppercase() + .as_str(), + "{{urn:12345678-1234-5678-1234-567812345678}}", + "{urn:uuid:12345678-1234-5678-1234-567812345678}", + ] { + assert_eq!(parse_uuid(text).as_deref(), Some(canonical), "{text:?}"); + } + // Each prefix is removed in one pass, as `str.replace` removes it. + assert_eq!( + parse_uuid("uuuuid:id:12345678123456781234567812345678"), + None + ); + // `int(text, 16)` reads a prefix, whitespace, a sign, and Unicode digits too. + for (text, expected) in [ + ( + format!("0x{}", "1".repeat(30)), + "00111111-1111-1111-1111-111111111111", + ), + ( + format!(" {}", "a".repeat(31)), + "0aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + ), + ( + format!("+{}", "1".repeat(31)), + "01111111-1111-1111-1111-111111111111", + ), + ( + format!("1_{}", "1".repeat(30)), + "01111111-1111-1111-1111-111111111111", + ), + ( + format!("\u{661}{}", "0".repeat(31)), + "10000000-0000-0000-0000-000000000000", + ), + ] { + assert_eq!(parse_uuid(&text).as_deref(), Some(expected), "{text:?}"); + } + for text in [ + "not-a-uuid".to_owned(), + String::new(), + // Hyphens are removed, a minus sign with them. + format!("-{}", "0".repeat(31)), + format!("{}g", "1".repeat(31)), + "1".repeat(33), + format!("{}_", "1".repeat(31)), + ] { + assert_eq!(parse_uuid(&text), None, "{text:?}"); + } + } +} diff --git a/nvx-rs/src/host/python/value.rs b/nvx-rs/src/host/python/value.rs new file mode 100644 index 000000000..454ae24a9 --- /dev/null +++ b/nvx-rs/src/host/python/value.rs @@ -0,0 +1,243 @@ +//! The built-in conversions that `nvx.py` applies to the values that `json.loads` returns: +//! `str()`, `int()`, and the `==` with which `in` compares a value with integers. +//! +//! A [`Node`] of a [`Document`] is the value that Python's decoder builds: `None`, `bool`, `int`, +//! `float`, `str`, `list`, or `dict`. [`py_str`] gives `str(value)`, which is the string itself +//! for a string and `repr(value)` for anything else, with floats as Python's `repr` writes them; +//! [`py_int`] gives `int(value)`; and [`equals_int`] gives `value == n`, under which `True` +//! equals 1 and `1.0` equals 1. + +use super::builtins::{Int, repr}; +use super::json::{Document, Node, NodeId}; + +/// Why `int(value)` fails. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum IntError { + /// `TypeError` or `ValueError`, which callers of `int` usually catch. + Invalid, + /// `OverflowError` for an infinite float, which `nvx.py` does not catch. + Overflow, +} + +/// Returns `int(value)` for the value at `id`: an integer as it is, a float truncated toward +/// zero, `True` and `False` as 1 and 0, and a string as `int(text)` reads it. +pub(crate) fn py_int(document: &Document, id: NodeId) -> Result { + match document.node(id) { + Node::Integer(literal) => Int::parse(literal).map_err(|_| IntError::Invalid), + Node::Bool(value) => Ok(Int::from(u64::from(*value))), + Node::Float(value) if value.is_nan() => Err(IntError::Invalid), + Node::Float(value) if value.is_infinite() => Err(IntError::Overflow), + Node::Float(value) => { + Int::parse(&format!("{:.0}", value.trunc())).map_err(|_| IntError::Invalid) + } + Node::String(text) => text + .as_str() + .and_then(|text| Int::parse(text).ok()) + .ok_or(IntError::Invalid), + Node::Null | Node::Array(_) | Node::Object(_) => Err(IntError::Invalid), + } +} + +/// Returns `str(value)` for the value at `id`, or `None` for a string that is not valid +/// Unicode, which holds unpaired surrogates. +pub(crate) fn py_str(document: &Document, id: NodeId) -> Option { + match document.node(id) { + Node::String(text) => text.as_str().map(str::to_owned), + _ => py_repr(document, id), + } +} + +/// Returns `repr(value)` for the value at `id`, or `None` if a string in it is not valid +/// Unicode. +pub(crate) fn py_repr(document: &Document, id: NodeId) -> Option { + Some(match document.node(id) { + Node::Null => "None".to_owned(), + Node::Bool(true) => "True".to_owned(), + Node::Bool(false) => "False".to_owned(), + Node::Integer(literal) => Int::parse(literal).ok()?.to_string(), + Node::Float(value) => float_repr(*value), + Node::String(text) => repr(text.as_str()?), + Node::Array(items) => { + let items: Option> = + items.iter().map(|&item| py_repr(document, item)).collect(); + format!("[{}]", items?.join(", ")) + } + Node::Object(members) => { + // A dict keeps the first position of a repeated name, with its last value. + let mut entries: Vec<(&[u8], String, NodeId)> = Vec::new(); + for (name, value) in members { + match entries.iter_mut().find(|entry| entry.0 == name.as_bytes()) { + Some(entry) => entry.2 = *value, + None => entries.push((name.as_bytes(), repr(name.as_str()?), *value)), + } + } + let entries: Option> = entries + .iter() + .map(|(_, name, value)| Some(format!("{name}: {}", py_repr(document, *value)?))) + .collect(); + format!("{{{}}}", entries?.join(", ")) + } + }) +} + +/// Returns whether the value at `id` equals the integer `expected` under Python's `==`. +pub(crate) fn equals_int(document: &Document, id: NodeId, expected: u64) -> bool { + match document.node(id) { + Node::Integer(literal) => Int::parse(literal).ok() == Some(Int::from(expected)), + Node::Bool(value) => u64::from(*value) == expected, + // Every integer that NVX compares with is exact as a float. + Node::Float(value) => *value == expected as f64, + _ => false, + } +} + +/// Returns `repr(value)` for a float: the shortest digits that read back as `value`, in fixed +/// notation from 1e-4 up to 1e16 with at least one digit after the point, and in exponent +/// notation otherwise, with a sign and at least two exponent digits. +pub(crate) fn float_repr(value: f64) -> String { + if value.is_nan() { + return "nan".to_owned(); + } + if value.is_infinite() { + return if value > 0.0 { "inf" } else { "-inf" }.to_owned(); + } + let sign = if value.is_sign_negative() { "-" } else { "" }; + // Rust's `{:e}` writes the shortest digits that read back as the value. + let scientific = format!("{:e}", value.abs()); + let (mantissa, exponent) = scientific + .split_once('e') + .expect("`{:e}` writes an exponent"); + let digits: String = mantissa.chars().filter(|&c| c != '.').collect(); + let exponent: i32 = exponent.parse().expect("`{:e}` writes a decimal exponent"); + // The position of the decimal point relative to the start of the digits. + let point = exponent + 1; + let length = digits.len() as i32; + let body = if (-3..=16).contains(&point) { + if point <= 0 { + format!("0.{}{digits}", "0".repeat((-point) as usize)) + } else if point >= length { + format!("{digits}{}.0", "0".repeat((point - length) as usize)) + } else { + let (whole, fraction) = digits.split_at(point as usize); + format!("{whole}.{fraction}") + } + } else { + let (first, rest) = digits.split_at(1); + let fraction = if rest.is_empty() { + String::new() + } else { + format!(".{rest}") + }; + let exponent = point - 1; + let exponent_sign = if exponent < 0 { '-' } else { '+' }; + format!("{first}{fraction}e{exponent_sign}{:02}", exponent.abs()) + }; + format!("{sign}{body}") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::host::python::json::{Loads, read}; + + fn document(text: &str) -> Document { + read(text, &mut Loads).unwrap() + } + + #[test] + fn floats_print_as_python_repr() { + for (value, expected) in [ + (1.0, "1.0"), + (0.1, "0.1"), + (-0.0, "-0.0"), + (1e16, "1e+16"), + (1e15, "1000000000000000.0"), + (123456789012345678.0, "1.2345678901234568e+17"), + (0.0001, "0.0001"), + (0.00001, "1e-05"), + (1.5e-7, "1.5e-07"), + (2.5, "2.5"), + (1e300, "1e+300"), + (f64::INFINITY, "inf"), + (f64::NEG_INFINITY, "-inf"), + (f64::NAN, "nan"), + (12345.678, "12345.678"), + ] { + assert_eq!(float_repr(value), expected, "{value:?}"); + } + } + + #[test] + fn values_convert_as_python_converts_them() { + let value = document( + r#"[1, -2.9, true, false, "7", " 8 ", "x", null, [1], {"a": 1}, 1e400, NaN, 1.0]"#, + ); + let Node::Array(items) = document_root(&value) else { + panic!("not an array") + }; + let ints: Vec> = items + .iter() + .map(|&item| py_int(&value, item).map(|value| value.to_string())) + .collect(); + let ok = |text: &str| Ok(text.to_owned()); + assert_eq!( + ints, + [ + ok("1"), + ok("-2"), + ok("1"), + ok("0"), + ok("7"), + ok("8"), + Err(IntError::Invalid), + Err(IntError::Invalid), + Err(IntError::Invalid), + Err(IntError::Invalid), + Err(IntError::Overflow), + Err(IntError::Invalid), + ok("1"), + ] + ); + let strings: Vec = items + .iter() + .map(|&item| py_str(&value, item).unwrap()) + .collect(); + assert_eq!( + strings, + [ + "1", "-2.9", "True", "False", "7", " 8 ", "x", "None", "[1]", "{'a': 1}", "inf", + "nan", "1.0" + ] + ); + let equal: Vec = items + .iter() + .map(|&item| equals_int(&value, item, 1)) + .collect(); + assert_eq!( + equal, + [ + true, false, true, false, false, false, false, false, false, false, false, false, + true + ] + ); + } + + #[test] + fn containers_print_as_python_repr() { + let value = document(r#"{"b": ["it's", 2.5, {"x": null}], "a": 1, "b": "last"}"#); + assert_eq!( + py_str(&value, value.root()).unwrap(), + "{'b': 'last', 'a': 1}" + ); + let value = document(r#"["it's", [2.5, {"x": null}]]"#); + assert_eq!( + py_str(&value, value.root()).unwrap(), + "[\"it's\", [2.5, {'x': None}]]" + ); + assert_eq!(py_str(&document(r#""\ud800""#), 0), None); + } + + fn document_root(document: &Document) -> &Node { + document.node(document.root()) + } +} diff --git a/nvx-rs/src/host/python/windows.rs b/nvx-rs/src/host/python/windows.rs new file mode 100644 index 000000000..d8804eb64 --- /dev/null +++ b/nvx-rs/src/host/python/windows.rs @@ -0,0 +1,539 @@ +//! The Win32 calls with which CPython's `nt` module stats files, reads links, and asks for final +//! path names. +//! +//! [`stat`] ports `win32_xstat_slow_impl`: it opens the path for its attributes only, and falls +//! back to `FindFirstFileW` when the path cannot be opened. Without links followed, only +//! `IO_REPARSE_TAG_SYMLINK` reparse points are links, as `os.lstat` reports them. +#![allow(unsafe_code)] + +use std::ffi::c_void; +use std::io; +use std::os::windows::ffi::OsStrExt; +use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; +use std::path::Path; +use std::{mem, ptr}; + +use windows_sys::Win32::Foundation::{ + ERROR_ACCESS_DENIED, ERROR_BAD_NET_NAME, ERROR_CANT_ACCESS_FILE, ERROR_FILE_NOT_FOUND, + ERROR_INVALID_FUNCTION, ERROR_INVALID_PARAMETER, ERROR_NOT_READY, ERROR_NOT_SUPPORTED, + ERROR_PATH_NOT_FOUND, ERROR_SHARING_VIOLATION, GENERIC_READ, GetLastError, HANDLE, + INVALID_HANDLE_VALUE, +}; +use windows_sys::Win32::Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_DIRECTORY, + FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_TAG_INFO, FILE_BASIC_INFO, + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, + FILE_TYPE_UNKNOWN, FileAttributeTagInfo, FileBasicInfo, FileIdInfo, FindClose, FindFirstFileW, + GetFileAttributesW, GetFileInformationByHandle, GetFileInformationByHandleEx, GetFileType, + GetFinalPathNameByHandleW, INVALID_FILE_ATTRIBUTES, OPEN_EXISTING, VOLUME_NAME_DOS, + WIN32_FIND_DATAW, +}; +use windows_sys::Win32::System::IO::DeviceIoControl; + +/// The reparse tag of a symbolic link. +const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000_000C; +/// The reparse tag of a junction, or mount point. +const IO_REPARSE_TAG_MOUNT_POINT: u32 = 0xA000_0003; +/// The control code that reads a reparse point's data (`winioctl.h`). +const FSCTL_GET_REPARSE_POINT: u32 = 0x0009_00A8; +/// The largest reparse point that `FSCTL_GET_REPARSE_POINT` returns. +const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; + +/// The type of file that `st_mode` gives. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum StatMode { + /// `S_IFREG`. + Regular, + /// `S_IFDIR`. + Directory, + /// `S_IFLNK`: a symbolic link, without links followed. + Link, + /// `S_IFCHR`, as `\\.\nul`. + Character, + /// `S_IFIFO`, as a named pipe. + Fifo, + /// `S_IFBLK`, as a volume. + Block, + /// No type, as an unknown device. + Unknown, +} + +/// The fields of `os.stat` and `os.lstat` that NVX reads. +#[derive(Clone, Copy, Debug)] +pub(crate) struct Status { + /// The type of file. + pub(crate) mode: StatMode, + /// `st_dev`: the volume serial number. + pub(crate) device: u64, + /// `st_ino`: the file ID. + pub(crate) inode: u128, +} + +/// Returns the NUL-terminated UTF-16 form of `path`, refusing an embedded NUL as Python does. +fn wide(path: &Path) -> io::Result> { + let mut units: Vec = path.as_os_str().encode_wide().collect(); + if units.contains(&0) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "embedded null character in path", + )); + } + units.push(0); + Ok(units) +} + +/// Returns the calling thread's last Win32 error. +fn last_error() -> u32 { + // SAFETY: GetLastError has no preconditions. + unsafe { GetLastError() } +} + +/// Returns the error with Win32 error code `code`. +fn error(code: u32) -> io::Error { + io::Error::from_raw_os_error(code as i32) +} + +/// Opens `path` with `access`, `share`, and `flags`, or returns the Win32 error code. +fn open(path: &[u16], access: u32, share: u32, flags: u32) -> Result { + // SAFETY: `path` is NUL-terminated, and the other arguments are plain values or null. + let handle = unsafe { + CreateFileW( + path.as_ptr(), + access, + share, + ptr::null(), + OPEN_EXISTING, + flags, + ptr::null_mut(), + ) + }; + if handle.is_null() || handle == INVALID_HANDLE_VALUE { + Err(last_error()) + } else { + // SAFETY: the handle was just opened, and nothing else owns it. + Ok(unsafe { OwnedHandle::from_raw_handle(handle as RawHandle) }) + } +} + +/// Borrows `handle` in the form that `windows-sys` takes. +fn raw(handle: &OwnedHandle) -> HANDLE { + handle.as_raw_handle() as HANDLE +} + +/// Reads the information of `class` into `T`, or returns the Win32 error code. `T` must be the +/// plain-data structure of `class`. +fn information(handle: &OwnedHandle, class: i32) -> Result { + // SAFETY: the structures that this module reads are plain data, for which all-zero bytes are + // valid. + let mut value: T = unsafe { mem::zeroed() }; + // SAFETY: the handle is open, and the buffer is a `T` of the size that is passed. + let ok = unsafe { + GetFileInformationByHandleEx( + raw(handle), + class, + (&raw mut value).cast::(), + mem::size_of::() as u32, + ) + }; + if ok == 0 { + Err(last_error()) + } else { + Ok(value) + } +} + +/// The attributes and reparse tag that `FindFirstFileW` gives for `path`, as CPython's +/// `attributes_from_dir` reads them, with the file's real name. +struct FindData { + /// The file attributes. + attributes: u32, + /// The reparse tag, or zero if the file is not a reparse point. + reparse_tag: u32, + /// The name of the file as the directory holds it. + name: Vec, +} + +/// Calls `FindFirstFileW` for `path`, which must be NUL-terminated, or returns the Win32 error. +fn find_first_file(path: &[u16]) -> Result { + // SAFETY: WIN32_FIND_DATAW is plain data, for which all-zero bytes are valid. + let mut data: WIN32_FIND_DATAW = unsafe { mem::zeroed() }; + // SAFETY: the path is NUL-terminated and the output pointer is valid. + let handle = unsafe { FindFirstFileW(path.as_ptr(), &mut data) }; + if handle == INVALID_HANDLE_VALUE { + return Err(last_error()); + } + // SAFETY: the handle is a find handle that was just opened. + unsafe { FindClose(handle) }; + let length = data + .cFileName + .iter() + .position(|&unit| unit == 0) + .unwrap_or(data.cFileName.len()); + Ok(FindData { + attributes: data.dwFileAttributes, + reparse_tag: if data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + data.dwReserved0 + } else { + 0 + }, + name: data.cFileName[..length].to_vec(), + }) +} + +/// Returns the find data of `path` as `attributes_from_dir` reads it: a path that ends in +/// separators is queried without them, and a root has nothing to query. +fn attributes_from_dir(path: &[u16]) -> Result { + let separator = |unit: u16| unit == u16::from(b'\\') || unit == u16::from(b'/'); + let text = &path[..path.len() - 1]; + if text.last().is_some_and(|&unit| separator(unit)) { + let mut end = text.len() - 1; + while end > 0 && separator(text[end]) { + end -= 1; + } + if end == 0 || (end == 1 && text[1] == u16::from(b':')) { + // Nothing is left to query; CPython leaves the last error as it was. + return Err(last_error()); + } + let mut trimmed = text[..=end].to_vec(); + trimmed.push(0); + return find_first_file(&trimmed); + } + find_first_file(path) +} + +/// Returns whether a reparse tag names another file, as links and junctions do. +fn is_name_surrogate(tag: u32) -> bool { + tag & 0x2000_0000 != 0 +} + +/// Returns the type of file that a file's attributes and reparse tag give. +fn mode(attributes: u32, reparse_tag: u32) -> StatMode { + if attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 && reparse_tag == IO_REPARSE_TAG_SYMLINK { + StatMode::Link + } else if attributes & FILE_ATTRIBUTE_DIRECTORY != 0 { + StatMode::Directory + } else { + StatMode::Regular + } +} + +/// Returns `os.stat(path)`, or `os.lstat(path)` without `traverse`, as CPython's +/// `win32_xstat_slow_impl` computes it. +pub(crate) fn stat(path: &Path, traverse: bool) -> io::Result { + stat_wide(&wide(path)?, traverse) +} + +/// Implements [`stat`] for a NUL-terminated path. +fn stat_wide(path: &[u16], mut traverse: bool) -> io::Result { + let mut unhandled_tag = false; + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !traverse { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + let handle = match open(path, FILE_READ_ATTRIBUTES, 0, flags) { + Ok(handle) => handle, + Err(code @ (ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION)) => { + // Read the attributes from the parent directory instead. + let data = attributes_from_dir(path).map_err(|find_error| match find_error { + ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND | ERROR_NOT_READY + | ERROR_BAD_NET_NAME => error(find_error), + _ => error(code), + })?; + if data.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + && (traverse || !is_name_surrogate(data.reparse_tag)) + { + // The link would have to be followed, but cannot be. + return Err(error(code)); + } + return Ok(Status { + mode: mode(data.attributes, data.reparse_tag), + device: 0, + inode: 0, + }); + } + Err(code @ ERROR_INVALID_PARAMETER) => { + // Devices such as `\\.\con` need read or write access. + open( + path, + FILE_READ_ATTRIBUTES | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + flags, + ) + .map_err(|_| error(code))? + } + Err(code @ ERROR_CANT_ACCESS_FILE) if traverse => { + // Open a reparse point that Windows cannot follow as itself. + traverse = false; + unhandled_tag = true; + open( + path, + FILE_READ_ATTRIBUTES, + 0, + flags | FILE_FLAG_OPEN_REPARSE_POINT, + ) + .map_err(|_| error(code))? + } + Err(code) => return Err(error(code)), + }; + + // SAFETY: the handle is open. + let file_type = unsafe { GetFileType(raw(&handle)) }; + if file_type != FILE_TYPE_DISK { + if file_type == FILE_TYPE_UNKNOWN && last_error() != 0 { + return Err(io::Error::last_os_error()); + } + // SAFETY: the path is NUL-terminated. + let attributes = unsafe { GetFileAttributesW(path.as_ptr()) }; + let mode = if attributes != INVALID_FILE_ATTRIBUTES + && attributes & FILE_ATTRIBUTE_DIRECTORY != 0 + { + StatMode::Directory + } else if file_type == FILE_TYPE_CHAR { + StatMode::Character + } else if file_type == FILE_TYPE_PIPE { + StatMode::Fifo + } else { + StatMode::Unknown + }; + return Ok(Status { + mode, + device: 0, + inode: 0, + }); + } + + let mut reparse_tag = 0; + if !traverse { + match information::(&handle, FileAttributeTagInfo) { + Ok(tag) if tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 => { + if is_name_surrogate(tag.ReparseTag) { + if unhandled_tag { + return Err(error(ERROR_CANT_ACCESS_FILE)); + } + } else if !unhandled_tag { + // Follow a reparse point that is not a link. + drop(handle); + return stat_wide(path, true); + } + reparse_tag = tag.ReparseTag; + } + Ok(_) => {} + // Devices without reparse tags are ordinary files. + Err(ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED) => {} + Err(code) => return Err(error(code)), + } + } + + // SAFETY: BY_HANDLE_FILE_INFORMATION is plain data, for which all-zero bytes are valid. + let mut file: BY_HANDLE_FILE_INFORMATION = unsafe { mem::zeroed() }; + // SAFETY: the handle is open and the output pointer is valid. + let queried = if unsafe { GetFileInformationByHandle(raw(&handle), &mut file) } == 0 { + Err(last_error()) + } else { + information::(&handle, FileBasicInfo).map(|_| ()) + }; + match queried { + Ok(()) => {} + // Volumes and physical disks are block devices. + Err(ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED) => { + return Ok(Status { + mode: StatMode::Block, + device: 0, + inode: 0, + }); + } + Err(code) => return Err(error(code)), + } + let id = information::(&handle, FileIdInfo).ok(); + let device = id.map_or(u64::from(file.dwVolumeSerialNumber), |id| { + id.VolumeSerialNumber + }); + let inode = match id.map_or(0, |id| u128::from_le_bytes(id.FileId.Identifier)) { + 0 => (u128::from(file.nFileIndexHigh) << 32) | u128::from(file.nFileIndexLow), + inode => inode, + }; + Ok(Status { + mode: mode(file.dwFileAttributes, reparse_tag), + device, + inode, + }) +} + +/// Returns `os.fstat` of an open file, as CPython's `_Py_fstat_noraise` computes it: a disk file's +/// type, identity, and link count come from `GetFileInformationByHandle` and `FILE_ID_INFO`, as +/// [`stat`] reads them, and its attributes tell whether the handle is a reparse point that was +/// opened as itself. +#[cfg(feature = "cli")] +pub(crate) fn fstat(file: &impl AsRawHandle) -> io::Result { + let handle = file.as_raw_handle() as HANDLE; + // SAFETY: the handle is open for as long as `file` is borrowed. + let file_type = unsafe { GetFileType(handle) }; + if file_type != FILE_TYPE_DISK { + if file_type == FILE_TYPE_UNKNOWN && last_error() != 0 { + return Err(io::Error::last_os_error()); + } + let mode = match file_type { + FILE_TYPE_CHAR => StatMode::Character, + FILE_TYPE_PIPE => StatMode::Fifo, + _ => StatMode::Unknown, + }; + return Ok(HandleStatus { + mode, + device: 0, + inode: 0, + links: 0, + reparse_point: false, + }); + } + // SAFETY: BY_HANDLE_FILE_INFORMATION is plain data, for which all-zero bytes are valid. + let mut information: BY_HANDLE_FILE_INFORMATION = unsafe { mem::zeroed() }; + // SAFETY: the handle is open and the output pointer is valid. + if unsafe { GetFileInformationByHandle(handle, &mut information) } == 0 { + return Err(io::Error::last_os_error()); + } + // SAFETY: FILE_ID_INFO is plain data, for which all-zero bytes are valid. + let mut id: FILE_ID_INFO = unsafe { mem::zeroed() }; + // SAFETY: the handle is open, and the buffer is a FILE_ID_INFO of the size that is passed. + let has_id = unsafe { + GetFileInformationByHandleEx( + handle, + FileIdInfo, + (&raw mut id).cast::(), + mem::size_of::() as u32, + ) + } != 0; + let device = if has_id { + id.VolumeSerialNumber + } else { + u64::from(information.dwVolumeSerialNumber) + }; + let inode = match has_id.then(|| u128::from_le_bytes(id.FileId.Identifier)) { + Some(inode) if inode != 0 => inode, + _ => (u128::from(information.nFileIndexHigh) << 32) | u128::from(information.nFileIndexLow), + }; + Ok(HandleStatus { + mode: mode(information.dwFileAttributes, 0), + device, + inode, + links: information.nNumberOfLinks, + reparse_point: information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0, + }) +} + +/// The fields of `os.fstat` that NVX reads. +#[cfg(feature = "cli")] +#[derive(Clone, Copy, Debug)] +pub(crate) struct HandleStatus { + /// The type of file. `os.fstat` reads no reparse tag, so a link opened as itself is no + /// [`StatMode::Link`]. + pub(crate) mode: StatMode, + /// `st_dev`: the volume serial number. + pub(crate) device: u64, + /// `st_ino`: the file ID. + pub(crate) inode: u128, + /// `st_nlink`: the number of names that link the file. + pub(crate) links: u32, + /// Whether `st_file_attributes` has `FILE_ATTRIBUTE_REPARSE_POINT`. + pub(crate) reparse_point: bool, +} + +/// Returns `nt._getfinalpathname(path)`: the path that Windows gives for the file, with its +/// `\\?\` prefix. +pub(crate) fn final_path_name(path: &Path) -> io::Result { + let path = wide(path)?; + let handle = open(&path, 0, 0, FILE_FLAG_BACKUP_SEMANTICS).map_err(error)?; + let mut buffer = vec![0u16; 260]; + loop { + // SAFETY: the handle is open and the buffer holds `buffer.len()` units. + let length = unsafe { + GetFinalPathNameByHandleW( + raw(&handle), + buffer.as_mut_ptr(), + buffer.len() as u32, + VOLUME_NAME_DOS, + ) + } as usize; + if length == 0 { + return Err(io::Error::last_os_error()); + } + if length < buffer.len() { + buffer.truncate(length); + break; + } + buffer.resize(length, 0); + } + String::from_utf16(&buffer).map_err(|_| super::not_unicode("a final path name")) +} + +/// Returns `nt._findfirstfile(path)`: the name of the file as its directory holds it. +pub(crate) fn find_first_file_name(path: &Path) -> io::Result { + let data = find_first_file(&wide(path)?).map_err(error)?; + String::from_utf16(&data.name).map_err(|_| super::not_unicode("a file name")) +} + +/// Returns `nt.readlink(path)`: the substitute name of a symbolic link or junction, with `\??\` +/// turned into `\\?\`, or `None` for another kind of reparse point. +pub(crate) fn read_link(path: &Path) -> io::Result> { + let path = wide(path)?; + let handle = open( + &path, + 0, + 0, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + ) + .map_err(error)?; + let mut buffer = vec![0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + let mut returned = 0u32; + // SAFETY: the handle is open, there is no input buffer, and the output buffer holds + // `buffer.len()` bytes. + let ok = unsafe { + DeviceIoControl( + raw(&handle), + FSCTL_GET_REPARSE_POINT, + ptr::null(), + 0, + buffer.as_mut_ptr().cast::(), + buffer.len() as u32, + &mut returned, + ptr::null_mut(), + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + let data = &buffer[..(returned as usize).min(buffer.len())]; + let u16_at = |offset: usize| -> Option { + Some(usize::from(u16::from_le_bytes( + data.get(offset..offset + 2)?.try_into().ok()?, + ))) + }; + let tag = data.get(..4).map_or(0, |bytes| { + u32::from_le_bytes(bytes.try_into().unwrap_or_default()) + }); + // The substitute name follows the fixed fields of REPARSE_DATA_BUFFER: a symbolic link has + // flags before its path buffer, and a junction does not. + let path_buffer = match tag { + IO_REPARSE_TAG_SYMLINK => 20, + IO_REPARSE_TAG_MOUNT_POINT => 16, + _ => return Ok(None), + }; + let malformed = || io::Error::new(io::ErrorKind::InvalidData, "malformed reparse point"); + let (offset, length) = ( + u16_at(8).ok_or_else(malformed)?, + u16_at(10).ok_or_else(malformed)?, + ); + let start = path_buffer + offset; + let bytes = data.get(start..start + length).ok_or_else(malformed)?; + let mut name: Vec = bytes + .chunks_exact(2) + .map(|pair| u16::from_le_bytes([pair[0], pair[1]])) + .collect(); + let nt_prefix = ['\\', '?', '?', '\\'].map(|c| c as u16); + if name.len() > 4 && name[..4] == nt_prefix { + name[1] = u16::from(b'\\'); + } + String::from_utf16(&name) + .map(Some) + .map_err(|_| super::not_unicode("a symbolic link's target")) +} diff --git a/nvx-rs/src/host/sandbox/golden.rs b/nvx-rs/src/host/sandbox/golden.rs new file mode 100644 index 000000000..5da660506 --- /dev/null +++ b/nvx-rs/src/host/sandbox/golden.rs @@ -0,0 +1,365 @@ +//! Checks the launch profile and the option checks against `tests/fixtures/sandbox.json` and +//! `tests/fixtures/sandbox_options.json`, which `scripts/nvx_rs_fixtures.py` records from +//! `scripts/nvx_tools/sandbox.py` and `scripts/nvx.py`. +//! +//! The fixtures hold the paths of both `pathlib` flavors, with a fixed current directory, so +//! every case runs on every host. The cases that read files run on files of a temporary +//! directory. + +use std::io; + +use serde_json::{Value, json}; + +use super::mount::{ + aggregate_child_name, mounts_openvmm_arguments_with, parse_mount_value, validate_mount_target, +}; +use super::options::{ + PolicyPathOption, SandboxOperation, SandboxOptions, sandbox_mounts_with, + validate_exec_environment, validate_run_share_targets, validate_sandbox_systemd_policy, +}; +use super::*; +use crate::host::python::golden::{Mismatches, cases, fixture, flavor, hex, text, texts}; + +/// Returns the message of a result's error, or `None` if it succeeded. +fn error(result: &Result) -> Option { + result.as_ref().err().map(ToString::to_string) +} + +/// Returns `Path.cwd()` of the fixture for paths of `flavor`. +fn cwd(fixture: &Value, flavor: Flavor) -> impl Fn() -> io::Result { + let name = match flavor { + Flavor::Posix => "posix", + Flavor::Windows => "windows", + }; + let directory = PurePath::new(flavor, text(&fixture["cwds"], name)); + move || Ok(directory.clone()) +} + +/// Returns the fields of a share as the generator records them. +fn mount_fields(mount: &SandboxMount) -> Value { + let policy = mount.policy(); + json!({ + "guest_target": mount.guest_target(), + "host_path": mount.host_path().to_str().expect("a Unicode path"), + "access": mount.access().as_str(), + "denied": policy.denied, + "allowed": policy.allowed, + "writable": policy.writable, + "owner": mount.owner().as_str(), + "kind": mount.kind().as_str(), + }) +} + +/// Parses the share of a generator's specification. +fn parse_mount(flavor: Flavor, spec: &Value) -> Result { + let policy = MountPolicy { + denied: texts(spec, "denied"), + allowed: texts(spec, "allowed"), + writable: texts(spec, "writable"), + }; + SandboxMount::parse_in( + flavor, + text(spec, "value"), + policy, + MountOwner::from_name(text(spec, "owner"))?, + MountKind::from_name(text(spec, "kind"))?, + ) +} + +/// Returns the expected outcome of a case: its `key` field, or its `error` field. +fn outcome(case: &Value, key: &str) -> std::result::Result { + match case.get("error") { + Some(Value::String(message)) => Err(message.clone()), + _ => Ok(case[key].clone()), + } +} + +#[test] +fn identities_and_layers_parse_as_python_parses_them() { + let fixture = fixture("sandbox.json"); + let mut mismatches = Mismatches::new("identities"); + for case in cases(&fixture, "identities") { + let actual = parse_workload_identity(text(case, "value")) + .map(|(uid, gid)| json!([uid, gid])) + .map_err(|error| error.to_string()); + mismatches.check(case, outcome(case, "identity"), actual); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("layers"); + for case in cases(&fixture, "layers") { + let expected = + outcome(case, "role").map(|_| json!([case["role"], case["path"], case["uuid"]])); + let actual = SandboxLayer::parse_in(flavor(case), text(case, "value")) + .map(|layer| { + json!([ + layer.role().as_str(), + layer.pure_path().as_str(), + layer.uuid() + ]) + }) + .map_err(|error| error.to_string()); + mismatches.check(case, expected, actual); + } + mismatches.finish(); +} + +#[test] +fn mount_values_targets_and_child_names_match_python() { + let fixture = fixture("sandbox.json"); + let mut mismatches = Mismatches::new("mount_values"); + for case in cases(&fixture, "mount_values") { + let expected = outcome(case, "target") + .map(|_| json!([case["target"], case["host_path"], case["access"]])); + let actual = parse_mount_value(text(case, "value")) + .map(|(target, host_path, access)| json!([target, host_path, access.as_str()])) + .map_err(|error| error.to_string()); + mismatches.check(case, expected, actual); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("mount_targets"); + for case in cases(&fixture, "mount_targets") { + mismatches.check( + case, + case["error"].as_str().map(str::to_owned), + error(&validate_mount_target(text(case, "target"))), + ); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("child_names"); + for case in cases(&fixture, "child_names") { + let index = usize::try_from(case["index"].as_u64().unwrap()).unwrap(); + mismatches.check( + case, + text(case, "name"), + aggregate_child_name(index, text(case, "target")).as_str(), + ); + } + mismatches.finish(); +} + +#[test] +fn mounts_lower_to_the_arguments_that_python_gives() { + let fixture = fixture("sandbox.json"); + let mut mismatches = Mismatches::new("mounts"); + for case in cases(&fixture, "mounts") { + let flavor = flavor(case); + let cwd = cwd(&fixture, flavor); + let expected = outcome(case, "mount").map(|_| { + json!([ + case["mount"], + case["arguments"], + case["fragment"], + case["absolute"], + case["absolute_policy"], + ]) + }); + let actual = parse_mount(flavor, case).and_then(|mount| { + let policy: Vec = mount + .absolute_policy_paths_with(&cwd)? + .into_iter() + .map(|(kind, paths)| json!([kind.as_str(), paths])) + .collect(); + Ok(json!([ + mount_fields(&mount), + mounts_openvmm_arguments_with(std::slice::from_ref(&mount), &cwd)?, + mount.command_line_fragment(), + mount_fields(&mount.absolute_with(&cwd)?), + policy, + ])) + }); + mismatches.check(case, expected, actual.map_err(|error| error.to_string())); + } + mismatches.finish(); +} + +#[test] +fn launches_give_the_arguments_and_command_lines_that_python_gives() { + let fixture = fixture("sandbox.json"); + let mut mismatches = Mismatches::new("launches"); + for case in cases(&fixture, "launches") { + let flavor = flavor(case); + let layers = texts(case, "layers") + .iter() + .map(|value| SandboxLayer::parse_in(flavor, value)) + .collect::>>() + .expect("the generator records launches whose layers parse"); + let mounts = case["mounts"] + .as_array() + .unwrap() + .iter() + .map(|spec| parse_mount(flavor, spec)) + .collect::>>() + .expect("the generator records launches whose shares parse"); + let limit = |key: &str| case[key].as_str().map(|value| Int::parse(value).unwrap()); + let identity = &case["identity"]; + let id = |index: usize| u32::try_from(identity[index].as_u64().unwrap()).unwrap(); + let settings = SandboxSettings { + entrypoint: text(case, "entrypoint").to_owned(), + args: texts(case, "args"), + hostname: text(case, "hostname").to_owned(), + workload_identity: (id(0), id(1)), + memory_max: limit("memory_max"), + pids_max: limit("pids_max"), + mounts, + }; + let expected = match (case.get("error"), case.get("kernel_error")) { + (Some(message), _) => Err(message.as_str().unwrap().to_owned()), + (None, kernel_error) => Ok(json!([ + case["arguments"], + case.get("kernel_command_line").unwrap_or(&Value::Null), + kernel_error.unwrap_or(&Value::Null), + ])), + }; + let actual = SandboxLaunch::new_in(flavor, layers, text(case, "scratch"), settings) + .and_then(|launch| { + let arguments = launch.openvmm_arguments_with(&cwd(&fixture, flavor))?; + let command_line = launch.kernel_command_line(text(case, "command_line")); + Ok(json!([ + arguments, + command_line.as_ref().ok(), + error(&command_line), + ])) + }) + .map_err(|error| error.to_string()); + mismatches.check(case, expected, actual); + } + mismatches.finish(); +} + +#[test] +fn sandbox_option_helpers_match_nvx_py() { + let fixture = fixture("sandbox_options.json"); + let mut mismatches = Mismatches::new("run_share_targets"); + for case in cases(&fixture, "run_share_targets") { + let targets = texts(case, "targets"); + let targets: Vec<&str> = targets.iter().map(String::as_str).collect(); + mismatches.check( + case, + case["error"].as_str().map(str::to_owned), + error(&validate_run_share_targets(&targets)), + ); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("sandbox_mounts"); + for case in cases(&fixture, "sandbox_mounts") { + let files = texts(case, "files"); + let policy_paths: Vec = case["policy"] + .as_array() + .unwrap() + .iter() + .map(|option| PolicyPathOption { + kind: match option[0].as_str().unwrap() { + "denied" => PolicyKind::Denied, + "allowed" => PolicyKind::Allowed, + _ => PolicyKind::Writable, + }, + preceding_mount: option[1] + .as_u64() + .map(|index| usize::try_from(index).unwrap()), + path: option[2].as_str().unwrap().to_owned(), + }) + .collect(); + let owner = case["owner"] + .as_str() + .map(|owner| MountOwner::from_name(owner).unwrap()); + let kind_of = |mount: &SandboxMount| { + let host_path = mount.host_path().to_str().unwrap(); + if files.iter().any(|file| file == host_path) { + MountKind::File + } else { + MountKind::Directory + } + }; + let actual = sandbox_mounts_with( + flavor(case), + &texts(case, "mounts"), + &policy_paths, + owner, + &kind_of, + ) + .map(|mounts| Value::Array(mounts.iter().map(mount_fields).collect())) + .map_err(|error| error.to_string()); + mismatches.check(case, outcome(case, "result"), actual); + } + mismatches.finish(); + + let mut mismatches = Mismatches::new("environments"); + for case in cases(&fixture, "environments") { + mismatches.check( + case, + case["error"].as_str().map(str::to_owned), + error(&validate_exec_environment(&texts(case, "entries"))), + ); + } + mismatches.finish(); +} + +/// Returns the bytes of a document of the generator: its text, its bytes in hex, or a number +/// of spaces. +fn document(case: &Value) -> Vec { + if let Some(text) = case["text"].as_str() { + text.as_bytes().to_vec() + } else if let Some(bytes) = case["bytes"].as_str() { + hex(bytes) + } else { + let spaces = case["spaces"].as_u64().expect("a document"); + vec![b' '; usize::try_from(spaces).unwrap()] + } +} + +#[test] +fn environment_files_and_distro_manifests_read_as_python_reads_them() { + let fixture = fixture("sandbox_options.json"); + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("environment.json"); + let path = path.to_str().unwrap(); + let shown = PurePath::native(path).as_str().to_owned(); + let mut mismatches = Mismatches::new("environment_files"); + for case in cases(&fixture, "environment_files") { + std::fs::write(path, document(case)).unwrap(); + let mut options = SandboxOptions::new(SandboxOperation::Exec); + options.environment_file = Some(path.to_owned()); + let expected = match case.get("environment") { + Some(environment) => Ok(environment.clone()), + None => Err(text(case, "error").to_owned()), + }; + let actual = options + .check() + .map(|environment| json!(environment)) + .map_err(|error| error.to_string().replace(&shown, "{path}")); + mismatches.check(case, expected, actual); + } + mismatches.finish(); + + let layer = directory.path().join("distro.erofs"); + std::fs::write(&layer, text(&fixture, "distro_layer")).unwrap(); + let manifest = directory.path().join("distro.erofs.manifest.json"); + let scratch = directory.path().join("scratch.ext4"); + let launch = SandboxLaunch::new( + vec![SandboxLayer::new( + LayerRole::Distro, + layer.to_str().unwrap(), + "11111111-1111-1111-1111-111111111111", + )], + scratch.to_str().unwrap(), + SandboxSettings { + entrypoint: "/sbin/init".to_owned(), + ..SandboxSettings::default() + }, + ) + .unwrap(); + let mut mismatches = Mismatches::new("distro_manifests"); + for case in cases(&fixture, "distro_manifests") { + std::fs::write(&manifest, document(&case["manifest"])).unwrap(); + mismatches.check( + case, + case["error"].as_str().map(str::to_owned), + error(&validate_sandbox_systemd_policy(&launch)), + ); + } + mismatches.finish(); +} diff --git a/nvx-rs/src/host/sandbox/layer.rs b/nvx-rs/src/host/sandbox/layer.rs new file mode 100644 index 000000000..60826ffdc --- /dev/null +++ b/nvx-rs/src/host/sandbox/layer.rs @@ -0,0 +1,186 @@ +//! The read-only EROFS layers of a sandbox. + +use std::fmt; +use std::path::Path; + +use super::refused; +use crate::error::Result; +use crate::host::python::builtins::repr; +use crate::host::python::path::{Flavor, PurePath}; +use crate::host::python::uuid::parse_uuid; + +/// The role of a read-only layer, which fixes its place in the guest's stack of layers and the +/// MMIO base of its block device. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum LayerRole { + /// The distribution: the lowest layer. + Distro, + /// A language runtime over the distribution. + Runtime, + /// The workload's own files, on top. + Custom, +} + +/// The layer roles in the order in which the guest stacks them, bottom first: `LAYER_ROLES`. +pub const LAYER_ROLES: [LayerRole; 3] = [LayerRole::Distro, LayerRole::Runtime, LayerRole::Custom]; + +impl LayerRole { + /// Returns the role's name: `distro`, `runtime`, or `custom`. + pub const fn as_str(self) -> &'static str { + match self { + Self::Distro => "distro", + Self::Runtime => "runtime", + Self::Custom => "custom", + } + } + + /// Returns the role that `name` names, if any. + pub fn from_name(name: &str) -> Option { + LAYER_ROLES.into_iter().find(|role| role.as_str() == name) + } + + /// Returns the MMIO base of the role's sandbox block device: `BLOCK_MMIO_BASES`. + pub const fn mmio_base(self) -> u64 { + match self { + Self::Distro => 0xD000_3000, + Self::Runtime => 0xD000_4000, + Self::Custom => 0xD000_5000, + } + } +} + +impl fmt::Display for LayerRole { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +/// A read-only EROFS layer: its role, its host image, and the UUID of its filesystem. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SandboxLayer { + /// The layer's role. + role: LayerRole, + /// The image, as `pathlib` normalizes it. + path: PurePath, + /// The filesystem UUID, which the guest checks before it mounts the layer. + uuid: String, +} + +impl SandboxLayer { + /// Returns the layer of `role` at `path` with filesystem `uuid`, which are used as given, + /// except that `path` is normalized as `pathlib.Path` normalizes it. + pub fn new(role: LayerRole, path: &str, uuid: impl Into) -> Self { + Self::new_in(Flavor::NATIVE, role, path, uuid) + } + + /// Implements [`new`](Self::new) for paths of `flavor`. + pub(crate) fn new_in( + flavor: Flavor, + role: LayerRole, + path: &str, + uuid: impl Into, + ) -> Self { + Self { + role, + path: PurePath::new(flavor, path), + uuid: uuid.into(), + } + } + + /// Parses a `--layer` value, `ROLE,PATH,EROFS_UUID`. + /// + /// The UUID may take any form that Python's `uuid.UUID` accepts, such as braces, a + /// `urn:uuid:` prefix, no hyphens, or uppercase digits, and is kept in its lowercase + /// hyphenated form. Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). + pub fn parse(value: &str) -> Result { + Self::parse_in(Flavor::NATIVE, value) + } + + /// Implements [`parse`](Self::parse) for paths of `flavor`. + pub(crate) fn parse_in(flavor: Flavor, value: &str) -> Result { + let fields: Vec<&str> = value.split(',').collect(); + let [role, path, uuid] = fields[..] else { + return Err(refused("--layer must be ROLE,PATH,EROFS_UUID")); + }; + let Some(role) = LayerRole::from_name(role) else { + return Err(refused(format!( + "unsupported layer role {}; choose distro, runtime, custom", + repr(role) + ))); + }; + if path.is_empty() { + return Err(refused(format!("{role} layer path is empty"))); + } + let Some(uuid) = parse_uuid(uuid) else { + return Err(refused(format!( + "{role} layer UUID is invalid: {}", + repr(uuid) + ))); + }; + Ok(Self::new_in(flavor, role, path, uuid)) + } + + /// Returns the layer's role. + pub fn role(&self) -> LayerRole { + self.role + } + + /// Returns the layer's image. + pub fn path(&self) -> &Path { + self.path.as_path() + } + + /// Returns the layer's image as a pure path. + pub(crate) fn pure_path(&self) -> &PurePath { + &self.path + } + + /// Returns the UUID of the layer's filesystem. + pub fn uuid(&self) -> &str { + &self.uuid + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn message(value: &str) -> String { + SandboxLayer::parse(value).unwrap_err().to_string() + } + + #[test] + fn layers_parse_with_python_uuids() { + let layer = SandboxLayer::parse_in( + Flavor::Posix, + "custom,layers//custom.erofs,{22222222-2222-2222-2222-22222222222A}", + ) + .unwrap(); + assert_eq!(layer.role(), LayerRole::Custom); + assert_eq!(layer.pure_path().as_str(), "layers/custom.erofs"); + assert_eq!(layer.uuid(), "22222222-2222-2222-2222-22222222222a"); + let layer = SandboxLayer::parse_in( + Flavor::Windows, + "distro,C:/l/d.erofs,urn:uuid:11111111111111111111111111111111", + ) + .unwrap(); + assert_eq!(layer.pure_path().as_str(), "C:\\l\\d.erofs"); + assert_eq!(layer.uuid(), "11111111-1111-1111-1111-111111111111"); + } + + #[test] + fn layer_parser_rejects_invalid_role_and_uuid() { + assert_eq!( + message("unknown,layer.erofs,11111111-1111-1111-1111-111111111111"), + "unsupported layer role 'unknown'; choose distro, runtime, custom" + ); + assert_eq!( + message("distro,layer.erofs,not-a-uuid"), + "distro layer UUID is invalid: 'not-a-uuid'" + ); + assert_eq!(message("distro,,1"), "distro layer path is empty"); + for value in ["distro", "distro,a", "distro,a,b,c"] { + assert_eq!(message(value), "--layer must be ROLE,PATH,EROFS_UUID"); + } + } +} diff --git a/nvx-rs/src/host/sandbox/mod.rs b/nvx-rs/src/host/sandbox/mod.rs new file mode 100644 index 000000000..9d4a50ce2 --- /dev/null +++ b/nvx-rs/src/host/sandbox/mod.rs @@ -0,0 +1,432 @@ +//! The sandbox launch profile: a microVM that runs one workload in a container over read-only +//! EROFS layers and a private ext4 scratch disk, with live virtio-fs shares. +//! +//! This is the profile of `nvx.py sandbox`, ported from `scripts/nvx_tools/sandbox.py` with its +//! limits, option semantics, OpenVMM arguments, kernel command line, and messages: +//! +//! - [`SandboxLayer`]: one to three EROFS layers, `ROLE,PATH,EROFS_UUID`, each of a +//! [`LayerRole`], which the guest stacks in role order on its own block device. +//! - [`SandboxMount`]: a live share of a host directory or regular file at a guest target, with +//! its access mode, owner, and access-policy paths; [`mount`] holds the rules of guest targets, +//! the aggregate of several shares, and the checks of host paths. +//! - [`SandboxLaunch`]: the layers, the scratch disk, the entrypoint and its arguments, the +//! hostname, the workload identity, the resource limits, and the shares, with +//! [`openvmm_arguments`](SandboxLaunch::openvmm_arguments) and +//! [`kernel_command_line`](SandboxLaunch::kernel_command_line). +//! - [`options`]: the checks that `nvx.py sandbox` and `nvx.py run` apply to their options: the +//! attribution of access-policy paths to shares, the systemd policy, the targets of +//! `nvx.py run` shares, and the managed exec environment. +//! +//! Values are checked as Python checked them: whitespace is what `str.isspace` accepts, +//! integers are what `int` reads ([`Int`]), UUIDs are what `uuid.UUID` reads, host paths are +//! normalized as `pathlib` normalizes them, with backslashes on Windows, and messages quote +//! values with Python's `repr`. +//! +//! # Errors +//! +//! A request that the profile refuses fails with +//! [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported), and a missing layer or scratch +//! image with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable). A host query that fails +//! is [`ErrorKind::Io`](crate::ErrorKind::Io), with Python's form of the system's error, such as +//! `[Errno 13] Permission denied: '/path'`. + +pub mod layer; +pub mod mount; +pub mod options; + +use std::io; +use std::path::Path; + +pub use self::layer::{LAYER_ROLES, LayerRole, SandboxLayer}; +pub use self::mount::{MountAccess, MountKind, MountOwner, MountPolicy, PolicyKind, SandboxMount}; +use crate::error::{Error, Result}; +pub use crate::host::python::builtins::{Int, IntError}; +use crate::host::python::builtins::{is_space, split, strip}; +use crate::host::python::fs; +use crate::host::python::path::{Flavor, PurePath}; +use crate::net::is_valid_hostname; + +/// The MMIO base of the scratch disk's sandbox block device. +pub const SCRATCH_MMIO_BASE: u64 = 0xD000_6000; +/// The size of the x86 kernel command line, including its NUL. +pub const KERNEL_COMMAND_LINE_MAX_SIZE: usize = 2048; +/// The part of the kernel command line that OpenVMM keeps for its own tokens. +pub const OPENVMM_COMMAND_LINE_RESERVE: usize = 1024; +/// The part of the kernel command line that the sandbox profile may use, including the NUL and +/// the bootstrap tokens that OpenVMM appends for live shares. +pub const SANDBOX_COMMAND_LINE_MAX_SIZE: usize = + KERNEL_COMMAND_LINE_MAX_SIZE - OPENVMM_COMMAND_LINE_RESERVE; +/// The workload identity by default, `65534:65534`. +pub const DEFAULT_WORKLOAD_IDENTITY: (u32, u32) = (65534, 65534); +/// The entrypoint by default. +pub const DEFAULT_ENTRYPOINT: &str = "/bin/sh"; +/// The hostname by default. +pub const DEFAULT_HOSTNAME: &str = "nvx-sandbox"; + +/// Returns the error of a request that the profile refuses. +pub(crate) fn refused(message: impl Into) -> Error { + Error::unsupported(message) +} + +/// Parses a `--workload-user` value, `UID:GID`, with both IDs between 1 and 4294967295. +/// +/// Each ID is read as Python's `int` reads it, so a sign, underscores between digits, Unicode +/// decimal digits, and surrounding whitespace are accepted. Fails with +/// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). +pub fn parse_workload_identity(value: &str) -> Result<(u32, u32)> { + let fields: Vec<&str> = value.split(':').collect(); + let [uid, gid] = fields[..] else { + return Err(refused("--workload-user must be UID:GID")); + }; + let (Ok(uid), Ok(gid)) = (Int::parse(uid), Int::parse(gid)) else { + return Err(refused("--workload-user must contain numeric UID and GID")); + }; + let id = |value: &Int, name: &str| { + value.to_u32().filter(|&id| id != 0).ok_or_else(|| { + refused(format!( + "--workload-user {name} must be between 1 and 4294967295" + )) + }) + }; + Ok((id(&uid, "UID")?, id(&gid, "GID")?)) +} + +/// Checks that `path` is a regular file, following links, as `require_file` of `nvx.py` does. +/// +/// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) and the message +/// `{description} not found: {path}`, with `path` normalized as `pathlib.Path` normalizes it. +pub fn require_file(path: &Path, description: &str) -> Result<()> { + match path.to_str() { + Some(text) => require_pure_file(&PurePath::native(text), description), + None if fs::is_file(path) => Ok(()), + None => Err(Error::unavailable(format!( + "{description} not found: {}", + path.display() + ))), + } +} + +/// Refuses a disk path that OpenVMM's `--microvm-sandbox-block` value could not hold: one with +/// a comma or semicolon. +fn reject_disk_path(path: &PurePath) -> Result<()> { + let value = path.as_str(); + if value.contains([',', ';']) { + return Err(refused(format!( + "disk paths containing commas or semicolons are unsupported: {value}" + ))); + } + Ok(()) +} + +/// The settings of a [`SandboxLaunch`] besides its layers and scratch disk, with the defaults of +/// `nvx.py sandbox`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SandboxSettings { + /// The absolute path of the workload's program in the container, without whitespace. + pub entrypoint: String, + /// The workload's arguments, each nonempty and without whitespace. + pub args: Vec, + /// The workload's hostname: a lowercase RFC 1123 label of up to 63 characters. + pub hostname: String, + /// The workload's UID and GID, neither of them root's. + pub workload_identity: (u32, u32), + /// The workload's memory limit in bytes, `memory.max` of its cgroup, if positive. + pub memory_max: Option, + /// The most processes that the workload may run, if positive; the guest's own init counts + /// too, so its `pids.max` is one more. + pub pids_max: Option, + /// The live shares, in child order. + pub mounts: Vec, +} + +impl Default for SandboxSettings { + fn default() -> Self { + Self { + entrypoint: DEFAULT_ENTRYPOINT.to_owned(), + args: Vec::new(), + hostname: DEFAULT_HOSTNAME.to_owned(), + workload_identity: DEFAULT_WORKLOAD_IDENTITY, + memory_max: None, + pids_max: None, + mounts: Vec::new(), + } + } +} + +/// The launch of a sandbox: everything that its OpenVMM arguments and kernel command line +/// depend on. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SandboxLaunch { + /// The read-only layers, in the order given. + layers: Vec, + /// The ext4 scratch image. + scratch: PurePath, + /// The other settings. + settings: SandboxSettings, +} + +impl SandboxLaunch { + /// Returns the launch of `layers` over the ext4 image at `scratch`, which is normalized as + /// `pathlib.Path` normalizes it. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported), checking in this + /// order: one to three layers, no duplicate roles, the entrypoint, the arguments, the + /// hostname, a non-root identity, positive limits, and shares whose targets do not overlap + /// and that have one owner. + pub fn new( + layers: Vec, + scratch: &str, + settings: SandboxSettings, + ) -> Result { + Self::new_in(Flavor::NATIVE, layers, scratch, settings) + } + + /// Implements [`new`](Self::new) for a scratch path of `flavor`. + pub(crate) fn new_in( + flavor: Flavor, + layers: Vec, + scratch: &str, + settings: SandboxSettings, + ) -> Result { + if !(1..=LAYER_ROLES.len()).contains(&layers.len()) { + return Err(refused("a sandbox requires one to three read-only layers")); + } + let mut duplicates: Vec<&str> = LAYER_ROLES + .iter() + .filter(|&&role| layers.iter().filter(|layer| layer.role() == role).count() > 1) + .map(|role| role.as_str()) + .collect(); + if !duplicates.is_empty() { + duplicates.sort_unstable(); + return Err(refused(format!( + "duplicate sandbox layer role(s): {}", + duplicates.join(", ") + ))); + } + let SandboxSettings { + entrypoint, + args, + hostname, + workload_identity: (uid, gid), + memory_max, + pids_max, + mounts, + } = &settings; + if !entrypoint.starts_with('/') || entrypoint.chars().any(is_space) { + return Err(refused( + "sandbox entrypoint must be an absolute path without spaces", + )); + } + if args + .iter() + .any(|argument| argument.is_empty() || argument.chars().any(is_space)) + { + return Err(refused( + "sandbox arguments must be nonempty and contain no spaces", + )); + } + if !is_valid_hostname(hostname) { + return Err(refused( + "sandbox hostname must be a lowercase RFC 1123 label up to 63 characters", + )); + } + if *uid == 0 || *gid == 0 { + return Err(refused( + "sandbox workload identity must be a non-root UID:GID", + )); + } + for (name, value) in [("memory-max", memory_max), ("pids-max", pids_max)] { + if value.as_ref().is_some_and(|value| !value.is_positive()) { + return Err(refused(format!("--{name} must be positive"))); + } + } + mount::validate_mounts(mounts)?; + Ok(Self { + layers, + scratch: PurePath::new(flavor, scratch), + settings, + }) + } + + /// Returns the layers, in the order given. + pub fn layers(&self) -> &[SandboxLayer] { + &self.layers + } + + /// Returns the scratch image. + pub fn scratch(&self) -> &Path { + self.scratch.as_path() + } + + /// Returns the settings besides the layers and the scratch image. + pub fn settings(&self) -> &SandboxSettings { + &self.settings + } + + /// Returns the shares. + pub fn mounts(&self) -> &[SandboxMount] { + &self.settings.mounts + } + + /// Checks the launch against the host: each layer and the scratch image must be a regular + /// file whose path has no comma or semicolon, each share must pass + /// [`SandboxMount::validated`], and their host paths [`mount::validate_mount_host_paths`]. + /// + /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) for a missing image, + /// and otherwise as those checks do. + pub fn validated(&self) -> Result<()> { + self.validated_with(&mount::HostQueries::native()) + } + + /// Implements [`validated`](Self::validated) with the given host queries. + pub(crate) fn validated_with(&self, host: &mount::HostQueries<'_>) -> Result<()> { + for layer in &self.layers { + require_pure_file(layer.pure_path(), &format!("{} EROFS layer", layer.role()))?; + reject_disk_path(layer.pure_path())?; + } + require_pure_file(&self.scratch, "ext4 scratch image")?; + reject_disk_path(&self.scratch)?; + for mount in &self.settings.mounts { + mount.validated()?; + } + mount::validate_mount_host_paths_with(&self.settings.mounts, host) + } + + /// Returns the layers in role order: distro, runtime, then custom. + pub fn ordered_layers(&self) -> Vec<&SandboxLayer> { + LAYER_ROLES + .iter() + .filter_map(|&role| self.layers.iter().rev().find(|layer| layer.role() == role)) + .collect() + } + + /// Returns the OpenVMM arguments of the sandbox profile: the microVM machine, a read-only + /// sandbox block device for each layer in role order, the scratch device, the workload + /// identity, and the arguments that attach the shares. + /// + /// Fails as [`mount::mounts_openvmm_arguments`] does. + pub fn openvmm_arguments(&self) -> Result> { + self.openvmm_arguments_with(&fs::cwd) + } + + /// Implements [`openvmm_arguments`](Self::openvmm_arguments) with `cwd` as `Path.cwd()`. + pub(crate) fn openvmm_arguments_with( + &self, + cwd: &dyn Fn() -> io::Result, + ) -> Result> { + let mut arguments = vec!["--machine".to_owned(), "microvm".to_owned()]; + for layer in self.ordered_layers() { + arguments.push("--microvm-sandbox-block".to_owned()); + arguments.push(format!( + "{}:file:{},ro", + layer.role(), + layer.pure_path().as_str() + )); + } + let (uid, gid) = self.settings.workload_identity; + arguments.extend([ + "--microvm-sandbox-block".to_owned(), + format!("scratch:file:{}", self.scratch.as_str()), + "--microvm-workload-identity".to_owned(), + format!("{uid}:{gid}"), + ]); + arguments.extend(mount::mounts_openvmm_arguments_with( + &self.settings.mounts, + cwd, + )?); + Ok(arguments) + } + + /// Returns the kernel command line of the sandbox: `user_command_line`, stripped of Python + /// whitespace, then `nvx_sandbox=1`, a `nvx_layer=` token for each layer in role order, + /// `nvx_scratch=`, `nvx_entrypoint=`, `nvx_hostname=`, a `nvx_arg=` token for each + /// argument, `nvx_memory_max=` and `nvx_pids_max=` when set, and the `nvx_share=` tokens of + /// an aggregate. + /// + /// The user's command line may hold no NUL and no `nvx_*` or `virtfs_*` token, which the + /// profile and OpenVMM own. The command line, the bootstrap tokens that OpenVMM appends for + /// the shares, and a NUL must fit in [`SANDBOX_COMMAND_LINE_MAX_SIZE`] bytes. Fails with + /// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). + pub fn kernel_command_line(&self, user_command_line: &str) -> Result { + if user_command_line.contains('\0') { + return Err(refused("kernel command line contains an embedded NUL")); + } + for token in split(user_command_line) { + let name = token.split('=').next().unwrap_or(token); + if token.starts_with("nvx_") { + return Err(refused(format!("{name} is owned by the sandbox profile"))); + } + if token.starts_with("virtfs_") { + return Err(refused(format!( + "{name} is owned by the sandbox --mount option" + ))); + } + } + let settings = &self.settings; + let mut tokens = vec![ + strip(user_command_line).to_owned(), + "nvx_sandbox=1".to_owned(), + ]; + for layer in self.ordered_layers() { + tokens.push(format!( + "nvx_layer={},0x{:x},{}", + layer.role(), + layer.role().mmio_base(), + layer.uuid() + )); + } + tokens.extend([ + format!("nvx_scratch=0x{SCRATCH_MMIO_BASE:x},ext4"), + format!("nvx_entrypoint={}", settings.entrypoint), + format!("nvx_hostname={}", settings.hostname), + ]); + tokens.extend( + settings + .args + .iter() + .map(|argument| format!("nvx_arg={argument}")), + ); + if let Some(memory_max) = &settings.memory_max { + tokens.push(format!("nvx_memory_max={memory_max}")); + } + if let Some(pids_max) = &settings.pids_max { + tokens.push(format!("nvx_pids_max={}", pids_max.plus_one())); + } + if mount::mounts_use_aggregate(&settings.mounts) { + let shares: Vec<_> = settings + .mounts + .iter() + .map(|mount| (mount.guest_target(), mount.access(), mount.kind())) + .collect(); + tokens.extend(mount::aggregate_share_tokens(&shares)); + } + tokens.retain(|token| !token.is_empty()); + let command_line = tokens.join(" "); + // OpenVMM appends the shares' bootstrap tokens after this command line, so they consume + // the same x86 budget. + let fragment = mount::mounts_command_line_fragment(&settings.mounts); + if command_line.len() + fragment.len() + 1 > SANDBOX_COMMAND_LINE_MAX_SIZE { + return Err(refused( + "sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer \ + --mount shares or use shorter targets and arguments", + )); + } + Ok(command_line) + } +} + +/// Checks that a pure path is a regular file, as [`require_file`] does. +fn require_pure_file(path: &PurePath, description: &str) -> Result<()> { + if fs::is_file(path.as_path()) { + Ok(()) + } else { + Err(Error::unavailable(format!( + "{description} not found: {path}" + ))) + } +} + +#[cfg(test)] +mod golden; +#[cfg(test)] +mod tests; diff --git a/nvx-rs/src/host/sandbox/mount.rs b/nvx-rs/src/host/sandbox/mount.rs new file mode 100644 index 000000000..89dfd140a --- /dev/null +++ b/nvx-rs/src/host/sandbox/mount.rs @@ -0,0 +1,962 @@ +//! Live virtio-fs shares of host directories and files inside the sandbox's container rootfs. +//! +//! OpenVMM attaches one virtio-fs device, with the tag [`MOUNT_TAG`]. One directory share uses +//! it directly, and the guest mounts it at its target. Several shares, or a file, share it as the +//! children of an aggregate, each named by [`aggregate_child_name`], which the guest mounts at +//! [`AGGREGATE_MOUNT_TARGET`], a directory that only the guest's root can enter, before it binds +//! each child at its target, as the `nvx_share=` kernel parameters of [`aggregate_share_tokens`] +//! tell it. +//! +//! A share's access-policy paths are host paths: `--mount-deny` hides a path, `--mount-allow` +//! exposes a path inside a denied path again, and `--mount-write` makes a path one of the only +//! writable parts of a read-write share. OpenVMM resolves relative paths in the only share, and +//! attributes absolute paths to the child that contains them. + +use std::fmt; +use std::io; +use std::path::Path; + +use sha2::{Digest, Sha256}; + +use super::refused; +use crate::error::{Error, Result}; +use crate::host::python::builtins::{is_space, repr}; +use crate::host::python::fs; +use crate::host::python::path::{Flavor, PurePath}; + +/// The tag of OpenVMM's virtio-fs device. +pub const MOUNT_TAG: &str = "microvm"; +/// The guest directory at which the guest mounts the aggregate of several shares. +pub const AGGREGATE_MOUNT_TARGET: &str = "/run/nvx/shares"; +/// The guest trees that the container runtime owns, which a share may neither be in nor contain: +/// it mounts procfs, sysfs, and a private `/dev` over them, stages its tools under +/// `/.nvx-agent`, and binds the workload machine ID at `/etc/machine-id`, which it first +/// replaces with a regular file. +pub const RESERVED_MOUNT_TARGETS: [&str; 5] = + ["/proc", "/sys", "/dev", "/.nvx-agent", "/etc/machine-id"]; +/// The guest paths at which a share may not be, though it may be below them. +pub const RESERVED_EXACT_MOUNT_TARGETS: [&str; 1] = ["/etc"]; +/// The most denied, allowed, and writable paths each that OpenVMM accepts for a share. +pub const MAX_MOUNT_POLICY_PATHS: usize = 128; +/// The longest guest target, in UTF-8 bytes. +pub const MAX_MOUNT_TARGET_BYTES: usize = 4096; + +/// The bootstrap tokens that OpenVMM appends to the kernel command line for its share, with the +/// guest directory, the tag, and the mode. +const MOUNT_COMMAND_LINE_FRAGMENT: [&str; 3] = [" virtfs_dir=", " virtfs_tag=", " virtfs_mode="]; +/// The token that OpenVMM appends after those of an aggregate. +const AGGREGATE_COMMAND_LINE_SUFFIX: &str = " virtfs_aggregate=1"; + +/// The access mode of a share. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum MountAccess { + /// `ro`, the default. + ReadOnly, + /// `rw`. + ReadWrite, +} + +impl MountAccess { + /// Returns the mode's name: `ro` or `rw`. + pub const fn as_str(self) -> &'static str { + match self { + Self::ReadOnly => "ro", + Self::ReadWrite => "rw", + } + } + + /// Returns the mode that `name` names, as a recorded share gives it. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for another name. + pub fn from_name(name: &str) -> Result { + match name { + "ro" => Ok(Self::ReadOnly), + "rw" => Ok(Self::ReadWrite), + _ => Err(refused(format!( + "unsupported sandbox mount mode {}; choose ro or rw", + repr(name) + ))), + } + } +} + +impl fmt::Display for MountAccess { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +/// The host identity of a share's file operations. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum MountOwner { + /// `vmm`, the default: every operation runs as OpenVMM. + Vmm, + /// `caller`: each operation runs as the guest caller's identity, with guest root squashed to + /// the owner of the shared directory or file. Linux hosts only. + Caller, +} + +impl MountOwner { + /// Returns the owner's name: `vmm` or `caller`. + pub const fn as_str(self) -> &'static str { + match self { + Self::Vmm => "vmm", + Self::Caller => "caller", + } + } + + /// Returns the owner that `name` names, as a recorded share gives it. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for another name. + pub fn from_name(name: &str) -> Result { + match name { + "vmm" => Ok(Self::Vmm), + "caller" => Ok(Self::Caller), + _ => Err(refused(format!( + "unsupported sandbox mount owner {}; choose vmm or caller", + repr(name) + ))), + } + } +} + +impl fmt::Display for MountOwner { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +/// What a share binds: a host directory, or one regular host file without its directory, which +/// only the child of an aggregate can share. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum MountKind { + /// `directory`, the default. + Directory, + /// `file`. + File, +} + +impl MountKind { + /// Returns the kind's name: `directory` or `file`. + pub const fn as_str(self) -> &'static str { + match self { + Self::Directory => "directory", + Self::File => "file", + } + } + + /// Returns the kind that `name` names, as a recorded share gives it. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for another name. + pub fn from_name(name: &str) -> Result { + match name { + "directory" => Ok(Self::Directory), + "file" => Ok(Self::File), + _ => Err(refused(format!( + "unsupported sandbox mount kind {}; choose directory or file", + repr(name) + ))), + } + } +} + +impl fmt::Display for MountKind { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +/// A kind of access-policy path. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum PolicyKind { + /// A path that the share hides: `--mount-deny`. + Denied, + /// A path inside a denied path that the share exposes again: `--mount-allow`. + Allowed, + /// One of the only writable paths of a read-write share: `--mount-write`. + Writable, +} + +/// The kinds of access-policy paths, in the order in which OpenVMM's options carry them. +pub const POLICY_KINDS: [PolicyKind; 3] = [ + PolicyKind::Denied, + PolicyKind::Allowed, + PolicyKind::Writable, +]; + +impl PolicyKind { + /// Returns the kind's name in messages: `denied`, `allowed`, or `writable`. + pub const fn as_str(self) -> &'static str { + match self { + Self::Denied => "denied", + Self::Allowed => "allowed", + Self::Writable => "writable", + } + } + + /// Returns the option that requests a path of this kind: `--mount-deny`, `--mount-allow`, or + /// `--mount-write`. + pub const fn option(self) -> &'static str { + match self { + Self::Denied => "--mount-deny", + Self::Allowed => "--mount-allow", + Self::Writable => "--mount-write", + } + } +} + +impl fmt::Display for PolicyKind { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +/// The access-policy paths of a share, as given: host paths, relative to the share or absolute. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct MountPolicy { + /// Paths that the share hides. + pub denied: Vec, + /// Paths inside denied paths that the share exposes again. + pub allowed: Vec, + /// The only paths of a read-write share that the workload can modify, if any. + pub writable: Vec, +} + +impl MountPolicy { + /// Returns the paths of `kind`. + pub fn paths(&self, kind: PolicyKind) -> &[String] { + match kind { + PolicyKind::Denied => &self.denied, + PolicyKind::Allowed => &self.allowed, + PolicyKind::Writable => &self.writable, + } + } + + /// Returns whether the policy has no paths. + pub fn is_empty(&self) -> bool { + POLICY_KINDS.iter().all(|&kind| self.paths(kind).is_empty()) + } +} + +/// Splits a `--mount` value, `GUEST_TARGET,HOST_PATH[,ro|rw]`, into its guest target, host path, +/// and access mode, which defaults to `ro`. +/// +/// The guest target ends at the first comma, and with another comma the mode follows the last +/// one, so a host path may contain commas when the mode is given. OpenVMM reads its `--mount` +/// and `--mount-child` values the same way. Fails with +/// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). +pub fn parse_mount_value(value: &str) -> Result<(String, String, MountAccess)> { + let Some((target, rest)) = value.split_once(',') else { + return Err(refused("--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]")); + }; + let (host_path, access) = rest.rsplit_once(',').unwrap_or((rest, "ro")); + if host_path.is_empty() { + return Err(refused("--mount host path is empty")); + } + let access = match access { + "ro" => MountAccess::ReadOnly, + "rw" => MountAccess::ReadWrite, + _ => { + return Err(refused(format!( + "unsupported --mount mode {}; choose ro or rw (if HOST_PATH contains commas, end \ + the value with ,ro or ,rw)", + repr(access) + ))); + } + }; + Ok((target.to_owned(), host_path.to_owned(), access)) +} + +/// Returns whether `target` holds a character that no guest target may: Python whitespace, NUL, +/// `\`, `=`, or `,`. +pub(crate) fn has_forbidden_character(target: &str) -> bool { + target + .chars() + .any(|character| is_space(character) || matches!(character, '\0' | '\\' | '=' | ',')) +} + +/// Returns whether a guest target has an empty, `.`, or `..` component after its leading slash. +pub(crate) fn has_irregular_component(target: &str) -> bool { + target + .split('/') + .skip(1) + .any(|component| matches!(component, "" | "." | "..")) +} + +/// Checks a live share's guest target inside the sandbox container rootfs: an absolute non-root +/// path of at most [`MAX_MOUNT_TARGET_BYTES`] bytes, without empty, dot, or parent components, +/// whitespace, `\`, `=`, or `,`, and outside the [`RESERVED_MOUNT_TARGETS`] trees and the +/// [`RESERVED_EXACT_MOUNT_TARGETS`]. +/// +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). +pub fn validate_mount_target(target: &str) -> Result<()> { + if !target.starts_with('/') + || target == "/" + || target.len() > MAX_MOUNT_TARGET_BYTES + || has_forbidden_character(target) + || has_irregular_component(target) + { + return Err(refused(format!( + "invalid sandbox mount target {}: expected an absolute non-root path without empty, \ + dot, or parent components, whitespace, '\\', '=', or ','", + repr(target) + ))); + } + for reserved in RESERVED_MOUNT_TARGETS { + if target == reserved + || target + .strip_prefix(reserved) + .is_some_and(|rest| rest.starts_with('/')) + { + return Err(refused(format!( + "sandbox mount target {target} overlaps the reserved {reserved} tree" + ))); + } + } + if RESERVED_EXACT_MOUNT_TARGETS.contains(&target) { + return Err(refused(format!( + "sandbox mount target {target} is reserved" + ))); + } + Ok(()) +} + +/// Refuses an owner that this host cannot enforce: `caller` on Windows, which has no +/// per-request POSIX identity for OpenVMM to switch to. +/// +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). +pub fn require_mount_owner_supported(owner: MountOwner) -> Result<()> { + require_mount_owner_supported_on(owner, cfg!(windows)) +} + +/// Implements [`require_mount_owner_supported`] for a Windows host or another. +pub(crate) fn require_mount_owner_supported_on(owner: MountOwner, windows: bool) -> Result<()> { + if owner == MountOwner::Caller && windows { + return Err(refused( + "--mount-owner caller requires a Linux host; Windows has no per-request POSIX \ + identity for OpenVMM to switch to", + )); + } + Ok(()) +} + +/// Returns whether two guest targets overlap: whether they are equal, or one contains the other. +pub(crate) fn targets_overlap(left: &str, right: &str) -> bool { + let inside = |path: &str, directory: &str| { + path.strip_prefix(directory) + .is_some_and(|rest| rest.starts_with('/')) + }; + left == right || inside(left, right) || inside(right, left) +} + +/// Returns the kind of share that `host_path` requests: a file for a regular file, and +/// otherwise a directory, which [`SandboxMount::validated`] then requires. +pub fn mount_kind(host_path: &Path) -> MountKind { + if fs::is_file(host_path) { + MountKind::File + } else { + MountKind::Directory + } +} + +/// Returns the error for an `OSError` that an operation on `path` raised. +pub(crate) fn os_error(error: &io::Error, path: Option<&str>) -> Error { + Error::io(fs::os_error_message(error, path)) +} + +/// A live virtio-fs share mounted inside the sandbox container rootfs. +/// +/// Its denied paths hide paths in the share, its allowed paths expose paths inside denied paths +/// again, and its writable paths, when present, are the only paths of a read-write share that +/// the workload can modify. A share of a file binds one regular host file, whose mode applies to +/// the whole file, so it has none of them. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SandboxMount { + /// The guest directory or file at which the share appears. + guest_target: String, + /// The host directory or file, as `pathlib` normalizes it. + host_path: PurePath, + /// The access mode. + access: MountAccess, + /// The access-policy paths. + policy: MountPolicy, + /// The identity of the share's file operations. + owner: MountOwner, + /// Whether the share binds a directory or a file. + kind: MountKind, +} + +impl SandboxMount { + /// Returns the share of `host_path` at `guest_target`, with `host_path` normalized as + /// `pathlib.Path` normalizes it. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a guest target + /// that [`validate_mount_target`] refuses, a host path with a NUL character or a `..` + /// component, policy paths for a file, more than [`MAX_MOUNT_POLICY_PATHS`] policy paths of a + /// kind, empty or repeated ones, allowed paths without denied paths, and writable paths of a + /// read-only share. + pub fn new( + guest_target: &str, + host_path: &str, + access: MountAccess, + policy: MountPolicy, + owner: MountOwner, + kind: MountKind, + ) -> Result { + Self::new_in( + Flavor::NATIVE, + guest_target, + host_path, + access, + policy, + owner, + kind, + ) + } + + /// Implements [`new`](Self::new) for host paths of `flavor`. + pub(crate) fn new_in( + flavor: Flavor, + guest_target: &str, + host_path: &str, + access: MountAccess, + policy: MountPolicy, + owner: MountOwner, + kind: MountKind, + ) -> Result { + Self::checked(Self { + guest_target: guest_target.to_owned(), + host_path: PurePath::new(flavor, host_path), + access, + policy, + owner, + kind, + }) + } + + /// Parses a `--mount` value with [`parse_mount_value`] into a share of the given policy, + /// owner, and kind. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported), as + /// [`parse_mount_value`] and [`new`](Self::new) do. + pub fn parse( + value: &str, + policy: MountPolicy, + owner: MountOwner, + kind: MountKind, + ) -> Result { + Self::parse_in(Flavor::NATIVE, value, policy, owner, kind) + } + + /// Implements [`parse`](Self::parse) for host paths of `flavor`. + pub(crate) fn parse_in( + flavor: Flavor, + value: &str, + policy: MountPolicy, + owner: MountOwner, + kind: MountKind, + ) -> Result { + let (guest_target, host_path, access) = parse_mount_value(value)?; + Self::new_in( + flavor, + &guest_target, + &host_path, + access, + policy, + owner, + kind, + ) + } + + /// Checks a share's fields in the order in which Python's `__post_init__` checks them. + fn checked(self) -> Result { + validate_mount_target(&self.guest_target)?; + let raw_path = self.host_path.as_str(); + if raw_path.contains('\0') { + return Err(refused(format!( + "sandbox mount host path contains a NUL character: {}", + repr(raw_path) + ))); + } + if self.host_path.parts().iter().any(|part| part == "..") { + return Err(refused(format!( + "sandbox mount host path contains a parent component: {raw_path}" + ))); + } + if self.kind == MountKind::File && !self.policy.is_empty() { + return Err(refused(format!( + "--mount-deny, --mount-allow, and --mount-write name paths inside a shared \ + directory, but --mount {} shares a file, whose mode applies to the whole file", + self.guest_target + ))); + } + for kind in POLICY_KINDS { + let paths = self.policy.paths(kind); + if paths.len() > MAX_MOUNT_POLICY_PATHS { + return Err(refused(format!( + "a sandbox mount permits at most {MAX_MOUNT_POLICY_PATHS} {kind} paths" + ))); + } + if paths + .iter() + .any(|path| path.is_empty() || path.contains('\0')) + { + return Err(refused(format!( + "sandbox mount {kind} paths must be nonempty" + ))); + } + let unique: std::collections::HashSet<&String> = paths.iter().collect(); + if unique.len() != paths.len() { + return Err(refused(format!( + "sandbox mount {kind} paths must be unique" + ))); + } + } + if !self.policy.allowed.is_empty() && self.policy.denied.is_empty() { + return Err(refused( + "--mount-allow exposes a path inside a --mount-deny path of the same share, \ + which has none", + )); + } + if !self.policy.writable.is_empty() && self.access != MountAccess::ReadWrite { + return Err(refused(format!( + "--mount-write narrows the writes of a read-write share; --mount {} is read-only", + self.guest_target + ))); + } + Ok(self) + } + + /// Returns the share with another kind, checked again, as `dataclasses.replace` does. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a file share + /// with policy paths. + pub fn with_kind(&self, kind: MountKind) -> Result { + Self::checked(Self { + kind, + ..self.clone() + }) + } + + /// Returns the guest target. + pub fn guest_target(&self) -> &str { + &self.guest_target + } + + /// Returns the host directory or file. + pub fn host_path(&self) -> &Path { + self.host_path.as_path() + } + + /// Returns the access mode. + pub fn access(&self) -> MountAccess { + self.access + } + + /// Returns the access-policy paths. + pub fn policy(&self) -> &MountPolicy { + &self.policy + } + + /// Returns the identity of the share's file operations. + pub fn owner(&self) -> MountOwner { + self.owner + } + + /// Returns whether the share binds a directory or a file. + pub fn kind(&self) -> MountKind { + self.kind + } + + /// Checks the share against the host: its owner must be one that the host supports, its + /// host path must be a regular file or a plain directory, as its kind requires, without + /// being a symbolic link, and with the `caller` owner on Linux, it must not belong to UID 0 + /// or GID 0, to which guest root would be squashed. + /// + /// The kind was fixed when the share was requested, so a directory that replaced a shared + /// file is not shared in its place. Fails with + /// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported), or + /// [`ErrorKind::Io`](crate::ErrorKind::Io) if the host path cannot be examined. + pub fn validated(&self) -> Result<()> { + require_mount_owner_supported(self.owner)?; + let host_path = self.host_path.as_path(); + match self.kind { + MountKind::File if fs::is_symlink(host_path) || !fs::is_file(host_path) => { + return Err(refused(format!( + "sandbox mount host path is not a regular file: {}", + self.host_path + ))); + } + MountKind::Directory if fs::is_symlink(host_path) || !fs::is_dir(host_path) => { + return Err(refused(format!( + "sandbox mount host path is not a plain directory: {}", + self.host_path + ))); + } + _ => {} + } + if self.owner == MountOwner::Caller && !cfg!(windows) { + let status = fs::stat(host_path) + .map_err(|error| os_error(&error, Some(self.host_path.as_str())))?; + if status.uid == 0 || status.gid == 0 { + return Err(refused(format!( + "--mount-owner caller squashes guest root to the owner of the shared {}, so \ + {} must not be owned by UID 0 or GID 0", + self.kind, self.host_path + ))); + } + } + Ok(()) + } + + /// Returns the share with its host path made absolute by joining it to the current + /// directory, without normalizing it, so OpenVMM still sees, and refuses, every + /// symbolic-link component of the path that was requested. + /// + /// Fails with [`ErrorKind::Io`](crate::ErrorKind::Io) if a relative host path meets an + /// unreadable current directory. + pub fn absolute(&self) -> Result { + self.absolute_with(&fs::cwd) + } + + /// Implements [`absolute`](Self::absolute) with `cwd` as `Path.cwd()`. + pub(crate) fn absolute_with(&self, cwd: &dyn Fn() -> io::Result) -> Result { + if self.host_path.is_absolute() { + return Ok(self.clone()); + } + let directory = cwd().map_err(|error| os_error(&error, None))?; + Self::checked(Self { + host_path: directory.join(self.host_path.as_str()), + ..self.clone() + }) + } + + /// Returns each kind of access-policy path with its requested paths, relative paths joined + /// to the absolute host path of the share, as OpenVMM attributes them to the children of an + /// aggregate. + /// + /// Fails as [`absolute`](Self::absolute) does. + pub fn absolute_policy_paths(&self) -> Result)>> { + self.absolute_policy_paths_with(&fs::cwd) + } + + /// Implements [`absolute_policy_paths`](Self::absolute_policy_paths) with `cwd` as + /// `Path.cwd()`. + pub(crate) fn absolute_policy_paths_with( + &self, + cwd: &dyn Fn() -> io::Result, + ) -> Result)>> { + let host_path = self.absolute_with(cwd)?.host_path; + let flavor = host_path.flavor(); + Ok(POLICY_KINDS + .iter() + .map(|&kind| { + let paths = self + .policy + .paths(kind) + .iter() + .map(|path| { + if PurePath::new(flavor, path).is_absolute() { + path.clone() + } else { + host_path.join(path).as_str().to_owned() + } + }) + .collect(); + (kind, paths) + }) + .collect()) + } + + /// Returns the bootstrap tokens that OpenVMM appends to the kernel command line for this + /// share alone: its target, the device tag, and its mode. + pub fn command_line_fragment(&self) -> String { + command_line_fragment(&self.guest_target, self.access) + } +} + +/// Returns the bootstrap tokens of a share at `target` with `access`. +fn command_line_fragment(target: &str, access: MountAccess) -> String { + let [directory, tag, mode] = MOUNT_COMMAND_LINE_FRAGMENT; + format!("{directory}{target}{tag}{MOUNT_TAG}{mode}{access}") +} + +/// Returns whether `mounts` share the virtio-fs device as the children of an aggregate: several +/// shares do, and so does a file, because the guest mounts the device's root, which is a +/// directory. +pub fn mounts_use_aggregate(mounts: &[SandboxMount]) -> bool { + mounts.len() > 1 || mounts.iter().any(|mount| mount.kind == MountKind::File) +} + +/// Checks the live shares that one sandbox attaches, in child order: no target may overlap +/// another, so that no share hides another, and every share has the same owner. +/// +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). +pub fn validate_mounts(mounts: &[SandboxMount]) -> Result<()> { + for (index, mount) in mounts.iter().enumerate() { + for other in &mounts[..index] { + if targets_overlap(&other.guest_target, &mount.guest_target) { + return Err(refused(format!( + "sandbox mount targets {} and {} overlap; a share cannot hide another", + other.guest_target, mount.guest_target + ))); + } + } + } + if mounts.iter().any(|mount| mount.owner != mounts[0].owner) { + return Err(refused( + "every sandbox --mount must use the same --mount-owner", + )); + } + Ok(()) +} + +/// The queries of the host that [`validate_mount_host_paths`] makes, which tests replace. +pub(crate) struct HostQueries<'a> { + /// `Path.cwd()`. + pub(crate) cwd: &'a dyn Fn() -> io::Result, + /// `Path.resolve()`. + pub(crate) resolve: &'a dyn Fn(&PurePath) -> io::Result, + /// `(st_dev, st_ino)` of `Path.stat()`. + pub(crate) identity: &'a dyn Fn(&PurePath) -> io::Result<(u64, u128)>, +} + +impl HostQueries<'_> { + /// Returns the queries of this host. + pub(crate) fn native() -> HostQueries<'static> { + HostQueries { + cwd: &fs::cwd, + resolve: &fs::resolve, + identity: &|path| fs::stat(path.as_path()).map(|status| status.identity()), + } + } +} + +/// Refuses shares whose host paths overlap, and policy paths outside their share. +/// +/// Two host paths overlap if one contains the other once both are resolved, or, since resolving +/// follows symbolic links but not bind mounts, if one has the file identity of the other or of +/// one of its ancestors, as a hard link or a path inside a bind mount of it does. A shared file +/// therefore overlaps a shared directory that contains it, or another share of the same file, +/// but not a file beside it. With several shares, each policy path must name a path inside the +/// host directory of its share, and only a denied path may name the share itself. One share has +/// nothing to compare. +/// +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported), or +/// [`ErrorKind::Io`](crate::ErrorKind::Io) if a path cannot be resolved or examined. +pub fn validate_mount_host_paths(mounts: &[SandboxMount]) -> Result<()> { + validate_mount_host_paths_with(mounts, &HostQueries::native()) +} + +/// Implements [`validate_mount_host_paths`] with the given host queries. +pub(crate) fn validate_mount_host_paths_with( + mounts: &[SandboxMount], + host: &HostQueries<'_>, +) -> Result<()> { + if mounts.len() < 2 { + return Ok(()); + } + let roots = mounts + .iter() + .map(|mount| { + (host.resolve)(&mount.host_path) + .map_err(|error| os_error(&error, Some(mount.host_path.as_str()))) + }) + .collect::>>()?; + // The identity of each root, then of each of its ancestors. + let lineages = roots + .iter() + .map(|root| { + std::iter::once(root.clone()) + .chain(root.parents()) + .map(|path| { + (host.identity)(&path).map_err(|error| os_error(&error, Some(path.as_str()))) + }) + .collect::>>() + }) + .collect::>>()?; + for (index, root) in roots.iter().enumerate() { + for (other_index, other) in roots[..index].iter().enumerate() { + if root == other + || root.parents().any(|parent| parent == *other) + || other.parents().any(|parent| parent == *root) + { + return Err(refused(format!( + "sandbox mount host paths {other} and {root} overlap; a share could reach \ + files of another under its access mode" + ))); + } + if lineages[other_index].contains(&lineages[index][0]) + || lineages[index].contains(&lineages[other_index][0]) + { + return Err(refused(format!( + "sandbox mount host paths {other} and {root} name the same directory or \ + file, or one is inside the other through a bind mount" + ))); + } + } + } + for (mount, root) in mounts.iter().zip(&roots) { + for (kind, paths) in mount.absolute_policy_paths_with(host.cwd)? { + let option = kind.option(); + for path in paths { + let pure = PurePath::new(root.flavor(), &path); + let resolved = + (host.resolve)(&pure).map_err(|error| os_error(&error, Some(pure.as_str())))?; + // A denied root hides the share behind its allowed paths, which OpenVMM requires; + // it accepts no other policy path at a root. + if resolved.parents().any(|parent| parent == *root) + || (kind == PolicyKind::Denied && resolved == *root) + { + continue; + } + return Err(refused(format!( + "{option} {path} is not inside the host directory of --mount {}; with several \ + shares, each {option} follows the --mount whose directory it names", + mount.guest_target + ))); + } + } + } + Ok(()) +} + +/// Returns the name of the aggregate child at `index` that the guest binds at `target`: the +/// index, a hyphen, and the first 32 hexadecimal digits of the SHA-256 digest of the target. +/// +/// A snapshot records every child's name, so it pins each target, and OpenVMM refuses a restore +/// that requests other targets before the guest runs, rather than keep the snapshot's binds. +pub fn aggregate_child_name(index: usize, target: &str) -> String { + let digest = Sha256::digest(target.as_bytes()); + let hex: String = digest[..16] + .iter() + .map(|byte| format!("{byte:02x}")) + .collect(); + format!("{index}-{hex}") +} + +/// Returns the `--mount-child` value that attaches `host_path` as the child `name`. It always +/// gives the mode, which OpenVMM reads after the last comma or before the `file` flag, so the +/// host path may contain commas. A file child carries OpenVMM's `file` flag, so OpenVMM refuses +/// a directory that replaced the file instead of exporting all of it. +pub fn aggregate_child_argument( + name: &str, + host_path: &str, + access: MountAccess, + kind: MountKind, +) -> String { + let file = if kind == MountKind::File { ",file" } else { "" }; + format!("{name},{host_path},{access}{file}") +} + +/// Returns the `--mount` value that attaches `host_path` at `target`. It always gives the mode, +/// which OpenVMM reads after the last comma, so the host path may contain commas. +pub fn mount_argument(target: &str, host_path: &str, access: MountAccess) -> String { + format!("{target},{host_path},{access}") +} + +/// Returns the OpenVMM arguments that attach `mounts`: one directory share directly, and +/// several shares, or a file, as the children of an aggregate, in order. Then come the +/// access-policy paths of each share, absolute for an aggregate, and the owner unless it is +/// `vmm`. +/// +/// Fails as [`SandboxMount::absolute`] does. +pub fn mounts_openvmm_arguments(mounts: &[SandboxMount]) -> Result> { + mounts_openvmm_arguments_with(mounts, &fs::cwd) +} + +/// Implements [`mounts_openvmm_arguments`] with `cwd` as `Path.cwd()`. +pub(crate) fn mounts_openvmm_arguments_with( + mounts: &[SandboxMount], + cwd: &dyn Fn() -> io::Result, +) -> Result> { + let mut arguments = Vec::new(); + let aggregate = mounts_use_aggregate(mounts); + match mounts { + [] => {} + [mount] if !aggregate => arguments.extend([ + "--mount".to_owned(), + mount_argument(&mount.guest_target, mount.host_path.as_str(), mount.access), + ]), + _ => { + arguments.extend([ + "--mount-aggregate".to_owned(), + AGGREGATE_MOUNT_TARGET.to_owned(), + ]); + for (index, mount) in mounts.iter().enumerate() { + let name = aggregate_child_name(index, &mount.guest_target); + arguments.push("--mount-child".to_owned()); + arguments.push(aggregate_child_argument( + &name, + mount.host_path.as_str(), + mount.access, + mount.kind, + )); + } + } + } + for mount in mounts { + let policy = if aggregate { + mount.absolute_policy_paths_with(cwd)? + } else { + POLICY_KINDS + .iter() + .map(|&kind| (kind, mount.policy.paths(kind).to_vec())) + .collect() + }; + for (kind, paths) in policy { + for path in paths { + arguments.push(kind.option().to_owned()); + arguments.push(path); + } + } + } + if let Some(first) = mounts.first() + && first.owner != MountOwner::Vmm + { + arguments.extend(["--mount-owner".to_owned(), first.owner.as_str().to_owned()]); + } + Ok(arguments) +} + +/// Returns the kernel command-line tokens that tell the guest where to bind each child of an +/// aggregate, given each share's guest target, access, and kind. A file child's token ends in +/// `,file`, so the guest binds it at a file. +pub fn aggregate_share_tokens(shares: &[(&str, MountAccess, MountKind)]) -> Vec { + shares + .iter() + .enumerate() + .map(|(index, &(target, access, kind))| { + let file = if kind == MountKind::File { ",file" } else { "" }; + format!( + "nvx_share={},{target},{access}{file}", + aggregate_child_name(index, target) + ) + }) + .collect() +} + +/// Returns the bootstrap tokens that OpenVMM appends for an aggregate of shares with +/// `accesses`, which it mounts read-write if any child is. +pub fn aggregate_command_line_fragment(accesses: &[MountAccess]) -> String { + let access = if accesses.contains(&MountAccess::ReadWrite) { + MountAccess::ReadWrite + } else { + MountAccess::ReadOnly + }; + command_line_fragment(AGGREGATE_MOUNT_TARGET, access) + AGGREGATE_COMMAND_LINE_SUFFIX +} + +/// Returns the bootstrap tokens that OpenVMM appends for `mounts`. +pub fn mounts_command_line_fragment(mounts: &[SandboxMount]) -> String { + match mounts { + [] => String::new(), + [mount] if !mounts_use_aggregate(mounts) => mount.command_line_fragment(), + _ => aggregate_command_line_fragment( + &mounts.iter().map(|mount| mount.access).collect::>(), + ), + } +} + +#[cfg(test)] +mod tests; diff --git a/nvx-rs/src/host/sandbox/mount/tests.rs b/nvx-rs/src/host/sandbox/mount/tests.rs new file mode 100644 index 000000000..2338bf93e --- /dev/null +++ b/nvx-rs/src/host/sandbox/mount/tests.rs @@ -0,0 +1,520 @@ +//! The share tests of `scripts/test_nvx_tools.py`, ported. + +use std::fs; + +use super::*; +use crate::ErrorKind; +use crate::host::python::fs::cwd; + +fn policy(denied: &[&str], allowed: &[&str], writable: &[&str]) -> MountPolicy { + let owned = |paths: &[&str]| paths.iter().map(|&path| path.to_owned()).collect(); + MountPolicy { + denied: owned(denied), + allowed: owned(allowed), + writable: owned(writable), + } +} + +fn parse(value: &str, policy: MountPolicy) -> Result { + SandboxMount::parse(value, policy, MountOwner::Vmm, MountKind::Directory) +} + +fn message(result: Result) -> String { + let error = result.unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unsupported); + error.to_string() +} + +fn native(path: &str) -> String { + PurePath::native(path).as_str().to_owned() +} + +#[test] +fn mount_parser_defaults_to_read_only_and_accepts_rw() { + let default = parse("/workspace,host-dir", MountPolicy::default()).unwrap(); + assert_eq!(default.guest_target(), "/workspace"); + assert_eq!(default.host_path(), Path::new("host-dir")); + assert_eq!(default.access(), MountAccess::ReadOnly); + assert!(default.policy().denied.is_empty()); + + let writable = parse( + "/opt/hostedtoolcache,host-dir,rw", + policy(&["logs", "secrets"], &[], &[]), + ) + .unwrap(); + assert_eq!(writable.access(), MountAccess::ReadWrite); + assert_eq!(writable.policy().denied, ["logs", "secrets"]); + assert_eq!( + mounts_openvmm_arguments(std::slice::from_ref(&writable)).unwrap(), + [ + "--mount", + "/opt/hostedtoolcache,host-dir,rw", + "--mount-deny", + "logs", + "--mount-deny", + "secrets", + ] + ); + assert_eq!( + writable.command_line_fragment(), + " virtfs_dir=/opt/hostedtoolcache virtfs_tag=microvm virtfs_mode=rw" + ); +} + +#[test] +fn mount_parser_rejects_malformed_specifications() { + let modes = "choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"; + for (value, expected) in [ + ( + "/workspace", + "--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]".to_owned(), + ), + ("/workspace,", "--mount host path is empty".to_owned()), + ("/workspace,,rw", "--mount host path is empty".to_owned()), + ( + "/workspace,host,rx", + format!("unsupported --mount mode 'rx'; {modes}"), + ), + // With more than one comma, the value must end in the mode, which the error suggests for + // a host path that contains commas. + ( + "/workspace,host,rw,extra", + format!("unsupported --mount mode 'extra'; {modes}"), + ), + ( + "/workspace,alpha,beta", + format!("unsupported --mount mode 'beta'; {modes}"), + ), + ] { + assert_eq!(message(parse(value, MountPolicy::default())), expected); + } + assert_eq!( + message(SandboxMount::new( + "/workspace", + "a\0b", + MountAccess::ReadOnly, + MountPolicy::default(), + MountOwner::Vmm, + MountKind::Directory, + )), + "sandbox mount host path contains a NUL character: 'a\\x00b'" + ); + assert_eq!( + message(parse("/workspace,link/../share", MountPolicy::default())), + format!( + "sandbox mount host path contains a parent component: {}", + native("link/../share") + ) + ); +} + +#[test] +fn mount_parser_accepts_host_paths_with_commas() { + let alpha = r"C:\projects\alpha,beta"; + let reports = r"C:\reports\2026, final"; + for (value, target, host_path, access) in [ + ( + format!("/work,{alpha},rw"), + "/work", + alpha, + MountAccess::ReadWrite, + ), + ( + format!("/reports,{reports},ro"), + "/reports", + reports, + MountAccess::ReadOnly, + ), + // A host path that ends in a comma, or in what looks like a mode, keeps it before the + // mode. + ( + "/data,/srv/data,rw,ro".to_owned(), + "/data", + "/srv/data,rw", + MountAccess::ReadOnly, + ), + ( + "/data,/srv/data,,rw".to_owned(), + "/data", + "/srv/data,", + MountAccess::ReadWrite, + ), + // Values without commas in the host path keep their meaning. + ( + "/data,/srv/data,rw".to_owned(), + "/data", + "/srv/data", + MountAccess::ReadWrite, + ), + ( + "/data,/srv/data".to_owned(), + "/data", + "/srv/data", + MountAccess::ReadOnly, + ), + ] { + assert_eq!( + parse_mount_value(&value).unwrap(), + (target.to_owned(), host_path.to_owned(), access) + ); + let mount = parse(&value, policy(&["secret"], &[], &[])).unwrap(); + assert_eq!(mount.host_path(), Path::new(&native(host_path))); + assert_eq!(mount.access(), access); + // OpenVMM finds the end of the host path before the mode, which NVX therefore always + // gives. + assert_eq!( + mounts_openvmm_arguments(&[mount]).unwrap(), + [ + "--mount".to_owned(), + format!("{target},{},{access}", native(host_path)), + "--mount-deny".to_owned(), + "secret".to_owned(), + ] + ); + } + let alpha_mount = parse(&format!("/work,{alpha},rw"), MountPolicy::default()).unwrap(); + let reports_mount = parse(&format!("/reports,{reports},ro"), MountPolicy::default()).unwrap(); + let arguments = mounts_openvmm_arguments(&[alpha_mount, reports_mount]).unwrap(); + let children: Vec<&String> = arguments + .iter() + .zip(arguments.iter().skip(1)) + .filter(|(option, _)| *option == "--mount-child") + .map(|(_, value)| value) + .collect(); + assert_eq!( + children, + [ + &format!("{},{},rw", aggregate_child_name(0, "/work"), native(alpha)), + &format!( + "{},{},ro", + aggregate_child_name(1, "/reports"), + native(reports) + ), + ] + ); +} + +#[test] +fn mount_owner_defaults_to_vmm_and_forwards_caller() { + let default = parse("/workspace,host-dir,rw", MountPolicy::default()).unwrap(); + assert_eq!(default.owner(), MountOwner::Vmm); + assert!( + !mounts_openvmm_arguments(std::slice::from_ref(&default)) + .unwrap() + .contains(&"--mount-owner".to_owned()) + ); + let caller = SandboxMount::parse( + "/workspace,host-dir,rw", + policy(&["secrets"], &[], &[]), + MountOwner::Caller, + MountKind::Directory, + ) + .unwrap(); + assert_eq!( + mounts_openvmm_arguments(std::slice::from_ref(&caller)).unwrap(), + [ + "--mount", + "/workspace,host-dir,rw", + "--mount-deny", + "secrets", + "--mount-owner", + "caller", + ] + ); + assert_eq!(caller.absolute().unwrap().owner(), MountOwner::Caller); + assert_eq!( + caller.command_line_fragment(), + default.command_line_fragment() + ); + assert_eq!( + message(MountOwner::from_name("root")), + "unsupported sandbox mount owner 'root'; choose vmm or caller" + ); + assert_eq!( + message(MountAccess::from_name("rx")), + "unsupported sandbox mount mode 'rx'; choose ro or rw" + ); +} + +#[test] +fn mount_access_policy_is_validated_and_forwarded() { + let mount = parse( + "/tmp/mcp-runtime,host-dir,rw", + policy(&["mcp-logs"], &["mcp-logs/payloads"], &["agent", "cache"]), + ) + .unwrap(); + assert_eq!( + mounts_openvmm_arguments(std::slice::from_ref(&mount)).unwrap(), + [ + "--mount", + "/tmp/mcp-runtime,host-dir,rw", + "--mount-deny", + "mcp-logs", + "--mount-allow", + "mcp-logs/payloads", + "--mount-write", + "agent", + "--mount-write", + "cache", + ] + ); + let absolute = mount.absolute().unwrap(); + assert_eq!(absolute.policy().allowed, ["mcp-logs/payloads"]); + assert_eq!(absolute.policy().writable, ["agent", "cache"]); + // OpenVMM enforces the policy on the host, so the guest's bootstrap tokens are those of the + // plain share. + assert_eq!( + mount.command_line_fragment(), + " virtfs_dir=/tmp/mcp-runtime virtfs_tag=microvm virtfs_mode=rw" + ); + let paths: Vec = (0..129).map(|index| format!("path-{index}")).collect(); + let many: Vec<&str> = paths.iter().map(String::as_str).collect(); + for (value, policy, expected) in [ + ( + "/w,host,ro", + policy(&[], &[], &["out"]), + "--mount-write narrows the writes of a read-write share; --mount /w is read-only", + ), + ( + "/w,host,rw", + policy(&[], &["logs/payloads"], &[]), + "--mount-allow exposes a path inside a --mount-deny path of the same share, which \ + has none", + ), + ( + "/w,host,rw", + policy(&["logs"], &["logs/a", "logs/a"], &[]), + "sandbox mount allowed paths must be unique", + ), + ( + "/w,host,rw", + policy(&[], &[], &[""]), + "sandbox mount writable paths must be nonempty", + ), + ( + "/w,host,rw", + policy(&[], &[], &["a\0"]), + "sandbox mount writable paths must be nonempty", + ), + ( + "/w,host,rw", + policy(&[], &[], &many), + "a sandbox mount permits at most 128 writable paths", + ), + ] { + assert_eq!(message(parse(value, policy)), expected); + } +} + +#[test] +fn caller_owner_requires_linux_and_a_directory_that_root_does_not_own() { + assert_eq!( + message(require_mount_owner_supported_on(MountOwner::Caller, true)), + "--mount-owner caller requires a Linux host; Windows has no per-request POSIX identity \ + for OpenVMM to switch to" + ); + require_mount_owner_supported_on(MountOwner::Vmm, true).unwrap(); + require_mount_owner_supported_on(MountOwner::Caller, false).unwrap(); + let directory = tempfile::tempdir().unwrap(); + let temporary = directory.path().to_str().unwrap(); + let caller = |path: &str| { + SandboxMount::new( + "/workspace", + path, + MountAccess::ReadOnly, + MountPolicy::default(), + MountOwner::Caller, + MountKind::Directory, + ) + .unwrap() + }; + if cfg!(windows) { + assert!(message(caller(temporary).validated()).contains("requires a Linux host")); + return; + } + let owner = crate::host::python::fs::stat(directory.path()).unwrap(); + if owner.uid != 0 && owner.gid != 0 { + caller(temporary).validated().unwrap(); + } + if crate::host::python::fs::stat(Path::new("/")).unwrap().uid == 0 { + assert_eq!( + message(caller("/").validated()), + "--mount-owner caller squashes guest root to the owner of the shared directory, so / \ + must not be owned by UID 0 or GID 0" + ); + SandboxMount::new( + "/workspace", + "/", + MountAccess::ReadOnly, + MountPolicy::default(), + MountOwner::Vmm, + MountKind::Directory, + ) + .unwrap() + .validated() + .unwrap(); + } +} + +#[test] +fn mount_absolute_path_keeps_symbolic_link_components() { + let mount = parse("/workspace,link/share,rw", MountPolicy::default()).unwrap(); + let absolute = mount.absolute().unwrap(); + let expected = cwd().unwrap().join("link").join("share"); + assert_eq!(absolute.host_path(), expected.as_path()); + assert!(absolute.host_path.parts().iter().any(|part| part == "link")); + assert_eq!(absolute.absolute().unwrap(), absolute); + let fixed = PurePath::new(Flavor::Posix, "/fixture/cwd"); + let relative = SandboxMount::parse_in( + Flavor::Posix, + "/workspace,link/share,rw", + MountPolicy::default(), + MountOwner::Vmm, + MountKind::Directory, + ) + .unwrap(); + let joined = relative.absolute_with(&|| Ok(fixed.clone())).unwrap(); + assert_eq!(joined.host_path.as_str(), "/fixture/cwd/link/share"); + for (policy, expected) in [ + ( + policy(&["logs", "logs"], &[], &[]), + "sandbox mount denied paths must be unique", + ), + ( + policy(&[""], &[], &[]), + "sandbox mount denied paths must be nonempty", + ), + ] { + assert_eq!(message(parse("/workspace,host", policy)), expected); + } + let paths: Vec = (0..129).map(|index| format!("path-{index}")).collect(); + let many: Vec<&str> = paths.iter().map(String::as_str).collect(); + assert_eq!( + message(parse("/workspace,host", policy(&many, &[], &[]))), + "a sandbox mount permits at most 128 denied paths" + ); +} + +#[test] +fn mount_absolute_path_keeps_a_link_on_the_host() { + let directory = tempfile::tempdir().unwrap(); + let root = PurePath::native(directory.path().to_str().unwrap()); + fs::create_dir_all(root.join("link-target/share").as_path()).unwrap(); + #[cfg(unix)] + let linked = std::os::unix::fs::symlink( + root.join("link-target").as_path(), + root.join("link").as_path(), + ); + #[cfg(windows)] + let linked = std::os::windows::fs::symlink_dir( + root.join("link-target").as_path(), + root.join("link").as_path(), + ); + if linked.is_err() { + // Symbolic links need a privilege that the host may not grant. + return; + } + let mount = parse( + &format!("/workspace,{},rw", root.join("link/share")), + MountPolicy::default(), + ) + .unwrap(); + let absolute = mount.absolute().unwrap(); + assert!( + !absolute + .host_path + .parts() + .iter() + .any(|part| part == "link-target") + ); +} + +#[test] +fn mount_target_validation_rejects_unsafe_and_reserved_targets() { + let long = format!("/{}", "x".repeat(4096)); + for target in [ + "", + "/", + "workspace", + "/workspace/", + "/work//space", + "/work/./space", + "/work/../space", + "/work space", + "/work=space", + "/work\\space", + "/work,space", + "/work\0space", + "/work\u{85}space", + "/work\x1cspace", + &long, + ] { + assert_eq!( + message(validate_mount_target(target)), + format!( + "invalid sandbox mount target {}: expected an absolute non-root path without \ + empty, dot, or parent components, whitespace, '\\', '=', or ','", + repr(target) + ) + ); + } + for (target, reserved) in [ + ("/proc", "/proc"), + ("/proc/self", "/proc"), + ("/sys", "/sys"), + ("/sys/fs", "/sys"), + ("/dev", "/dev"), + ("/dev/shm", "/dev"), + ("/.nvx-agent", "/.nvx-agent"), + ("/.nvx-agent/tools", "/.nvx-agent"), + ] { + assert_eq!( + message(validate_mount_target(target)), + format!("sandbox mount target {target} overlaps the reserved {reserved} tree") + ); + } + assert_eq!( + message(validate_mount_target("/etc")), + "sandbox mount target /etc is reserved" + ); + for target in ["/workspace", "/procfs", "/etc/app", "/opt/hostedtoolcache"] { + validate_mount_target(target).unwrap(); + } + // The longest target fits. + validate_mount_target(&format!("/{}", "x".repeat(4095))).unwrap(); +} + +#[test] +fn aggregate_child_names_pin_their_targets() { + let name = aggregate_child_name(0, "/workspace"); + assert_eq!(name, "0-c52ddf65534b7b46035084358ab7902b"); + // Equal targets give equal names, so a restore names its children as the boot did, and + // another target or position gives another name. + assert_eq!(aggregate_child_name(0, "/workspace"), name); + assert_ne!(aggregate_child_name(0, "/workspace2"), name); + assert_ne!(aggregate_child_name(1, "/workspace"), name); + // Every name follows OpenVMM's rule for the name of a child. + for (index, target) in [ + (0, "/w".to_owned()), + (999, format!("/{}", "x".repeat(1000))), + ] { + let name = aggregate_child_name(index, &target); + let (number, digest) = name.split_once('-').unwrap(); + assert!(number.chars().all(|c| c.is_ascii_digit())); + assert_eq!(digest.len(), 32); + assert!(digest.chars().all(|c| matches!(c, '0'..='9' | 'a'..='f'))); + } + assert!(aggregate_child_name(999, "/w").len() <= 64); + assert_eq!( + aggregate_child_argument("n", "/h,x", MountAccess::ReadWrite, MountKind::File), + "n,/h,x,rw,file" + ); + assert_eq!( + mount_argument("/t", "/h", MountAccess::ReadOnly), + "/t,/h,ro" + ); + assert_eq!( + aggregate_command_line_fragment(&[MountAccess::ReadOnly, MountAccess::ReadOnly]), + " virtfs_dir=/run/nvx/shares virtfs_tag=microvm virtfs_mode=ro virtfs_aggregate=1" + ); +} diff --git a/nvx-rs/src/host/sandbox/options.rs b/nvx-rs/src/host/sandbox/options.rs new file mode 100644 index 000000000..dfdb1afb3 --- /dev/null +++ b/nvx-rs/src/host/sandbox/options.rs @@ -0,0 +1,585 @@ +//! The checks that `nvx.py sandbox` and `nvx.py run` apply to their options, ported from +//! `scripts/nvx.py` with their order and messages: +//! +//! - [`SandboxOptions::check`]: the checks of `command_sandbox` before it builds the launch: the +//! operations that each option is valid for, the options that need others, and the managed exec +//! environment, from `--environment` or `--environment-file`. +//! - [`sandbox_mounts`]: the shares of the `--mount` options, with each `--mount-deny`, +//! `--mount-allow`, and `--mount-write` attributed to its share, and each share's kind. +//! - [`validate_sandbox_systemd_policy`]: the refusal of systemd entrypoints and images. +//! - [`validate_run_share_targets`]: the guest targets of several `nvx.py run` shares, or of a +//! shared file. +//! - [`validate_exec_environment`] and [`exec_environment_from_file`]: the environment of a +//! managed exec. + +use std::fmt; +use std::fs::File; +use std::io::Read; + +use sha2::{Digest, Sha256}; + +use super::layer::LayerRole; +use super::mount::{ + AGGREGATE_MOUNT_TARGET, MountKind, MountOwner, MountPolicy, POLICY_KINDS, PolicyKind, + SandboxMount, has_forbidden_character, has_irregular_component, mount_kind, os_error, +}; +use super::{SandboxLaunch, refused}; +use crate::error::Result; +use crate::host::artifacts::DISTRO_MANIFEST_SUFFIX; +use crate::host::python::builtins::repr; +use crate::host::python::fs; +use crate::host::python::json::{self, Loads, Node, ReadError}; +use crate::host::python::path::{Flavor, PurePath}; +use crate::host::python::text::{PyText, decode_utf8}; + +/// The entrypoints of systemd, which the sandbox security profile does not run. +pub const SYSTEMD_ENTRYPOINTS: [&str; 2] = ["/usr/lib/systemd/systemd", "/lib/systemd/systemd"]; +/// The largest environment file of a managed exec, in bytes. +pub const MAX_ENVIRONMENT_FILE_BYTES: usize = 1024 * 1024; +/// The most entries of a managed exec environment. +pub const MAX_EXEC_ENVIRONMENT: usize = 256; +/// The longest entry of a managed exec environment, in UTF-8 bytes. +pub const MAX_EXEC_ENVIRONMENT_BYTES: usize = 4096; + +/// An operation of `nvx.py sandbox`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum SandboxOperation { + /// `run`: a one-shot sandbox, the default. + Run, + /// `provision`: record a managed sandbox's launch in its state directory. + Provision, + /// `start`: start a provisioned sandbox. + Start, + /// `exec`: run a workload in a started sandbox. + Exec, + /// `stop`: stop a started sandbox. + Stop, + /// `deprovision`: remove a sandbox's state directory. + Deprovision, +} + +impl SandboxOperation { + /// Returns the operation's name. + pub const fn as_str(self) -> &'static str { + match self { + Self::Run => "run", + Self::Provision => "provision", + Self::Start => "start", + Self::Exec => "exec", + Self::Stop => "stop", + Self::Deprovision => "deprovision", + } + } + + /// Returns whether the operation launches a sandbox: `run` or `provision`. + fn launches(self) -> bool { + matches!(self, Self::Run | Self::Provision) + } +} + +impl fmt::Display for SandboxOperation { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +/// A `--mount-deny`, `--mount-allow`, or `--mount-write` option, with the position among the +/// `--mount` options of the one before it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PolicyPathOption { + /// The kind of path, which the option gives. + pub kind: PolicyKind, + /// The index of the last `--mount` option before this one, if any. + pub preceding_mount: Option, + /// The host path, as given. + pub path: String, +} + +/// The options of `nvx.py sandbox` that [`SandboxOptions::check`] examines, as given. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SandboxOptions { + /// The operation. + pub operation: SandboxOperation, + /// `--dry-run`. + pub dry_run: bool, + /// `--cwd`. + pub cwd: Option, + /// The `--environment` options. + pub environment: Vec, + /// `--environment-file`. + pub environment_file: Option, + /// `--inherit-default-environment`. + pub inherit_default_environment: bool, + /// Whether any network policy option was given: `--net`, `--network-profile`, + /// `--network-egress`, `--network-ingress`, `--network-egress-policy-file`, + /// `--host-loopback`, `--network-proxy`, `--network-egress-allow`, + /// `--network-egress-deny`, or `--host-loopback-forward`. + pub network_options: bool, + /// Whether `--net` was given. + pub net: bool, + /// Whether `--network-profile` was given. + pub network_profile: bool, + /// `--entrypoint`. + pub entrypoint: String, + /// Whether `--outcome-report` was given. + pub outcome_report: bool, + /// The `--mount` options. + pub mounts: Vec, + /// The `--mount-deny`, `--mount-allow`, and `--mount-write` options, in order. + pub policy_paths: Vec, + /// `--mount-owner`. + pub mount_owner: Option, + /// Whether any `--layer` was given. + pub layers: bool, + /// Whether `--scratch` was given. + pub scratch: bool, +} + +impl SandboxOptions { + /// Returns the options of an operation with the defaults of `nvx.py sandbox`. + pub fn new(operation: SandboxOperation) -> Self { + Self { + operation, + dry_run: false, + cwd: None, + environment: Vec::new(), + environment_file: None, + inherit_default_environment: false, + network_options: false, + net: false, + network_profile: false, + entrypoint: super::DEFAULT_ENTRYPOINT.to_owned(), + outcome_report: false, + mounts: Vec::new(), + policy_paths: Vec::new(), + mount_owner: None, + layers: false, + scratch: false, + } + } + + /// Applies the checks of `command_sandbox` that precede the launch, in its order, and + /// returns the managed exec environment of an `exec`, if one was given. + /// + /// It refuses `--dry-run` except for `run`, both `--environment` and `--environment-file`, + /// the managed exec options except for `exec`, an `exec` environment that + /// [`exec_environment_from_file`] or [`validate_exec_environment`] refuses, network policy + /// options except for `run` and `provision`, a systemd entrypoint for `run`, `provision`, + /// and `exec`, `--outcome-report` except for `run` and `exec`, share options without a + /// `--mount`, `--mount` except for `run` and `provision`, and for `run` and `provision`, + /// `--net` without `--network-profile` or the reverse, and a missing `--layer` or + /// `--scratch`. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). + pub fn check(&self) -> Result>> { + let operation = self.operation; + if self.dry_run && operation != SandboxOperation::Run { + return Err(refused("--dry-run is only valid for sandbox run")); + } + if !self.environment.is_empty() && self.environment_file.is_some() { + return Err(refused( + "--environment and --environment-file are mutually exclusive", + )); + } + if operation != SandboxOperation::Exec + && (self.cwd.is_some() + || !self.environment.is_empty() + || self.environment_file.is_some() + || self.inherit_default_environment) + { + return Err(refused( + "managed execution options require the sandbox exec operation", + )); + } + let mut environment = None; + if operation == SandboxOperation::Exec { + if let Some(file) = &self.environment_file { + environment = Some(exec_environment_from_file(file)?); + } else if !self.environment.is_empty() { + validate_exec_environment(&self.environment)?; + environment = Some(self.environment.clone()); + } + } + if !operation.launches() && self.network_options { + return Err(refused( + "network policy options are only valid for sandbox run or provision", + )); + } + if matches!( + operation, + SandboxOperation::Run | SandboxOperation::Provision | SandboxOperation::Exec + ) && SYSTEMD_ENTRYPOINTS.contains(&self.entrypoint.as_str()) + { + return Err(refused( + "systemd entrypoints are unsupported by the sandbox security profile", + )); + } + if self.outcome_report + && !matches!(operation, SandboxOperation::Run | SandboxOperation::Exec) + { + return Err(refused( + "--outcome-report is only valid for one-shot run or managed exec", + )); + } + for kind in POLICY_KINDS { + if self.mounts.is_empty() && self.policy_paths.iter().any(|path| path.kind == kind) { + return Err(refused(format!("{} requires --mount", kind.option()))); + } + } + if self.mount_owner.is_some() && self.mounts.is_empty() { + return Err(refused("--mount-owner requires --mount")); + } + if !self.mounts.is_empty() && !operation.launches() { + return Err(refused( + "--mount is only valid for sandbox run or provision", + )); + } + if operation.launches() { + if self.net != self.network_profile { + return Err(refused( + "--net and --network-profile must be specified together", + )); + } + if !self.layers || !self.scratch { + return Err(refused(format!( + "sandbox {operation} requires --layer and --scratch" + ))); + } + } + Ok(environment) + } +} + +/// Returns the shares of the `--mount` values in `mounts`, each with its access-policy paths, the +/// `owner` (`vmm` by default), and the kind that [`mount_kind`] finds for its host path. +/// +/// With one `--mount`, every policy path names a path in it. With several, each names a path in +/// the `--mount` before it. Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) +/// for a policy path without a `--mount` before it among several, and as +/// [`SandboxMount::parse`] and [`SandboxMount::with_kind`] do. +pub fn sandbox_mounts( + mounts: &[String], + policy_paths: &[PolicyPathOption], + owner: Option, +) -> Result> { + sandbox_mounts_with(Flavor::NATIVE, mounts, policy_paths, owner, &|mount| { + mount_kind(mount.host_path()) + }) +} + +/// Implements [`sandbox_mounts`] for host paths of `flavor`, with `kind_of` as [`mount_kind`]. +pub(crate) fn sandbox_mounts_with( + flavor: Flavor, + mounts: &[String], + policy_paths: &[PolicyPathOption], + owner: Option, + kind_of: &dyn Fn(&SandboxMount) -> MountKind, +) -> Result> { + let mut policies = vec![MountPolicy::default(); mounts.len()]; + for kind in POLICY_KINDS { + for option in policy_paths.iter().filter(|option| option.kind == kind) { + let index = match (mounts.len(), option.preceding_mount) { + (1, _) => 0, + (_, Some(index)) => index, + (_, None) => { + return Err(refused(format!( + "with several --mount options, each {} must follow the --mount whose \ + host directory it names", + kind.option() + ))); + } + }; + let policy = policies.get_mut(index).ok_or_else(|| { + refused(format!( + "{} follows --mount number {}, which is missing", + kind.option(), + index + 1 + )) + })?; + match kind { + PolicyKind::Denied => &mut policy.denied, + PolicyKind::Allowed => &mut policy.allowed, + PolicyKind::Writable => &mut policy.writable, + } + .push(option.path.clone()); + } + } + let owner = owner.unwrap_or(MountOwner::Vmm); + mounts + .iter() + .zip(policies) + .map(|(value, policy)| { + let mount = SandboxMount::parse_in(flavor, value, policy, owner, MountKind::Directory)?; + mount.with_kind(kind_of(&mount)) + }) + .collect() +} + +/// Refuses the guest targets of `nvx.py run` shares that the guest cannot bind from an +/// aggregate: targets that would not survive the kernel command line, or that hide each other or +/// the aggregate itself. `nvx.py run` checks them when it attaches several shares, or a file. +/// +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). +pub fn validate_run_share_targets(targets: &[&str]) -> Result<()> { + for (index, target) in targets.iter().enumerate() { + if !target.starts_with('/') + || *target == "/" + || has_forbidden_character(target) + || has_irregular_component(target) + { + return Err(refused(format!( + "invalid --mount target {}: with several shares, or a shared file, each target \ + must be an absolute non-root path without empty, dot, or parent components, \ + whitespace, '\\', '=', or ','", + repr(target) + ))); + } + for other in std::iter::once(&AGGREGATE_MOUNT_TARGET).chain(&targets[..index]) { + let inside = |path: &str, directory: &str| { + path.strip_prefix(directory) + .is_some_and(|rest| rest.starts_with('/')) + }; + if target == other || inside(target, other) || inside(other, target) { + return Err(refused(format!( + "--mount targets {other} and {target} overlap; a share cannot hide another" + ))); + } + } + } + Ok(()) +} + +/// Refuses a launch that would run systemd: a systemd entrypoint, or a distro layer whose +/// manifest, `LAYER.manifest.json` next to it, lists the `systemd` package. +/// +/// A layer without a manifest passes. A manifest must be a JSON object of format 1 that names +/// the layer and its SHA-256 digest, with a list of packages that each have a name. Fails with +/// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported), or +/// [`ErrorKind::Io`](crate::ErrorKind::Io) if the layer cannot be read to compute its digest. +pub fn validate_sandbox_systemd_policy(launch: &SandboxLaunch) -> Result<()> { + if SYSTEMD_ENTRYPOINTS.contains(&launch.settings().entrypoint.as_str()) { + return Err(refused( + "systemd entrypoints are unsupported by the sandbox security profile", + )); + } + let Some(distro) = launch + .layers() + .iter() + .find(|layer| layer.role() == LayerRole::Distro) + else { + return Ok(()); + }; + let path = distro.pure_path(); + let Some(manifest) = path.with_name(&format!("{}{DISTRO_MANIFEST_SUFFIX}", path.name())) else { + // `with_name` raises `ValueError` for a path without a name. + let class = match path.flavor() { + Flavor::Posix => "PosixPath", + Flavor::Windows => "WindowsPath", + }; + return Err(refused(format!( + "{class}({}) has an empty name", + repr(&path.as_str().replace('\\', "/")) + ))); + }; + if !fs::exists(manifest.as_path()) { + return Ok(()); + } + let invalid = |detail: String| refused(format!("invalid sandbox distro manifest: {detail}")); + let bytes = std::fs::read(manifest.as_path()) + .map_err(|error| invalid(fs::os_error_message(&error, Some(manifest.as_str()))))?; + // `read_text` decodes strictly, outside the errors that become this message, and reads with + // universal newlines. + let text = decode_utf8(&bytes) + .map_err(refused)? + .replace("\r\n", "\n") + .replace('\r', "\n"); + let document = match json::read(&text, &mut Loads) { + Ok(document) => document, + Err(ReadError::Syntax { message, offset }) => { + return Err(invalid(json::syntax_error(&text, message, offset))); + } + Err(ReadError::Hook(error)) => return Err(refused(error.message(&text))), + }; + let root = document.root(); + if !matches!(document.node(root), Node::Object(_)) { + return Err(invalid("expected a JSON object".to_owned())); + } + let mismatch = || refused("sandbox distro manifest does not match its artifact"); + if !document.get(root, "format").is_some_and(equals_one) { + return Err(mismatch()); + } + if !document + .get(root, "artifact") + .is_some_and(|artifact| string_equals(artifact, path.name())) + { + return Err(mismatch()); + } + let digest = sha256_file(path)?; + if !document + .get(root, "artifact_sha256") + .is_some_and(|value| string_equals(value, &digest)) + { + return Err(mismatch()); + } + let invalid_packages = || refused("sandbox distro manifest has invalid package metadata"); + let Some(Node::Array(packages)) = document.get(root, "packages") else { + return Err(invalid_packages()); + }; + let mut systemd = false; + for &package in packages { + if !matches!(document.node(package), Node::Object(_)) { + return Err(invalid_packages()); + } + let Some(Node::String(name)) = document.get(package, "name") else { + return Err(invalid_packages()); + }; + systemd |= name.as_bytes() == b"systemd"; + } + if systemd { + return Err(refused( + "systemd images are unsupported by the sandbox security profile", + )); + } + Ok(()) +} + +/// Returns whether a JSON value equals Python's `1`: the integer, a float of that value, or +/// `true`. +fn equals_one(value: &Node) -> bool { + match value { + Node::Integer(literal) => literal.as_ref() == "1", + Node::Float(float) => *float == 1.0, + Node::Bool(boolean) => *boolean, + _ => false, + } +} + +/// Returns whether a JSON value is the string `text`. +fn string_equals(value: &Node, text: &str) -> bool { + matches!(value, Node::String(string) if string.as_bytes() == text.as_bytes()) +} + +/// Returns the lowercase hexadecimal SHA-256 digest of the file at `path`. +fn sha256_file(path: &PurePath) -> Result { + let io_error = |error: std::io::Error| os_error(&error, Some(path.as_str())); + let mut file = File::open(path.as_path()).map_err(io_error)?; + let mut hasher = Sha256::new(); + let mut buffer = vec![0; 1024 * 1024]; + loop { + let read = file.read(&mut buffer).map_err(io_error)?; + if read == 0 { + break; + } + hasher.update(&buffer[..read]); + } + Ok(hasher + .finalize() + .iter() + .map(|byte| format!("{byte:02x}")) + .collect()) +} + +/// Checks a managed exec environment, as `encode_exec_environment` does: at most +/// [`MAX_EXEC_ENVIRONMENT`] entries, each a `KEY=VALUE` string with a nonempty key, without NUL, +/// of at most [`MAX_EXEC_ENVIRONMENT_BYTES`] bytes, and no key twice. +/// +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported). +pub fn validate_exec_environment(entries: &[String]) -> Result<()> { + let entries: Vec = entries + .iter() + .map(|entry| PyText::from(entry.as_str())) + .collect(); + validate_entries(&entries) +} + +/// Implements [`validate_exec_environment`] for strings that may hold surrogates. +fn validate_entries(entries: &[PyText]) -> Result<()> { + if entries.len() > MAX_EXEC_ENVIRONMENT { + return Err(refused(format!( + "managed exec environment exceeds {MAX_EXEC_ENVIRONMENT} entries" + ))); + } + let mut names = std::collections::HashSet::new(); + for entry in entries { + // Python encodes the entry, which fails at its first run of surrogates, before it + // checks it. + let entry = entry.encode_utf8().map_err(refused)?; + let (name, separator) = match entry.split_once('=') { + Some((name, _)) => (name, true), + None => (entry, false), + }; + if !separator + || name.is_empty() + || entry.contains('\0') + || entry.len() > MAX_EXEC_ENVIRONMENT_BYTES + { + return Err(refused(format!( + "managed exec environment entries must be non-empty KEY=VALUE strings of at most \ + {MAX_EXEC_ENVIRONMENT_BYTES} bytes" + ))); + } + if !names.insert(name.to_owned()) { + return Err(refused(format!( + "managed exec environment contains duplicate key: {name}" + ))); + } + } + Ok(()) +} + +/// Reads the managed exec environment of `--environment-file`: a JSON array of `KEY=VALUE` +/// strings of at most [`MAX_ENVIRONMENT_FILE_BYTES`] bytes, which must then pass +/// [`validate_exec_environment`]. +/// +/// The file is read as `json.loads` reads it, so Python's extensions to JSON are accepted, and +/// `\u` escapes of unpaired surrogates fail as Python fails to encode them. Fails with +/// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported), naming the file as `pathlib` +/// normalizes it when it cannot be read or decoded. +pub fn exec_environment_from_file(path: &str) -> Result> { + let path = PurePath::native(path); + let unreadable = || { + refused(format!( + "failed to read managed execution environment: {path}" + )) + }; + let mut data = Vec::new(); + File::open(path.as_path()) + .and_then(|file| { + file.take(MAX_ENVIRONMENT_FILE_BYTES as u64 + 1) + .read_to_end(&mut data) + }) + .map_err(|_| unreadable())?; + if data.len() > MAX_ENVIRONMENT_FILE_BYTES { + return Err(refused(format!( + "managed execution environment file exceeds {MAX_ENVIRONMENT_FILE_BYTES}-byte limit" + ))); + } + let text = std::str::from_utf8(&data).map_err(|_| unreadable())?; + let document = match json::read(text, &mut Loads) { + Ok(document) => document, + Err(ReadError::Syntax { .. }) => return Err(unreadable()), + // `int` raises a `ValueError` that is not a decoding error. + Err(ReadError::Hook(error)) => return Err(refused(error.message(text))), + }; + let not_strings = || { + refused("managed execution environment file must contain a JSON array of KEY=VALUE strings") + }; + let Node::Array(values) = document.node(document.root()) else { + return Err(not_strings()); + }; + let entries = values + .iter() + .map(|&index| match document.node(index) { + Node::String(string) => Ok(string.clone()), + _ => Err(not_strings()), + }) + .collect::>>()?; + validate_entries(&entries)?; + Ok(entries + .iter() + .map(|entry| entry.as_str().unwrap_or_default().to_owned()) + .collect()) +} + +#[cfg(test)] +mod tests; diff --git a/nvx-rs/src/host/sandbox/options/tests.rs b/nvx-rs/src/host/sandbox/options/tests.rs new file mode 100644 index 000000000..47fa37b90 --- /dev/null +++ b/nvx-rs/src/host/sandbox/options/tests.rs @@ -0,0 +1,974 @@ +//! The `nvx.py sandbox` and `nvx.py run` option tests of `scripts/test_nvx_tools.py`, ported. + +use std::fs; +use std::path::Path; + +use super::*; +use crate::ErrorKind; +use crate::host::python::fs::resolve; +use crate::host::sandbox::mount::{aggregate_child_name, mounts_openvmm_arguments}; +use crate::host::sandbox::{SandboxLayer, SandboxSettings}; + +fn message(result: Result) -> String { + let error = result.unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unsupported, "{error}"); + error.to_string() +} + +fn launching(operation: SandboxOperation) -> SandboxOptions { + SandboxOptions { + layers: true, + scratch: true, + ..SandboxOptions::new(operation) + } +} + +#[test] +fn sandbox_rejects_systemd_entrypoint() { + for entrypoint in SYSTEMD_ENTRYPOINTS { + for operation in [ + SandboxOperation::Run, + SandboxOperation::Provision, + SandboxOperation::Exec, + ] { + let options = SandboxOptions { + entrypoint: entrypoint.to_owned(), + ..launching(operation) + }; + assert_eq!( + message(options.check()), + "systemd entrypoints are unsupported by the sandbox security profile" + ); + } + let stop = SandboxOptions { + entrypoint: entrypoint.to_owned(), + ..SandboxOptions::new(SandboxOperation::Stop) + }; + assert_eq!(stop.check().unwrap(), None); + } + let init = SandboxOptions { + entrypoint: "/sbin/init".to_owned(), + ..launching(SandboxOperation::Run) + }; + assert_eq!(init.check().unwrap(), None); +} + +/// A distro layer in a temporary directory. +struct Layer { + /// The temporary directory. + directory: tempfile::TempDir, +} + +impl Layer { + fn new() -> Self { + let directory = tempfile::tempdir().unwrap(); + fs::write(directory.path().join("distro.erofs"), b"distro").unwrap(); + Self { directory } + } + + fn path(&self, name: &str) -> String { + PurePath::native(self.directory.path().to_str().unwrap()) + .join(name) + .as_str() + .to_owned() + } + + fn launch(&self, entrypoint: &str) -> SandboxLaunch { + SandboxLaunch::new( + vec![SandboxLayer::new( + LayerRole::Distro, + &self.path("distro.erofs"), + "11111111-1111-1111-1111-111111111111", + )], + &self.path("scratch.ext4"), + SandboxSettings { + entrypoint: entrypoint.to_owned(), + ..SandboxSettings::default() + }, + ) + .unwrap() + } + + fn manifest(&self, text: &str) { + fs::write(self.path("distro.erofs.manifest.json"), text).unwrap(); + } +} + +fn distro_sha256() -> String { + Sha256::digest(b"distro") + .iter() + .map(|byte| format!("{byte:02x}")) + .collect() +} + +#[test] +fn sandbox_allows_non_systemd_sbin_init() { + let layer = Layer::new(); + validate_sandbox_systemd_policy(&layer.launch("/sbin/init")).unwrap(); + assert_eq!( + message(validate_sandbox_systemd_policy( + &layer.launch("/lib/systemd/systemd") + )), + "systemd entrypoints are unsupported by the sandbox security profile" + ); +} + +#[test] +fn sandbox_rejects_systemd_from_bound_distro_metadata() { + let layer = Layer::new(); + let digest = distro_sha256(); + let manifest = |format: &str, artifact: &str, sha256: &str, packages: &str| { + layer.manifest(&format!( + "{{\"format\": {format}, \"artifact\": {artifact}, \"artifact_sha256\": {sha256}, \ + \"packages\": {packages}}}" + )); + validate_sandbox_systemd_policy(&layer.launch("/sbin/init")) + }; + let sha256 = format!("\"{digest}\""); + assert_eq!( + message(manifest( + "1", + "\"distro.erofs\"", + &sha256, + "[{\"name\": \"systemd\"}]" + )), + "systemd images are unsupported by the sandbox security profile" + ); + // Python compares the format numerically, so 1.0 and true are 1. + for format in ["1", "1.0", "true"] { + manifest( + format, + "\"distro.erofs\"", + &sha256, + "[{\"name\": \"bash\"}]", + ) + .unwrap(); + } + for (format, artifact, sha256) in [ + ("\"1\"", "\"distro.erofs\"", sha256.as_str()), + ("2", "\"distro.erofs\"", &sha256), + ("1", "\"other.erofs\"", &sha256), + ("1", "\"distro.erofs\"", "\"0\""), + ] { + assert_eq!( + message(manifest(format, artifact, sha256, "[]")), + "sandbox distro manifest does not match its artifact" + ); + } + for packages in [ + "{}", + "[1]", + "[{}]", + "[{\"name\": 1}]", + "[{\"name\": \"systemd\"}, 1]", + ] { + assert_eq!( + message(manifest("1", "\"distro.erofs\"", &sha256, packages)), + "sandbox distro manifest has invalid package metadata" + ); + } + layer.manifest("[]"); + assert_eq!( + message(validate_sandbox_systemd_policy(&layer.launch("/sbin/init"))), + "invalid sandbox distro manifest: expected a JSON object" + ); + layer.manifest("{\r\n\"format\": }"); + assert_eq!( + message(validate_sandbox_systemd_policy(&layer.launch("/sbin/init"))), + "invalid sandbox distro manifest: Expecting value: line 2 column 11 (char 12)" + ); + fs::write(layer.path("distro.erofs.manifest.json"), b"{\xff}").unwrap(); + assert_eq!( + message(validate_sandbox_systemd_policy(&layer.launch("/sbin/init"))), + "'utf-8' codec can't decode byte 0xff in position 1: invalid start byte" + ); + // A layer without a manifest passes. + fs::remove_file(layer.path("distro.erofs.manifest.json")).unwrap(); + validate_sandbox_systemd_policy(&layer.launch("/sbin/init")).unwrap(); +} + +#[test] +fn sandbox_managed_operations_reject_dry_run_before_state_access() { + for operation in [ + SandboxOperation::Provision, + SandboxOperation::Start, + SandboxOperation::Exec, + SandboxOperation::Stop, + SandboxOperation::Deprovision, + ] { + let options = SandboxOptions { + dry_run: true, + ..SandboxOptions::new(operation) + }; + assert_eq!( + message(options.check()), + "--dry-run is only valid for sandbox run" + ); + } + let run = SandboxOptions { + dry_run: true, + ..launching(SandboxOperation::Run) + }; + run.check().unwrap(); +} + +#[test] +fn sandbox_exec_forwards_explicit_empty_environment_and_cwd() { + let directory = tempfile::tempdir().unwrap(); + let file = directory.path().join("environment.json"); + fs::write(&file, "[]").unwrap(); + let options = SandboxOptions { + cwd: Some("/work".to_owned()), + environment_file: Some(file.to_str().unwrap().to_owned()), + ..SandboxOptions::new(SandboxOperation::Exec) + }; + assert_eq!(options.check().unwrap(), Some(Vec::new())); +} + +#[test] +fn sandbox_exec_forwards_environment_inheritance() { + for (environment, expected) in [ + (vec!["A=1"], Some(vec!["A=1"])), + (vec!["A=1", "TERM=dumb"], Some(vec!["A=1", "TERM=dumb"])), + // Without entries the workload gets the defaults whatever the flag says. + (Vec::new(), None), + ] { + let options = SandboxOptions { + environment: environment.iter().map(|&entry| entry.to_owned()).collect(), + inherit_default_environment: true, + ..SandboxOptions::new(SandboxOperation::Exec) + }; + let expected = + expected.map(|entries| entries.iter().map(|&entry| entry.to_owned()).collect()); + assert_eq!(options.check().unwrap(), expected); + } +} + +#[test] +fn sandbox_exec_rejects_ambiguous_or_one_shot_environment() { + let both = SandboxOptions { + environment: vec!["A=1".to_owned()], + environment_file: Some("environment.json".to_owned()), + ..SandboxOptions::new(SandboxOperation::Exec) + }; + assert_eq!( + message(both.check()), + "--environment and --environment-file are mutually exclusive" + ); + let one_shot = SandboxOptions { + environment: vec!["A=1".to_owned()], + ..SandboxOptions::new(SandboxOperation::Run) + }; + let exec_only = "managed execution options require the sandbox exec operation"; + assert_eq!(message(one_shot.check()), exec_only); + for operation in [SandboxOperation::Run, SandboxOperation::Start] { + let inheriting = SandboxOptions { + inherit_default_environment: true, + ..SandboxOptions::new(operation) + }; + assert_eq!(message(inheriting.check()), exec_only); + let cwd = SandboxOptions { + cwd: Some("/".to_owned()), + ..SandboxOptions::new(operation) + }; + assert_eq!(message(cwd.check()), exec_only); + } +} + +#[test] +fn sandbox_environment_file_is_bounded_before_state_access() { + let directory = tempfile::tempdir().unwrap(); + let file = directory.path().join("environment.json"); + fs::write(&file, vec![b' '; MAX_ENVIRONMENT_FILE_BYTES + 1]).unwrap(); + assert_eq!( + message(exec_environment_from_file(file.to_str().unwrap())), + "managed execution environment file exceeds 1048576-byte limit" + ); + // A file of the limit is read, and its spaces are no JSON value. + fs::write(&file, vec![b' '; MAX_ENVIRONMENT_FILE_BYTES]).unwrap(); + let path = PurePath::native(file.to_str().unwrap()); + assert_eq!( + message(exec_environment_from_file(file.to_str().unwrap())), + format!("failed to read managed execution environment: {path}") + ); +} + +#[test] +fn sandbox_environment_is_validated_before_state_access() { + let too_many: Vec = (0..257).map(|index| format!("KEY{index}=value")).collect(); + let entries = |entries: &[&str]| entries.iter().map(|&entry| entry.to_owned()).collect(); + let non_empty = "managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"; + for (environment, expected) in [ + ( + too_many, + "managed exec environment exceeds 256 entries".to_owned(), + ), + (entries(&["NO_EQUALS"]), non_empty.to_owned()), + (entries(&["=empty-key"]), non_empty.to_owned()), + (entries(&["KEY=embedded\0nul"]), non_empty.to_owned()), + ( + vec![format!("KEY={}", "\u{2603}".repeat(1365))], + non_empty.to_owned(), + ), + ( + entries(&["DUPLICATE=one", "DUPLICATE=two"]), + "managed exec environment contains duplicate key: DUPLICATE".to_owned(), + ), + ] { + let options = SandboxOptions { + environment, + ..SandboxOptions::new(SandboxOperation::Exec) + }; + assert_eq!(message(options.check()), expected); + } + // An entry of exactly 4096 bytes fits. + validate_exec_environment(&[format!("K={}", "v".repeat(4094))]).unwrap(); +} + +#[test] +fn sandbox_environment_file_entries_are_validated_before_state_access() { + let directory = tempfile::tempdir().unwrap(); + let file = directory.path().join("environment.json"); + let read = |text: &str| { + fs::write(&file, text).unwrap(); + exec_environment_from_file(file.to_str().unwrap()) + }; + assert_eq!( + message(read(r#"["DUPLICATE=one", "DUPLICATE=two"]"#)), + "managed exec environment contains duplicate key: DUPLICATE" + ); + assert_eq!( + read(r#"["A=1", "B=\u00e9"]"#).unwrap(), + ["A=1".to_owned(), "B=\u{e9}".to_owned()] + ); + let not_strings = + "managed execution environment file must contain a JSON array of KEY=VALUE strings"; + for text in ["{}", "\"A=1\"", "[1]", "[NaN]", "[[\"A=1\"]]"] { + assert_eq!(message(read(text)), not_strings, "{text}"); + } + let path = PurePath::native(file.to_str().unwrap()); + let unreadable = format!("failed to read managed execution environment: {path}"); + for text in ["", "[", "[\"A=1\",]", "\u{feff}[]", "[\"a\tb\"]"] { + assert_eq!(message(read(text)), unreadable, "{text:?}"); + } + fs::write(&file, b"[\"\xff\"]").unwrap(); + assert_eq!( + message(exec_environment_from_file(file.to_str().unwrap())), + unreadable + ); + // Python refuses to encode unpaired surrogates, after it accepted them in the file. + assert_eq!( + message(read(r#"["A=1", "B=\ud800"]"#)), + "'utf-8' codec can't encode character '\\ud800' in position 2: surrogates not allowed" + ); + assert_eq!( + message(read(r#"["B=\udc00\ud800x"]"#)), + "'utf-8' codec can't encode characters in position 2-3: surrogates not allowed" + ); + // `int` refuses an integer of more than 4300 digits. + assert_eq!( + message(read(&format!("[{}]", "1".repeat(4301)))), + "Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; \ + use sys.set_int_max_str_digits() to increase the limit" + ); + assert_eq!( + message(exec_environment_from_file( + directory.path().join("missing.json").to_str().unwrap() + )), + format!( + "failed to read managed execution environment: {}", + PurePath::native(directory.path().join("missing.json").to_str().unwrap()) + ) + ); +} + +#[test] +fn managed_non_launch_operations_reject_network_policy_options() { + for operation in [ + SandboxOperation::Start, + SandboxOperation::Exec, + SandboxOperation::Stop, + SandboxOperation::Deprovision, + ] { + let options = SandboxOptions { + network_options: true, + ..SandboxOptions::new(operation) + }; + assert_eq!( + message(options.check()), + "network policy options are only valid for sandbox run or provision" + ); + } + for operation in [SandboxOperation::Run, SandboxOperation::Provision] { + let options = SandboxOptions { + network_options: true, + net: true, + ..launching(operation) + }; + assert_eq!( + message(options.check()), + "--net and --network-profile must be specified together" + ); + let profile = SandboxOptions { + network_options: true, + network_profile: true, + ..launching(operation) + }; + assert_eq!( + message(profile.check()), + "--net and --network-profile must be specified together" + ); + let missing = SandboxOptions { + scratch: false, + ..launching(operation) + }; + assert_eq!( + message(missing.check()), + format!("sandbox {operation} requires --layer and --scratch") + ); + } + let report = SandboxOptions { + outcome_report: true, + ..SandboxOptions::new(SandboxOperation::Start) + }; + assert_eq!( + message(report.check()), + "--outcome-report is only valid for one-shot run or managed exec" + ); +} + +#[test] +fn sandbox_command_rejects_misplaced_mount_options() { + for kind in POLICY_KINDS { + let options = SandboxOptions { + policy_paths: vec![PolicyPathOption { + kind, + preceding_mount: None, + path: "secrets".to_owned(), + }], + ..launching(SandboxOperation::Run) + }; + assert_eq!( + message(options.check()), + format!("{} requires --mount", kind.option()) + ); + } + let owner = SandboxOptions { + mount_owner: Some(MountOwner::Caller), + ..launching(SandboxOperation::Run) + }; + assert_eq!(message(owner.check()), "--mount-owner requires --mount"); + for operation in [ + SandboxOperation::Start, + SandboxOperation::Exec, + SandboxOperation::Stop, + SandboxOperation::Deprovision, + ] { + let options = SandboxOptions { + mounts: vec!["/workspace,share,rw".to_owned()], + ..SandboxOptions::new(operation) + }; + assert_eq!( + message(options.check()), + "--mount is only valid for sandbox run or provision" + ); + } +} + +/// Host directories for shares in a temporary directory, with a distro layer and a scratch +/// image. +struct Shares { + /// The temporary directory. + directory: tempfile::TempDir, + /// The directory, resolved. + root: PurePath, +} + +impl Shares { + fn new() -> Self { + let directory = tempfile::tempdir().unwrap(); + let root = resolve(&PurePath::native(directory.path().to_str().unwrap())).unwrap(); + fs::write(root.join("distro.erofs").as_path(), b"distro").unwrap(); + fs::write(root.join("scratch.ext4").as_path(), b"scratch").unwrap(); + Self { directory, root } + } + + fn path(&self, name: &str) -> String { + let _ = &self.directory; + self.root.join(name).as_str().to_owned() + } + + fn directory(&self, name: &str) -> String { + let path = self.path(name); + fs::create_dir_all(&path).unwrap(); + path + } + + /// Runs the steps of `nvx.py sandbox --dry-run` that build the OpenVMM arguments and the + /// kernel command line of these shares. + fn run( + &self, + mounts: &[String], + policy_paths: &[(PolicyKind, Option, &str)], + owner: Option, + ) -> Result<(Vec, String)> { + let mounts = mounts.to_vec(); + let policy_paths: Vec = policy_paths + .iter() + .map(|&(kind, preceding_mount, path)| PolicyPathOption { + kind, + preceding_mount, + path: path.to_owned(), + }) + .collect(); + SandboxOptions { + mounts: mounts.clone(), + policy_paths: policy_paths.clone(), + mount_owner: owner, + dry_run: true, + ..launching(SandboxOperation::Run) + } + .check()?; + let launch = SandboxLaunch::new( + vec![SandboxLayer::new( + LayerRole::Distro, + &self.path("distro.erofs"), + "11111111-1111-1111-1111-111111111111", + )], + &self.path("scratch.ext4"), + SandboxSettings { + mounts: sandbox_mounts(&mounts, &policy_paths, owner)?, + ..SandboxSettings::default() + }, + )?; + launch.validated()?; + validate_sandbox_systemd_policy(&launch)?; + Ok((launch.openvmm_arguments()?, launch.kernel_command_line("")?)) + } +} + +/// Returns the values of `option` in `arguments`. +fn values(arguments: &[String], option: &str) -> Vec { + arguments + .windows(2) + .filter(|pair| pair[0] == option) + .map(|pair| pair[1].clone()) + .collect() +} + +#[test] +fn sandbox_command_forwards_mount_to_openvmm() { + let shares = Shares::new(); + let share = shares.directory("share"); + let (arguments, _) = shares + .run( + &[format!("/workspace,{share},rw")], + &[(PolicyKind::Denied, Some(0), "secrets")], + None, + ) + .unwrap(); + assert_eq!( + values(&arguments, "--mount"), + [format!("/workspace,{share},rw")] + ); + assert_eq!(values(&arguments, "--mount-deny"), ["secrets"]); + assert!(!arguments.contains(&"--mount-owner".to_owned())); +} + +#[test] +fn sandbox_command_shares_a_file() { + let shares = Shares::new(); + let settings = shares.path("settings.json"); + fs::write(&settings, b"{}").unwrap(); + let (arguments, command_line) = shares + .run( + &[format!("/etc/app/settings.json,{settings},ro")], + &[], + None, + ) + .unwrap(); + let name = aggregate_child_name(0, "/etc/app/settings.json"); + // The regular file is a child of its own, without its directory, which OpenVMM must find to + // be a regular file. + assert!(!arguments.contains(&"--mount".to_owned())); + assert_eq!( + values(&arguments, "--mount-child"), + [format!("{name},{settings},ro,file")] + ); + assert!(command_line.contains(&format!("nvx_share={name},/etc/app/settings.json,ro,file"))); + let error = shares + .run( + &[format!("/etc/app/settings.json,{settings},rw")], + &[(PolicyKind::Writable, Some(0), &settings)], + None, + ) + .unwrap_err(); + assert!(error.to_string().contains("shares a file"), "{error}"); +} + +#[test] +fn sandbox_command_shares_host_paths_with_commas() { + let shares = Shares::new(); + let alpha = shares.directory("alpha,beta"); + let reports = shares.directory("2026, final"); + shares.directory("alpha,beta/secret"); + shares.directory("2026, final/secret"); + let (arguments, _) = shares + .run( + &[format!("/work,{alpha},rw")], + &[(PolicyKind::Denied, Some(0), "secret")], + None, + ) + .unwrap(); + assert_eq!(values(&arguments, "--mount"), [format!("/work,{alpha},rw")]); + assert_eq!(values(&arguments, "--mount-deny"), ["secret"]); + let (arguments, _) = shares + .run( + &[ + format!("/work,{alpha},rw"), + format!("/reports,{reports},ro"), + ], + &[ + (PolicyKind::Denied, Some(0), "secret"), + (PolicyKind::Denied, Some(1), "secret"), + ], + None, + ) + .unwrap(); + let (work, report) = ( + aggregate_child_name(0, "/work"), + aggregate_child_name(1, "/reports"), + ); + let pairs: Vec<(String, String)> = arguments + .windows(2) + .filter(|pair| pair[0] == "--mount-child" || pair[0] == "--mount-deny") + .map(|pair| (pair[0].clone(), pair[1].clone())) + .collect(); + assert_eq!( + pairs, + [ + ("--mount-child".to_owned(), format!("{work},{alpha},rw")), + ("--mount-child".to_owned(), format!("{report},{reports},ro")), + ( + "--mount-deny".to_owned(), + shares.root.join("alpha,beta/secret").as_str().to_owned() + ), + ( + "--mount-deny".to_owned(), + shares.root.join("2026, final/secret").as_str().to_owned() + ), + ] + ); + let error = shares + .run(&[format!("/work,{alpha}")], &[], None) + .unwrap_err(); + assert!( + error.to_string().contains("end the value with ,ro or ,rw"), + "{error}" + ); +} + +#[test] +fn sandbox_command_forwards_mount_owner() { + let shares = Shares::new(); + let share = shares.directory("share"); + let result = shares.run( + &[format!("/workspace,{share},rw")], + &[], + Some(MountOwner::Caller), + ); + if cfg!(windows) { + assert!(message(result).contains("Linux host")); + return; + } + let owner = crate::host::python::fs::stat(Path::new(&share)).unwrap(); + if owner.uid == 0 || owner.gid == 0 { + // Caller ownership refuses a root-owned share. + return; + } + let (arguments, _) = result.unwrap(); + assert_eq!(values(&arguments, "--mount-owner"), ["caller"]); + let position = |option: &str| arguments.iter().position(|argument| argument == option); + assert!(position("--mount") < position("--mount-owner")); +} + +#[test] +fn sandbox_command_attributes_policy_paths_to_the_preceding_mount() { + let shares = Shares::new(); + let work = shares.directory("work"); + let tools = shares.directory("tools"); + let mounts = [ + format!("/workspace,{work},rw"), + format!("/opt/hostedtoolcache,{tools},ro"), + ]; + let policy = [ + (PolicyKind::Denied, Some(0), "logs"), + (PolicyKind::Allowed, Some(0), "logs/payloads"), + (PolicyKind::Writable, Some(0), "out"), + (PolicyKind::Denied, Some(1), "credentials"), + ]; + let options: Vec = policy + .iter() + .map(|&(kind, preceding_mount, path)| PolicyPathOption { + kind, + preceding_mount, + path: path.to_owned(), + }) + .collect(); + let parsed = sandbox_mounts(&mounts, &options, None).unwrap(); + assert_eq!(parsed[0].policy().denied, ["logs"]); + assert_eq!(parsed[0].policy().allowed, ["logs/payloads"]); + assert_eq!(parsed[0].policy().writable, ["out"]); + assert_eq!(parsed[1].policy().denied, ["credentials"]); + assert!(parsed[1].policy().allowed.is_empty() && parsed[1].policy().writable.is_empty()); + let (arguments, _) = shares.run(&mounts, &policy, None).unwrap(); + // With several shares, OpenVMM attributes absolute paths. + assert_eq!( + values(&arguments, "--mount-allow"), + [shares.root.join("work/logs/payloads").as_str()] + ); + assert_eq!( + values(&arguments, "--mount-write"), + [shares.root.join("work/out").as_str()] + ); + + let workspace = format!("/workspace,{work},rw"); + let toolcache = format!("/opt/hostedtoolcache,{tools}"); + let cache = shares.root.join("tools/cache").as_str().to_owned(); + let error = |mounts: &[String], policy: &[(PolicyKind, Option, &str)]| { + shares.run(mounts, policy, None).unwrap_err().to_string() + }; + assert_eq!( + error(&[], &[(PolicyKind::Writable, None, "out")]), + "--mount-write requires --mount" + ); + // A --mount-write after the read-only share would narrow it. + assert!( + error( + &[workspace.clone(), format!("{toolcache},ro")], + &[(PolicyKind::Writable, Some(1), "cache")] + ) + .contains("is read-only") + ); + assert!( + error( + &[workspace.clone(), toolcache.clone()], + &[(PolicyKind::Allowed, None, "logs")] + ) + .contains("each --mount-allow must follow the --mount") + ); + // A path in one share cannot be attributed to the other. + assert_eq!( + error( + &[workspace.clone(), toolcache.clone()], + &[(PolicyKind::Writable, Some(0), &cache)] + ), + format!( + "--mount-write {cache} is not inside the host directory of --mount /workspace; with \ + several shares, each --mount-write follows the --mount whose directory it names" + ) + ); + // Only a denied path may name a share's root. + assert!( + error( + &[workspace.clone(), toolcache.clone()], + &[ + (PolicyKind::Denied, Some(0), "logs"), + (PolicyKind::Allowed, Some(0), &work) + ] + ) + .contains(&format!( + "--mount-allow {work} is not inside the host directory" + )) + ); + assert!( + error( + &[workspace.clone(), toolcache.clone()], + &[(PolicyKind::Writable, Some(0), &work)] + ) + .contains(&format!( + "--mount-write {work} is not inside the host directory" + )) + ); +} + +#[test] +fn sandbox_command_hides_a_share_root_behind_its_allowed_paths() { + let shares = Shares::new(); + let work = shares.directory("work"); + let src = shares.directory("work/src"); + let tools = shares.directory("tools"); + let (arguments, _) = shares + .run( + &[ + format!("/workspace,{work},rw"), + format!("/opt/hostedtoolcache,{tools}"), + ], + &[ + (PolicyKind::Denied, Some(0), &work), + (PolicyKind::Allowed, Some(0), &src), + ], + None, + ) + .unwrap(); + // OpenVMM hides the workspace's root behind its one allowed path. + let (workspace, toolcache) = ( + aggregate_child_name(0, "/workspace"), + aggregate_child_name(1, "/opt/hostedtoolcache"), + ); + assert_eq!( + values(&arguments, "--mount-child"), + [ + format!("{workspace},{work},rw"), + format!("{toolcache},{tools},ro") + ] + ); + assert_eq!(values(&arguments, "--mount-deny"), [work]); + assert_eq!(values(&arguments, "--mount-allow"), [src]); +} + +#[test] +fn sandbox_command_attributes_mount_deny_to_the_preceding_mount() { + let shares = Shares::new(); + let work = shares.directory("work"); + let tools = shares.directory("tools"); + let mounts = [ + format!("/workspace,{work},rw"), + format!("/opt/hostedtoolcache,{tools},ro"), + ]; + let policy = [ + (PolicyKind::Denied, Some(0), "secrets"), + (PolicyKind::Denied, Some(1), "credentials"), + (PolicyKind::Denied, Some(1), "logs"), + ]; + let (arguments, command_line) = shares.run(&mounts, &policy, None).unwrap(); + let (workspace, toolcache) = ( + aggregate_child_name(0, "/workspace"), + aggregate_child_name(1, "/opt/hostedtoolcache"), + ); + assert!(!arguments.contains(&"--mount".to_owned())); + assert_eq!(values(&arguments, "--mount-aggregate"), ["/run/nvx/shares"]); + assert_eq!( + values(&arguments, "--mount-child"), + [ + format!("{workspace},{work},rw"), + format!("{toolcache},{tools},ro") + ] + ); + assert_eq!( + values(&arguments, "--mount-deny"), + [ + shares.root.join("work/secrets").as_str(), + shares.root.join("tools/credentials").as_str(), + shares.root.join("tools/logs").as_str(), + ] + ); + assert!(command_line.contains(&format!( + "nvx_share={workspace},/workspace,rw nvx_share={toolcache},/opt/hostedtoolcache,ro" + ))); + + // With one share, a --mount-deny may come first; with several, it must follow the share + // whose directory it hides. + let single = sandbox_mounts( + &[format!("/w,{work},rw")], + &[PolicyPathOption { + kind: PolicyKind::Denied, + preceding_mount: None, + path: "secrets".to_owned(), + }], + None, + ) + .unwrap(); + assert_eq!(single[0].policy().denied, ["secrets"]); + let error = shares + .run( + &[ + format!("/workspace,{work},rw"), + format!("/opt/hostedtoolcache,{tools}"), + ], + &[(PolicyKind::Denied, None, "secrets")], + None, + ) + .unwrap_err(); + assert_eq!( + error.to_string(), + "with several --mount options, each --mount-deny must follow the --mount whose host \ + directory it names" + ); + // More shares are children of the same aggregate. + let cache = shares.directory("cache"); + let (arguments, _) = shares + .run( + &[ + format!("/a,{work}"), + format!("/b,{tools}"), + format!("/c,{cache}"), + ], + &[], + None, + ) + .unwrap(); + assert_eq!(values(&arguments, "--mount-child").len(), 3); +} + +#[test] +fn sandbox_mounts_take_the_kind_of_each_host_path() { + let mounts = ["/a,a".to_owned(), "/b,b.json,rw".to_owned()]; + let kind_of = |mount: &SandboxMount| { + if mount.guest_target() == "/b" { + MountKind::File + } else { + MountKind::Directory + } + }; + let parsed = sandbox_mounts_with( + Flavor::Posix, + &mounts, + &[], + Some(MountOwner::Caller), + &kind_of, + ) + .unwrap(); + assert_eq!(parsed[1].kind(), MountKind::File); + assert_eq!(parsed[0].owner(), MountOwner::Caller); + let arguments = mounts_openvmm_arguments(&parsed).unwrap(); + assert!(arguments.contains(&format!("{},b.json,rw,file", aggregate_child_name(1, "/b")))); +} + +#[test] +fn run_share_targets_bind_from_an_aggregate() { + validate_run_share_targets(&["/workspace", "/opt/hostedtoolcache", "/srv/cache"]).unwrap(); + validate_run_share_targets(&["/config/settings.json"]).unwrap(); + for (targets, expected) in [ + ( + &["/workspace", "/workspace/cache"][..], + "--mount targets /workspace and /workspace/cache overlap; a share cannot hide another", + ), + ( + &["/run/nvx", "/b"], + "--mount targets /run/nvx/shares and /run/nvx overlap; a share cannot hide another", + ), + ( + &["/run/nvx/shares/x"], + "--mount targets /run/nvx/shares and /run/nvx/shares/x overlap; a share cannot hide \ + another", + ), + ] { + assert_eq!(message(validate_run_share_targets(targets)), expected); + } + for target in [ + "/a b", "relative", "/", "/a//b", "/a/./b", "/a\\b", "/a=b", "/a,b", + ] { + assert_eq!( + message(validate_run_share_targets(&[target, "/b"])), + format!( + "invalid --mount target {}: with several shares, or a shared file, each target \ + must be an absolute non-root path without empty, dot, or parent components, \ + whitespace, '\\', '=', or ','", + repr(target) + ) + ); + } + // Unlike a sandbox target, a run target has no length limit. + validate_run_share_targets(&[&format!("/{}", "x".repeat(5000))]).unwrap(); +} diff --git a/nvx-rs/src/host/sandbox/tests.rs b/nvx-rs/src/host/sandbox/tests.rs new file mode 100644 index 000000000..eadf2a5d1 --- /dev/null +++ b/nvx-rs/src/host/sandbox/tests.rs @@ -0,0 +1,893 @@ +//! The launch-profile tests of `scripts/test_nvx_tools.py`, ported. + +use std::fs; + +use super::*; +use crate::ErrorKind; +use crate::host::python::fs::cwd; + +const DISTRO: &str = "distro,distro.erofs,11111111-1111-1111-1111-111111111111"; + +fn distro() -> SandboxLayer { + SandboxLayer::parse(DISTRO).unwrap() +} + +fn policy(denied: &[&str], allowed: &[&str], writable: &[&str]) -> MountPolicy { + let owned = |paths: &[&str]| paths.iter().map(|&path| path.to_owned()).collect(); + MountPolicy { + denied: owned(denied), + allowed: owned(allowed), + writable: owned(writable), + } +} + +fn mount(value: &str, denied: &[&str]) -> SandboxMount { + SandboxMount::parse( + value, + policy(denied, &[], &[]), + MountOwner::Vmm, + MountKind::Directory, + ) + .unwrap() +} + +fn with_mounts(mounts: Vec) -> Result { + SandboxLaunch::new( + vec![distro()], + "scratch.ext4", + SandboxSettings { + mounts, + ..SandboxSettings::default() + }, + ) +} + +fn message(result: Result) -> String { + result.unwrap_err().to_string() +} + +fn native(path: &str) -> String { + PurePath::native(path).as_str().to_owned() +} + +#[test] +fn launch_contract_orders_roles_and_builds_agent_command_line() { + let custom = + SandboxLayer::parse("custom,custom.erofs,22222222-2222-2222-2222-222222222222").unwrap(); + let launch = SandboxLaunch::new( + vec![custom, distro()], + "scratch.ext4", + SandboxSettings { + entrypoint: "/bin/workload".to_owned(), + args: vec!["--serve".to_owned()], + hostname: "example".to_owned(), + workload_identity: (1000, 1001), + memory_max: Some(Int::from(268_435_456_u32)), + pids_max: Some(Int::from(64_u32)), + mounts: Vec::new(), + }, + ) + .unwrap(); + let roles: Vec = launch + .ordered_layers() + .iter() + .map(|layer| layer.role()) + .collect(); + assert_eq!(roles, [LayerRole::Distro, LayerRole::Custom]); + assert_eq!( + launch.kernel_command_line("quiet").unwrap(), + "quiet nvx_sandbox=1 \ + nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 \ + nvx_layer=custom,0xd0005000,22222222-2222-2222-2222-222222222222 \ + nvx_scratch=0xd0006000,ext4 \ + nvx_entrypoint=/bin/workload nvx_hostname=example \ + nvx_arg=--serve nvx_memory_max=268435456 nvx_pids_max=65" + ); + assert_eq!( + launch.openvmm_arguments().unwrap(), + [ + "--machine", + "microvm", + "--microvm-sandbox-block", + "distro:file:distro.erofs,ro", + "--microvm-sandbox-block", + "custom:file:custom.erofs,ro", + "--microvm-sandbox-block", + "scratch:file:scratch.ext4", + "--microvm-workload-identity", + "1000:1001", + ] + ); +} + +#[test] +fn launch_contract_rejects_duplicates_and_reserved_tokens() { + let error = SandboxLaunch::new( + vec![distro(), distro()], + "scratch.ext4", + SandboxSettings::default(), + ) + .unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unsupported); + assert_eq!(error.to_string(), "duplicate sandbox layer role(s): distro"); + let launch = with_mounts(Vec::new()).unwrap(); + assert_eq!( + message(launch.kernel_command_line("nvx_sandbox=0")), + "nvx_sandbox is owned by the sandbox profile" + ); + assert_eq!( + message(launch.kernel_command_line(r"foo=bar\ nvx_memory_max=max")), + "nvx_memory_max is owned by the sandbox profile" + ); + assert_eq!( + message(launch.kernel_command_line(&"x".repeat(SANDBOX_COMMAND_LINE_MAX_SIZE))), + "sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount \ + shares or use shorter targets and arguments" + ); + assert_eq!( + message(launch.kernel_command_line("a\0b")), + "kernel command line contains an embedded NUL" + ); + assert_eq!( + message(parse_workload_identity("0:0")), + "--workload-user UID must be between 1 and 4294967295" + ); +} + +#[test] +fn launch_settings_are_checked_in_python_order() { + let settings = |change: fn(&mut SandboxSettings)| { + let mut settings = SandboxSettings::default(); + change(&mut settings); + message(SandboxLaunch::new(vec![distro()], "s.ext4", settings)) + }; + assert_eq!( + message(SandboxLaunch::new( + Vec::new(), + "s.ext4", + SandboxSettings::default() + )), + "a sandbox requires one to three read-only layers" + ); + assert_eq!( + message(SandboxLaunch::new( + vec![distro(); 4], + "s.ext4", + SandboxSettings::default() + )), + "a sandbox requires one to three read-only layers" + ); + let custom = SandboxLayer::parse("custom,c.erofs,11111111111111111111111111111111").unwrap(); + assert_eq!( + message(SandboxLaunch::new( + vec![custom.clone(), distro(), custom], + "s.ext4", + SandboxSettings::default() + )), + "duplicate sandbox layer role(s): custom" + ); + // The entrypoint is checked before the hostname. + for entrypoint in ["bin/sh", "/bin/s h", "/bin/sh\x1c"] { + let settings = SandboxSettings { + entrypoint: entrypoint.to_owned(), + hostname: "Invalid".to_owned(), + ..SandboxSettings::default() + }; + assert_eq!( + message(SandboxLaunch::new(vec![distro()], "s.ext4", settings)), + "sandbox entrypoint must be an absolute path without spaces" + ); + } + assert_eq!( + settings(|settings| settings.args = vec![String::new()]), + "sandbox arguments must be nonempty and contain no spaces" + ); + assert_eq!( + settings(|settings| settings.args = vec!["a\u{3000}b".to_owned()]), + "sandbox arguments must be nonempty and contain no spaces" + ); + for hostname in ["", "-a", "a-", "A", "a.b", "a\n"] { + let launch = SandboxLaunch::new( + vec![distro()], + "s.ext4", + SandboxSettings { + hostname: hostname.to_owned(), + ..SandboxSettings::default() + }, + ); + assert_eq!( + message(launch), + "sandbox hostname must be a lowercase RFC 1123 label up to 63 characters", + "{hostname:?}" + ); + } + assert_eq!( + settings(|settings| settings.workload_identity = (1000, 0)), + "sandbox workload identity must be a non-root UID:GID" + ); + assert_eq!( + settings(|settings| settings.memory_max = Some(Int::zero())), + "--memory-max must be positive" + ); + assert_eq!( + settings(|settings| settings.pids_max = Some(Int::from(-1_i64))), + "--pids-max must be positive" + ); +} + +#[test] +fn workload_identities_parse_as_python_int() { + assert_eq!(parse_workload_identity("1000:1001").unwrap(), (1000, 1001)); + assert_eq!( + parse_workload_identity(" +1_000 :\u{ff11}\u{ff10}").unwrap(), + (1000, 10) + ); + assert_eq!( + parse_workload_identity("4294967295:1").unwrap(), + (u32::MAX, 1) + ); + for (value, expected) in [ + ("1000", "--workload-user must be UID:GID"), + ("1:2:3", "--workload-user must be UID:GID"), + ("a:1", "--workload-user must contain numeric UID and GID"), + ("1:", "--workload-user must contain numeric UID and GID"), + ("1:0x10", "--workload-user must contain numeric UID and GID"), + ( + "4294967296:1", + "--workload-user UID must be between 1 and 4294967295", + ), + ( + "-1:1", + "--workload-user UID must be between 1 and 4294967295", + ), + ( + "1:0", + "--workload-user GID must be between 1 and 4294967295", + ), + ] { + assert_eq!( + message(parse_workload_identity(value)), + expected, + "{value:?}" + ); + } + let digits = format!("{}:1", "1".repeat(4301)); + assert_eq!( + message(parse_workload_identity(&digits)), + "--workload-user must contain numeric UID and GID" + ); +} + +#[test] +fn launch_contract_attaches_mount_and_reserves_bootstrap_tokens() { + let launch = with_mounts(vec![mount("/workspace,share,rw", &["secrets"])]).unwrap(); + let arguments = launch.openvmm_arguments().unwrap(); + let start = arguments + .iter() + .position(|argument| argument == "--mount") + .unwrap(); + assert_eq!( + arguments[start..], + ["--mount", "/workspace,share,rw", "--mount-deny", "secrets"] + ); + assert!( + !launch + .kernel_command_line("quiet") + .unwrap() + .contains("virtfs_") + ); + for (token, name) in [ + ("virtfs_dir=/other", "virtfs_dir"), + ("virtfs_tag=other", "virtfs_tag"), + ("virtfs_mode=rw", "virtfs_mode"), + ] { + assert_eq!( + message(launch.kernel_command_line(token)), + format!("{name} is owned by the sandbox --mount option") + ); + } + let base = with_mounts(Vec::new()) + .unwrap() + .kernel_command_line("") + .unwrap(); + let fragment = mount::mounts_command_line_fragment(launch.mounts()); + assert_eq!( + fragment, + " virtfs_dir=/workspace virtfs_tag=microvm virtfs_mode=rw" + ); + let fitting = "x".repeat(SANDBOX_COMMAND_LINE_MAX_SIZE - base.len() - fragment.len() - 2); + launch.kernel_command_line(&fitting).unwrap(); + assert!(message(launch.kernel_command_line(&format!("{fitting}x"))).contains("1024-byte")); +} + +#[test] +fn launch_contract_attaches_several_shares_as_an_aggregate() { + let launch = with_mounts(vec![ + mount("/workspace,work,rw", &["secrets"]), + mount("/opt/hostedtoolcache,tools,ro", &["credentials"]), + mount("/srv/cache,cache,ro", &[]), + ]) + .unwrap(); + let names: Vec = launch + .mounts() + .iter() + .enumerate() + .map(|(index, mount)| mount::aggregate_child_name(index, mount.guest_target())) + .collect(); + let arguments = launch.openvmm_arguments().unwrap(); + // Several shares are the children of one aggregate, and OpenVMM attributes each denied + // path to the child that contains it, so relative paths are joined to their share. + assert!(!arguments.iter().any(|argument| argument == "--mount")); + let start = arguments + .iter() + .position(|argument| argument == "--mount-aggregate") + .unwrap(); + let directory = cwd().unwrap(); + assert_eq!( + arguments[start..], + [ + "--mount-aggregate".to_owned(), + "/run/nvx/shares".to_owned(), + "--mount-child".to_owned(), + format!("{},work,rw", names[0]), + "--mount-child".to_owned(), + format!("{},tools,ro", names[1]), + "--mount-child".to_owned(), + format!("{},cache,ro", names[2]), + "--mount-deny".to_owned(), + directory.join("work").join("secrets").as_str().to_owned(), + "--mount-deny".to_owned(), + directory + .join("tools") + .join("credentials") + .as_str() + .to_owned(), + ] + ); + let fragment = mount::mounts_command_line_fragment(launch.mounts()); + assert_eq!( + fragment, + " virtfs_dir=/run/nvx/shares virtfs_tag=microvm virtfs_mode=rw virtfs_aggregate=1" + ); + // The guest binds each child at its target, in order. + let command_line = launch.kernel_command_line("").unwrap(); + assert!(!command_line.contains("virtfs_")); + assert!( + command_line.ends_with(&format!( + " nvx_share={},/workspace,rw nvx_share={},/opt/hostedtoolcache,ro \ + nvx_share={},/srv/cache,ro", + names[0], names[1], names[2] + )), + "{command_line}" + ); + assert_eq!( + mount::mounts_command_line_fragment(&launch.mounts()[1..]), + " virtfs_dir=/run/nvx/shares virtfs_tag=microvm virtfs_mode=ro virtfs_aggregate=1" + ); + assert_eq!( + message(launch.kernel_command_line("nvx_share=0,/etc,rw")), + "nvx_share is owned by the sandbox profile" + ); + // Every share's tokens count toward the x86 budget. + let base = with_mounts(Vec::new()) + .unwrap() + .kernel_command_line("") + .unwrap(); + let shares = command_line.len() - base.len(); + let fitting = + "x".repeat(SANDBOX_COMMAND_LINE_MAX_SIZE - base.len() - shares - fragment.len() - 2); + launch.kernel_command_line(&fitting).unwrap(); + assert!(message(launch.kernel_command_line(&format!("{fitting}x"))).contains("1024-byte")); + + let caller: Vec = [("/workspace", "work"), ("/tools", "tools")] + .iter() + .map(|(target, path)| { + SandboxMount::new( + target, + path, + MountAccess::ReadOnly, + MountPolicy::default(), + MountOwner::Caller, + MountKind::Directory, + ) + .unwrap() + }) + .collect(); + let arguments = mount::mounts_openvmm_arguments(&caller).unwrap(); + assert_eq!( + arguments + .iter() + .filter(|argument| *argument == "--mount-owner") + .count(), + 1 + ); + assert_eq!( + arguments[arguments.len() - 2..], + ["--mount-owner", "caller"] + ); +} + +#[test] +fn launch_bounds_shares_by_the_kernel_command_line() { + let launch = |count: usize| { + with_mounts( + (0..count) + .map(|index| mount(&format!("/share/{index:02},host{index}"), &[])) + .collect(), + ) + .unwrap() + }; + // No fixed number of shares is the limit, only the command line. + launch(10).kernel_command_line("").unwrap(); + assert!(message(launch(20).kernel_command_line("")).contains("fewer --mount shares")); +} + +#[test] +fn launch_rejects_invalid_share_sets() { + let owned = |value: &str, owner: MountOwner| { + SandboxMount::parse(value, MountPolicy::default(), owner, MountKind::Directory).unwrap() + }; + let vmm = |value: &str| owned(value, MountOwner::Vmm); + for (mounts, expected) in [ + ( + vec![vmm("/workspace,a,rw"), vmm("/workspace,b")], + "sandbox mount targets /workspace and /workspace overlap; a share cannot hide another", + ), + ( + vec![vmm("/workspace,a,rw"), vmm("/workspace/cache,b")], + "sandbox mount targets /workspace and /workspace/cache overlap; a share cannot hide \ + another", + ), + ( + vec![vmm("/opt/tools/node,a"), vmm("/opt,b,rw")], + "sandbox mount targets /opt/tools/node and /opt overlap; a share cannot hide another", + ), + ( + vec![ + vmm("/workspace,a,rw"), + owned("/tools,b", MountOwner::Caller), + ], + "every sandbox --mount must use the same --mount-owner", + ), + ] { + assert_eq!(message(with_mounts(mounts)), expected); + } + // A shared prefix that is not a path component is not an overlap. + with_mounts(vec![vmm("/work,a,rw"), vmm("/workspace,b")]).unwrap(); +} + +#[test] +fn launch_contract_attaches_a_file_as_an_aggregate_child() { + let file = |value: &str| { + SandboxMount::parse( + value, + MountPolicy::default(), + MountOwner::Vmm, + MountKind::File, + ) + .unwrap() + }; + let settings = file("/etc/app/settings.json,settings.json,ro"); + let launch = with_mounts(vec![settings.clone()]).unwrap(); + let name = mount::aggregate_child_name(0, "/etc/app/settings.json"); + // Even alone, a file is the child of an aggregate, because the guest mounts the device's + // root, which is a directory. + assert!(mount::mounts_use_aggregate(launch.mounts())); + let arguments = launch.openvmm_arguments().unwrap(); + assert!(!arguments.iter().any(|argument| argument == "--mount")); + let start = arguments + .iter() + .position(|argument| argument == "--mount-aggregate") + .unwrap(); + assert_eq!( + arguments[start..], + [ + "--mount-aggregate".to_owned(), + "/run/nvx/shares".to_owned(), + "--mount-child".to_owned(), + format!("{name},settings.json,ro,file"), + ] + ); + assert_eq!( + mount::mounts_command_line_fragment(launch.mounts()), + " virtfs_dir=/run/nvx/shares virtfs_tag=microvm virtfs_mode=ro virtfs_aggregate=1" + ); + assert!( + launch + .kernel_command_line("") + .unwrap() + .ends_with(&format!(" nvx_share={name},/etc/app/settings.json,ro,file")) + ); + + // Files and directories share one aggregate, and only a file's token is marked. + let workspace = mount("/workspace,work,rw", &["secrets"]); + let output = file("/results/output.txt,output.txt,rw"); + let mixed = with_mounts(vec![workspace, settings, output]).unwrap(); + let names: Vec = mixed + .mounts() + .iter() + .enumerate() + .map(|(index, mount)| mount::aggregate_child_name(index, mount.guest_target())) + .collect(); + assert!(mixed.kernel_command_line("").unwrap().ends_with(&format!( + " nvx_share={},/workspace,rw nvx_share={},/etc/app/settings.json,ro,file \ + nvx_share={},/results/output.txt,rw,file", + names[0], names[1], names[2] + ))); + let arguments = mixed.openvmm_arguments().unwrap(); + let denied = arguments + .iter() + .position(|argument| argument == "--mount-deny") + .unwrap(); + assert_eq!( + arguments[denied + 1], + cwd().unwrap().join("work").join("secrets").as_str() + ); + + // A file's mode applies to the whole file, so no policy path names one. + for policy in [ + policy(&["x"], &[], &[]), + policy(&[], &["x"], &[]), + policy(&[], &[], &["x"]), + ] { + let error = SandboxMount::new( + "/results/output.txt", + "output.txt", + MountAccess::ReadWrite, + policy, + MountOwner::Vmm, + MountKind::File, + ) + .unwrap_err(); + assert!(error.to_string().contains("shares a file"), "{error}"); + } + assert_eq!( + message(MountKind::from_name("socket")), + "unsupported sandbox mount kind 'socket'; choose directory or file" + ); + // The runtime replaces /etc/machine-id with a file, so nothing at or below it may be shared. + for target in ["/etc/machine-id", "/etc/machine-id/child"] { + let error = SandboxMount::parse( + &format!("{target},id"), + MountPolicy::default(), + MountOwner::Vmm, + MountKind::File, + ) + .unwrap_err(); + assert!(error.to_string().contains("reserved"), "{error}"); + } + // Only /etc itself is reserved, not what lies below it. + mount("/etc/app,app", &[]); +} + +/// A sandbox's distro layer and scratch image in a temporary directory. +struct Fixture { + /// The temporary directory. + directory: tempfile::TempDir, +} + +impl Fixture { + fn new() -> Self { + let directory = tempfile::tempdir().unwrap(); + fs::write(directory.path().join("distro.erofs"), b"layer").unwrap(); + fs::write(directory.path().join("scratch.ext4"), b"scratch").unwrap(); + Self { directory } + } + + /// Returns the path of `name` in the directory, or of the directory. + fn path(&self, name: &str) -> String { + let root = PurePath::native(self.directory.path().to_str().unwrap()); + if name.is_empty() { + return root.as_str().to_owned(); + } + root.join(name).as_str().to_owned() + } + + fn launch(&self, mounts: Vec) -> SandboxLaunch { + SandboxLaunch::new( + vec![SandboxLayer::new( + LayerRole::Distro, + &self.path("distro.erofs"), + "11111111-1111-1111-1111-111111111111", + )], + &self.path("scratch.ext4"), + SandboxSettings { + mounts, + ..SandboxSettings::default() + }, + ) + .unwrap() + } +} + +fn parse(value: &str, policy: MountPolicy) -> SandboxMount { + SandboxMount::parse(value, policy, MountOwner::Vmm, MountKind::Directory).unwrap() +} + +fn resolve(path: &str) -> PurePath { + crate::host::python::fs::resolve(&PurePath::native(path)).unwrap() +} + +fn path_hash(text: &str) -> u64 { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + text.hash(&mut hasher); + hasher.finish() +} + +#[test] +fn launch_validation_rejects_overlapping_or_foreign_host_paths() { + let fixture = Fixture::new(); + let (work, tools, root) = ( + fixture.path("work"), + fixture.path("tools"), + fixture.path(""), + ); + fs::create_dir_all(fixture.path("work/nested")).unwrap(); + fs::create_dir_all(fixture.path("tools/credentials")).unwrap(); + let nested = fixture.path("work/nested"); + let credentials = fixture.path("tools/credentials"); + + let workspace = parse(&format!("/workspace,{work},rw"), MountPolicy::default()); + for host_path in [&nested, &work, &root] { + let toolcache = parse( + &format!("/opt/hostedtoolcache,{host_path}"), + MountPolicy::default(), + ); + let error = fixture + .launch(vec![workspace.clone(), toolcache]) + .validated() + .unwrap_err(); + assert!(error.to_string().contains("overlap"), "{error}"); + } + + // Each denied path must be inside its own share. + let foreign = parse( + &format!("/workspace,{work},rw"), + policy(&[&credentials], &[], &[]), + ); + let toolcache = parse( + &format!("/opt/hostedtoolcache,{tools}"), + MountPolicy::default(), + ); + let error = fixture + .launch(vec![foreign, toolcache.clone()]) + .validated() + .unwrap_err(); + assert_eq!( + error.to_string(), + format!( + "--mount-deny {credentials} is not inside the host directory of --mount /workspace; \ + with several shares, each --mount-deny follows the --mount whose directory it names" + ) + ); + // A denied path may name a share's root, which OpenVMM then hides behind its allowed paths, + // but no allowed or writable path may. + fixture + .launch(vec![ + parse( + &format!("/workspace,{work},rw"), + policy(&["."], &[&nested], &[]), + ), + toolcache.clone(), + ]) + .validated() + .unwrap(); + for (allowed, writable) in [(&["."][..], &[][..]), (&[], &["."])] { + let error = fixture + .launch(vec![ + parse( + &format!("/workspace,{work},rw"), + policy(&["nested"], allowed, writable), + ), + toolcache.clone(), + ]) + .validated() + .unwrap_err(); + assert!(error.to_string().contains("not inside"), "{error}"); + } + let validated = fixture.launch(vec![ + parse( + &format!("/workspace,{work},rw"), + policy(&["nested"], &[], &[]), + ), + parse( + &format!("/opt/hostedtoolcache,{tools}"), + policy(&[&credentials], &[], &[]), + ), + ]); + validated.validated().unwrap(); + assert_eq!(validated.mounts().len(), 2); + + // Resolving a path doesn't see through a bind mount, so one directory reached through two + // paths, or a share inside a bind mount of the other share's directory, is rejected by its + // file identities. + let shares = [workspace.clone(), toolcache.clone()]; + mount::validate_mount_host_paths(&shares).unwrap(); + let same = |_: &PurePath| -> std::io::Result<(u64, u128)> { Ok((1, 1)) }; + let queries = mount::HostQueries { + identity: &same, + ..mount::HostQueries::native() + }; + let error = mount::validate_mount_host_paths_with(&shares, &queries).unwrap_err(); + assert!(error.to_string().contains("same directory"), "{error}"); + fs::create_dir_all(fixture.path("alias/cache")).unwrap(); + let (work_resolved, alias_resolved) = (resolve(&work), resolve(&fixture.path("alias"))); + let bound = |path: &PurePath| -> std::io::Result<(u64, u128)> { + // `alias` is a bind mount of the workspace's directory. + if *path == work_resolved || *path == alias_resolved { + return Ok((1, 1)); + } + Ok((0, u128::from(path_hash(path.as_str())))) + }; + let queries = mount::HostQueries { + identity: &bound, + ..mount::HostQueries::native() + }; + let cache = parse( + &format!("/opt/hostedtoolcache,{}", fixture.path("alias/cache")), + MountPolicy::default(), + ); + mount::validate_mount_host_paths_with(&shares, &queries).unwrap(); + for mounts in [ + [workspace.clone(), cache.clone()], + [cache.clone(), workspace.clone()], + ] { + let error = mount::validate_mount_host_paths_with(&mounts, &queries).unwrap_err(); + assert!(error.to_string().contains("inside the other"), "{error}"); + } +} + +#[test] +fn launch_validation_requires_plain_mount_directory() { + let fixture = Fixture::new(); + let share = fixture.path("share"); + let launch = || { + fixture.launch(vec![ + SandboxMount::new( + "/workspace", + &share, + MountAccess::ReadOnly, + MountPolicy::default(), + MountOwner::Vmm, + MountKind::Directory, + ) + .unwrap(), + ]) + }; + let expected = format!("sandbox mount host path is not a plain directory: {share}"); + assert_eq!(launch().validated().unwrap_err().to_string(), expected); + fs::write(&share, b"file").unwrap(); + assert_eq!(launch().validated().unwrap_err().to_string(), expected); + fs::remove_file(&share).unwrap(); + fs::create_dir(&share).unwrap(); + let validated = launch(); + validated.validated().unwrap(); + assert_eq!(validated.mounts()[0].host_path(), Path::new(&share)); +} + +#[test] +fn launch_validation_requires_the_kind_of_each_share() { + let fixture = Fixture::new(); + let config = fixture.path("config"); + fs::create_dir(&config).unwrap(); + let settings = fixture.path("config/settings.json"); + fs::write(&settings, b"{}").unwrap(); + assert_eq!(mount::mount_kind(Path::new(&settings)), MountKind::File); + assert_eq!(mount::mount_kind(Path::new(&config)), MountKind::Directory); + assert_eq!( + mount::mount_kind(Path::new(&fixture.path("missing"))), + MountKind::Directory + ); + let share = |target: &str, path: &str, kind: MountKind| { + SandboxMount::new( + target, + path, + MountAccess::ReadOnly, + MountPolicy::default(), + MountOwner::Vmm, + kind, + ) + .unwrap() + }; + let file = share("/etc/app/settings.json", &settings, MountKind::File); + file.validated().unwrap(); + // A directory at the file's path is not shared in its place. + fs::remove_file(&settings).unwrap(); + fs::create_dir(&settings).unwrap(); + let expected = format!("sandbox mount host path is not a regular file: {settings}"); + assert_eq!(file.validated().unwrap_err().to_string(), expected); + fs::remove_dir(&settings).unwrap(); + assert_eq!(file.validated().unwrap_err().to_string(), expected); + + // A file overlaps a shared directory that contains it, and another share of the same file, + // such as through a hard link, but not a file beside it. + fs::write(&settings, b"{}").unwrap(); + let other = fixture.path("config/other.json"); + fs::write(&other, b"{}").unwrap(); + let link = fixture.path("link.json"); + fs::hard_link(&settings, &link).unwrap(); + mount::validate_mount_host_paths(&[ + share("/a.json", &settings, MountKind::File), + share("/b.json", &other, MountKind::File), + ]) + .unwrap(); + let error = mount::validate_mount_host_paths(&[ + share("/config", &config, MountKind::Directory), + share("/a.json", &settings, MountKind::File), + ]) + .unwrap_err(); + assert!(error.to_string().contains("overlap"), "{error}"); + let error = mount::validate_mount_host_paths(&[ + share("/a.json", &settings, MountKind::File), + share("/b.json", &link, MountKind::File), + ]) + .unwrap_err(); + assert!( + error.to_string().contains("same directory or file"), + "{error}" + ); +} + +#[test] +fn launch_contract_rejects_disk_option_delimiters() { + let fixture = Fixture::new(); + let layer = fixture.path("layer;create=1G"); + fs::write(&layer, b"").unwrap(); + let launch = SandboxLaunch::new( + vec![SandboxLayer::new( + LayerRole::Distro, + &layer, + "11111111-1111-1111-1111-111111111111", + )], + &fixture.path("scratch.ext4"), + SandboxSettings::default(), + ) + .unwrap(); + assert_eq!( + launch.validated().unwrap_err().to_string(), + format!("disk paths containing commas or semicolons are unsupported: {layer}") + ); +} + +#[test] +fn launch_validation_requires_the_layers_and_the_scratch_image() { + let fixture = Fixture::new(); + let missing = SandboxLaunch::new( + vec![SandboxLayer::new( + LayerRole::Runtime, + &fixture.path("missing.erofs"), + "11111111-1111-1111-1111-111111111111", + )], + &fixture.path("scratch.ext4"), + SandboxSettings::default(), + ) + .unwrap(); + let error = missing.validated().unwrap_err(); + assert_eq!(error.kind(), ErrorKind::Unavailable); + assert_eq!( + error.to_string(), + format!( + "runtime EROFS layer not found: {}", + fixture.path("missing.erofs") + ) + ); + let scratch = fixture.launch(Vec::new()); + fs::remove_file(fixture.path("scratch.ext4")).unwrap(); + assert_eq!( + scratch.validated().unwrap_err().to_string(), + format!( + "ext4 scratch image not found: {}", + fixture.path("scratch.ext4") + ) + ); + assert_eq!( + require_file(Path::new("missing//file"), "OpenVMM release binary") + .unwrap_err() + .to_string(), + format!( + "OpenVMM release binary not found: {}", + native("missing/file") + ) + ); +} diff --git a/nvx-rs/src/host/session.rs b/nvx-rs/src/host/session.rs index 8c087c2f1..52d6ebcc0 100644 --- a/nvx-rs/src/host/session.rs +++ b/nvx-rs/src/host/session.rs @@ -331,6 +331,28 @@ impl ControlSession { Ok(()) } + /// Sends an application request of `kind` with `request_id` and `payload` in the next + /// sequenced outer record, for a caller that checks the responses itself, as the `nvx-rs` + /// CLI does with the messages of `nvx.py`. + pub fn send_request( + &mut self, + kind: u8, + request_id: u64, + payload: &[u8], + deadline: Instant, + ) -> Result<(), SessionError> { + self.send_app(kind, request_id, payload, Some(deadline)) + } + + /// Reads the next application frame, checked against the session's identity and sequence, + /// and returns its kind, request ID, status, and payload. + pub fn read_response( + &mut self, + deadline: Option, + ) -> Result<(u8, u64, i32, Vec), SessionError> { + self.read_app(deadline) + } + /// Encodes and sends one application frame in the next sequenced outer record. fn send_app( &mut self, diff --git a/nvx-rs/src/host/sys/interrupt.rs b/nvx-rs/src/host/sys/interrupt.rs new file mode 100644 index 000000000..75c254bfb --- /dev/null +++ b/nvx-rs/src/host/sys/interrupt.rs @@ -0,0 +1,46 @@ +//! Ctrl-C on Unix hosts: a `SIGINT` handler that records the interrupt, which the CLI polls. + +use std::io; +use std::mem::MaybeUninit; +use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; + +use nix::errno::Errno; +use nix::libc; +use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction}; + +/// Whether a `SIGINT` arrived since [`watch_interrupts`] installed its handler. +static INTERRUPTED: AtomicBool = AtomicBool::new(false); + +/// The `SIGINT` handler, which only records the signal. +extern "C" fn record(_signal: libc::c_int) { + INTERRUPTED.store(true, Ordering::SeqCst); +} + +/// Records each `SIGINT` for [`interrupted`] instead of letting it terminate the process, as +/// Python's handler, which raises `KeyboardInterrupt`, does. +/// +/// Like Python, it keeps a `SIGINT` that the process started with ignored: a caller that ignores +/// the signal for its children keeps it from interrupting them. +pub(crate) fn watch_interrupts() -> io::Result<()> { + let mut current = MaybeUninit::::zeroed(); + // SAFETY: a null action only reads the current disposition into `current`. + Errno::result(unsafe { libc::sigaction(libc::SIGINT, ptr::null(), current.as_mut_ptr()) })?; + // SAFETY: sigaction filled `current` in. + if unsafe { current.assume_init() }.sa_sigaction == libc::SIG_IGN { + return Ok(()); + } + let action = SigAction::new( + SigHandler::Handler(record), + SaFlags::SA_RESTART, + SigSet::empty(), + ); + // SAFETY: the handler only stores to an atomic, which is async-signal-safe. + unsafe { sigaction(Signal::SIGINT, &action) }?; + Ok(()) +} + +/// Returns whether a `SIGINT` arrived since [`watch_interrupts`]. +pub(crate) fn interrupted() -> bool { + INTERRUPTED.load(Ordering::SeqCst) +} diff --git a/nvx-rs/src/host/sys/mod.rs b/nvx-rs/src/host/sys/mod.rs index d0757e44e..21b247a58 100644 --- a/nvx-rs/src/host/sys/mod.rs +++ b/nvx-rs/src/host/sys/mod.rs @@ -4,9 +4,11 @@ //! standard library or this module, which makes its system calls through `nix` and confines the //! unsafe ones behind wrappers that establish their ownership and async-signal-safety invariants. //! `nix` has no wrapper for `pidfd_open`, `pidfd_send_signal`, `close_range`, or `getdents64`, so -//! those go through `nix`'s re-export of libc. The Windows platform module still calls -//! `windows-sys` directly. +//! those go through `nix`'s re-export of libc, as does the query of the `SIGINT` disposition that +//! precedes the CLI's handler. The Windows platform module still calls `windows-sys` directly. +#[cfg(feature = "cli")] +mod interrupt; #[cfg(target_os = "linux")] mod process; #[cfg(target_os = "linux")] @@ -16,6 +18,8 @@ mod socket; pub(crate) use nix::errno::Errno; use nix::unistd::{getegid, geteuid}; +#[cfg(feature = "cli")] +pub(crate) use interrupt::{interrupted, watch_interrupts}; #[cfg(target_os = "linux")] pub(crate) use process::{PidFd, detach}; #[cfg(all(test, target_os = "linux"))] diff --git a/nvx-rs/src/protocol/features.rs b/nvx-rs/src/protocol/features.rs index 2252b2239..cd7db3176 100644 --- a/nvx-rs/src/protocol/features.rs +++ b/nvx-rs/src/protocol/features.rs @@ -7,10 +7,10 @@ use super::{ProtocolError, array, violation}; /// Control features of a guest image, advertised in the response to /// [`APP_FEATURES`](super::APP_FEATURES). /// -/// The bits are defined by the guest agent in `guest/common/nvx-managed-agent.c`, which also -/// speaks for the features of its image's init script. A host drives a sandbox only when its -/// guest provides every feature in [`GuestFeatures::REQUIRED`]; an older image would silently -/// ignore the policy or the request that depends on the missing feature. +/// The Rust guest agent uses these shared definitions and also speaks for the features of its +/// image's init script. A host drives a sandbox only when its guest provides every feature in +/// [`GuestFeatures::REQUIRED`]; an older image would silently ignore the policy or the request +/// that depends on the missing feature. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GuestFeatures(u32); @@ -50,6 +50,17 @@ impl GuestFeatures { Self(Self::CANCEL.0 | Self::WORKLOAD_ACCOUNT.0 | Self::EXEC_ENVIRONMENT.0); /// The features that the host depends on. pub const REQUIRED: Self = Self::DIRECT_AGENT; + /// The features that `nvx-rs sandbox` depends on in the layered sandbox root, whose agent + /// announces [`SANDBOX_AGENT`](Self::SANDBOX_AGENT): only + /// [`EXEC_ENVIRONMENT`](Self::EXEC_ENVIRONMENT), because `exec` sends a working directory and + /// an environment in an extended `EXEC`. + /// + /// It holds no [`CANCEL`](Self::CANCEL), because the CLI sends no `APP_CANCEL`: as `nvx.py` + /// did, Ctrl-C ends an `exec` by closing the session, and the guest agent ends the workload + /// when OpenVMM resets the session that it ran for. It holds no + /// [`WORKLOAD_ACCOUNT`](Self::WORKLOAD_ACCOUNT), because no sandbox launch requests account + /// creation, which the layered root does not perform (microsoft/nvx#566). + pub const LAYERED_SANDBOX: Self = Self::EXEC_ENVIRONMENT; /// Human-readable names of every required feature, used in compatibility errors. pub(crate) const NAMES: [(Self, &'static str); 6] = [ diff --git a/nvx-rs/src/protocol/mod.rs b/nvx-rs/src/protocol/mod.rs index 776c66075..a003e866d 100644 --- a/nvx-rs/src/protocol/mod.rs +++ b/nvx-rs/src/protocol/mod.rs @@ -1,8 +1,8 @@ //! Codec of the OpenVMM control-console protocol. //! //! OpenVMM brokers an authenticated stream of outer records (magic `NVXS`) between one host -//! client and the guest agent. Data records carry application frames (magic `NVXC`) that belong -//! to the guest agent in `guest/common/nvx-managed-agent.c`. All integers are little-endian. +//! client and the Rust guest agent. Data records carry application frames (magic `NVXC`) that +//! the host and guest share from this module. All integers are little-endian. //! //! - Outer records: [`encode_outer`] and [`decode_outer_header`], and the `OUTER_*` record types. //! - Application frames: [`encode_app`] and [`decode_app`], the `APP_*` request and response @@ -17,8 +17,7 @@ //! such as [`MAPS_TOKEN`]. //! //! The module depends on nothing outside the standard library, so the host, the guest, and test -//! doubles share it. Its wire constants are those of the guest agent, which tests compare with -//! the agent's source, and golden byte fixtures pin every record that both sides exchange. +//! doubles share it. Tests pin its wire constants as literals and its records as golden bytes. use std::fmt; diff --git a/nvx-rs/src/protocol/tests.rs b/nvx-rs/src/protocol/tests.rs index 506e3799b..2fb788e05 100644 --- a/nvx-rs/src/protocol/tests.rs +++ b/nvx-rs/src/protocol/tests.rs @@ -854,136 +854,81 @@ fn maps_decoding_refuses_what_the_guest_agent_refuses() { } } -fn guest_agent_source() -> Option { - let source = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .parent()? - .join("guest") - .join("common") - .join("nvx-managed-agent.c"); - std::fs::read_to_string(source).ok() +// Hosts and guests share the wire constants, so changing one passes every round trip between +// them yet breaks compatibility with released peers. These tests pin each value literally. + +#[test] +fn record_definitions_keep_their_wire_values() { + assert_eq!(OUTER_HEADER_LEN, 44); + assert_eq!(OUTER_MAX_PAYLOAD, 65_536); + assert_eq!(OUTER_VERSION, 1); + assert_eq!(OUTER_GUEST_ATTACH, 1); + assert_eq!(OUTER_RESET, 3); + assert_eq!(OUTER_ACK, 4); + assert_eq!(OUTER_DATA, 5); + assert_eq!(OUTER_CREDIT, 9); + assert_eq!(OUTER_RECEIVE_WINDOW, 1024 * 1024); + assert_eq!(APP_HEADER_LEN, 24); + assert_eq!(APP_VERSION, 1); + assert_eq!(APP_PING, 1); + assert_eq!(APP_EXEC, 2); + assert_eq!(APP_STOP, 3); + assert_eq!(APP_CANCEL, 4); + assert_eq!(APP_READY, 0x81); + assert_eq!(APP_STDOUT, 0x82); + assert_eq!(APP_STDERR, 0x83); + assert_eq!(APP_EXIT, 0x84); + assert_eq!(APP_STOPPED, 0x85); + assert_eq!(APP_ERROR, 0xff); } #[test] -fn feature_definitions_match_the_guest_agent_source() { - let Some(source) = guest_agent_source() else { - return; - }; - let mut definitions = vec![ - format!("#define APP_FEATURES {APP_FEATURES}U"), - format!("#define APP_MAPS {APP_MAPS}U"), - ]; - for (name, feature) in [ - ("CANCEL", GuestFeatures::CANCEL), - ("WORKLOAD_ACCOUNT", GuestFeatures::WORKLOAD_ACCOUNT), - ("EXEC_CGROUP", GuestFeatures::EXEC_CGROUP), - ("EXEC_ENVIRONMENT", GuestFeatures::EXEC_ENVIRONMENT), - ("EXEC_CWD", GuestFeatures::EXEC_CWD), - ("HOST_MAPPING_TABLE", GuestFeatures::HOST_MAPPING_TABLE), - ] { - let bit = feature.bits().trailing_zeros(); - definitions.push(format!("#define FEATURE_{name} (1U << {bit})")); - } - for definition in definitions { - assert!( - source.contains(&definition), - "the guest agent does not define `{definition}`" - ); - } - // The retired bit stays unused, so hosts that need it refuse the image. - assert!(!source.contains("(1U << 1)")); +fn feature_definitions_keep_their_wire_values() { + assert_eq!(APP_FEATURES, 5); + assert_eq!(APP_MAPS, 6); + assert_eq!(GuestFeatures::CANCEL.bits(), 0x01); + // Bit 1 is retired and stays unused, so hosts that still need it refuse the image. + assert_eq!(GuestFeatures::WORKLOAD_ACCOUNT.bits(), 0x04); + assert_eq!(GuestFeatures::EXEC_CGROUP.bits(), 0x08); + assert_eq!(GuestFeatures::EXEC_ENVIRONMENT.bits(), 0x10); + assert_eq!(GuestFeatures::EXEC_CWD.bits(), 0x20); + assert_eq!(GuestFeatures::HOST_MAPPING_TABLE.bits(), 0x40); } #[test] -fn host_mapping_table_matches_the_guest_agent_source() { - let Some(source) = guest_agent_source() else { - return; - }; - for expected in [ - format!("#define MAX_HOST_MAPPINGS {MAX_HOST_MAPPINGS}U"), - format!("#define MAX_GUEST_PATH {}U", MAX_MAP_PATH_BYTES + 1), - format!("#define MAPS_HEADER_LEN {MAPS_HEADER_LEN}U"), - format!("#define MAP_ENTRY_HEADER_LEN {MAP_ENTRY_HEADER_LEN}U"), - format!("#define MAP_READ_ONLY {MAP_READ_ONLY}U"), - format!("#define MAPS_TOKEN \"{MAPS_TOKEN}\""), - format!("\"{MAPPINGS_INCOMPLETE}\""), - format!("\"{MAPPING_FAILED}\""), - ] { - assert!( - source.contains(&expected), - "the guest agent does not contain `{expected}`" - ); - } +fn host_mapping_table_keeps_its_wire_values() { + assert_eq!(MAX_HOST_MAPPINGS, 4096); + assert_eq!(MAX_MAP_PATH_BYTES, 4095); + assert_eq!(MAPS_HEADER_LEN, 8); + assert_eq!(MAP_ENTRY_HEADER_LEN, 6); + assert_eq!(MAP_READ_ONLY, 1); + assert_eq!(MAPS_TOKEN, "nvx_maps="); + assert_eq!(MAPPINGS_INCOMPLETE, "mappings-incomplete"); + assert_eq!(MAPPING_FAILED, "mapping-failed"); } #[test] -fn exec_extension_matches_the_guest_agent_source() { - // The agent accepts a working directory as long as one argument, more than any directory - // that a workload can enter. - const { assert!(MAX_CWD_BYTES < MAX_ARGUMENT_BYTES) }; - let Some(source) = guest_agent_source() else { - return; - }; - for expected in [ - format!("#define EXEC_EXTENDED {EXEC_EXTENDED}U"), - format!("#define EXEC_CWD_PRESENT {EXEC_CWD_PRESENT}U"), - format!("#define EXEC_ENVIRONMENT_PRESENT {EXEC_ENVIRONMENT_PRESENT}U"), - format!("#define EXEC_INHERIT_DEFAULT_ENV {EXEC_INHERIT_DEFAULT_ENV}U"), - format!("#define MAX_ARGUMENTS {MAX_ARGUMENTS}U"), - format!("#define MAX_ARGUMENT_LEN {MAX_ARGUMENT_BYTES}U"), - format!("#define MAX_ENVIRONMENT {MAX_ENVIRONMENT}U"), - format!( - "#define MAX_OUTPUT_BYTES ({}U * 1024U)", - MAX_OUTPUT_BYTES / 1024 - ), - format!("\"{CWD_FAILED}\""), - format!("\"{LAUNCH_FAILED}\""), - format!("\"{INVALID_REQUEST}\""), - format!("\"{BUSY}\""), - format!("\"{}\"", String::from_utf8_lossy(UNSUPPORTED_OPERATION)), - ] { - assert!( - source.contains(&expected), - "the guest agent does not contain `{expected}`" - ); - } +fn exec_extension_keeps_its_wire_values() { + assert_eq!(EXEC_EXTENDED, 1); + assert_eq!(EXEC_CWD_PRESENT, 1); + assert_eq!(EXEC_ENVIRONMENT_PRESENT, 2); + assert_eq!(EXEC_INHERIT_DEFAULT_ENV, 4); + assert_eq!(MAX_ARGUMENTS, 64); + assert_eq!(MAX_ARGUMENT_BYTES, 4096); + assert_eq!(MAX_ENVIRONMENT, 256); + assert_eq!(MAX_OUTPUT_BYTES, 1024 * 1024); + assert_eq!(CWD_FAILED, "cwd-failed"); + assert_eq!(LAUNCH_FAILED, "launch-failed"); + assert_eq!(INVALID_REQUEST, "invalid-request"); + assert_eq!(BUSY, "busy"); + assert_eq!(UNSUPPORTED_OPERATION, b"unsupported-operation"); } #[test] -fn record_definitions_match_the_guest_agent_source() { - let Some(source) = guest_agent_source() else { - return; - }; - for expected in [ - format!("#define OUTER_HEADER_LEN {OUTER_HEADER_LEN}U"), - format!("#define OUTER_MAX_PAYLOAD {OUTER_MAX_PAYLOAD}U"), - format!("#define OUTER_VERSION {OUTER_VERSION}U"), - format!("#define OUTER_GUEST_ATTACH {OUTER_GUEST_ATTACH}U"), - format!("#define OUTER_RESET {OUTER_RESET}U"), - format!("#define OUTER_ACK {OUTER_ACK}U"), - format!("#define OUTER_DATA {OUTER_DATA}U"), - format!("#define OUTER_CREDIT {OUTER_CREDIT}U"), - format!( - "#define OUTER_RECEIVE_WINDOW ({}U * 1024U)", - OUTER_RECEIVE_WINDOW / 1024 - ), - format!("#define APP_HEADER_LEN {APP_HEADER_LEN}U"), - format!("#define APP_VERSION {APP_VERSION}U"), - format!("#define APP_PING {APP_PING}U"), - format!("#define APP_EXEC {APP_EXEC}U"), - format!("#define APP_STOP {APP_STOP}U"), - format!("#define APP_CANCEL {APP_CANCEL}U"), - format!("#define APP_READY {APP_READY:#x}U"), - format!("#define APP_STDOUT {APP_STDOUT:#x}U"), - format!("#define APP_STDERR {APP_STDERR:#x}U"), - format!("#define APP_EXIT {APP_EXIT:#x}U"), - format!("#define APP_STOPPED {APP_STOPPED:#x}U"), - format!("#define APP_ERROR {APP_ERROR:#x}U"), - ] { - assert!( - source.contains(&expected), - "the guest agent does not contain `{expected}`" - ); - } +fn exec_limits_support_linux_paths() { + // A working directory may be as long as one argument, and Linux cannot + // enter a directory longer than that argument limit. + const { assert!(MAX_CWD_BYTES < MAX_ARGUMENT_BYTES) }; } #[test] @@ -1121,6 +1066,24 @@ fn agent_feature_sets_match_the_c_modes() { ); } +#[test] +fn layered_sandboxes_need_only_per_execution_environments() { + // The CLI's exec sends working directories and environments; it never cancels a workload or + // asks for a workload account (microsoft/nvx#566). + assert_eq!(GuestFeatures::LAYERED_SANDBOX.bits(), 1 << 4); + assert!(GuestFeatures::SANDBOX_AGENT.contains(GuestFeatures::LAYERED_SANDBOX)); + assert!( + GuestFeatures::SANDBOX_AGENT + .missing(GuestFeatures::LAYERED_SANDBOX) + .is_empty() + ); + assert_eq!( + (GuestFeatures::CANCEL | GuestFeatures::WORKLOAD_ACCOUNT) + .missing(GuestFeatures::LAYERED_SANDBOX), + ["per-execution environments"] + ); +} + #[test] fn host_exec_encoding_round_trips_through_the_guest_decoder() { let environment = strings(&["FOO=a b", "EMPTY="]); diff --git a/nvx-rs/src/testing/fake_openvmm.rs b/nvx-rs/src/testing/fake_openvmm.rs index 58affaf43..0e92db20a 100644 --- a/nvx-rs/src/testing/fake_openvmm.rs +++ b/nvx-rs/src/testing/fake_openvmm.rs @@ -47,6 +47,22 @@ //! predates it, and `fake_refuse_maps=1` fails every `MAPS` request like a guest that cannot //! mount a mapped path. The command line is recorded in `fake-openvmm-.json` next to the //! kernel. +//! +//! The fake also serves the sandbox profile of `nvx.py sandbox` and `nvx-rs sandbox`, which a +//! `--microvm-sandbox-block` option selects: read-only `distro`, `runtime`, and `custom` layers +//! and a `scratch` disk, each an existing file, with `nvx_sandbox=1`, `nvx_entrypoint=`, and +//! `nvx_hostname=` on the kernel command line, and shares through one `--mount` or the children +//! of an aggregate at `/run/nvx/shares`. Its guest advertises the features of the sandbox agent, +//! [`GuestFeatures::SANDBOX_AGENT`], and the guest targets of its directory shares are +//! directories that a workload can enter. With `--microvm-lifecycle managed`, it serves the +//! control console as above. With `--microvm-lifecycle one-shot`, it reads no capability and +//! serves no console: it runs the entrypoint and arguments of the kernel command line with its +//! own standard streams, `/bin/echo` printing its arguments, `/bin/sleep` sleeping for its +//! argument in seconds, `/bin/true` and `/bin/false` exiting with 0 and 1, and any other program +//! not found with 127, then writes an outcome report +//! of operation `run` if `--microvm-report` asks for one, and exits with the workload's status, +//! or with `STATUS` if the kernel command line has `fake_exit_status=STATUS`. Sandbox outcome +//! reports take the form of OpenVMM's. use std::collections::{BTreeMap, BTreeSet}; use std::fs::{self, OpenOptions}; @@ -139,14 +155,29 @@ fn atoi(value: &str) -> i32 { } } +/// The lifecycle that the command line selects. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Lifecycle { + /// `--microvm-lifecycle managed`: the guest serves the control console until a stop. + Managed, + /// `--microvm-lifecycle one-shot`: the guest runs its entrypoint once, and OpenVMM exits with + /// its status. Only the sandbox profile uses it. + OneShot, +} + /// Parsed OpenVMM-compatible command line and fake-specific test controls. struct Options { /// Name of the executable, which prefixes the fake's diagnostics. program: String, - /// Control endpoint at which the fake accepts host sessions. + /// The lifecycle. + lifecycle: Lifecycle, + /// Whether the command line launches the sandbox profile of `nvx.py sandbox`: a container + /// over EROFS layers and an ext4 scratch disk. + sandbox: bool, + /// Control endpoint at which the fake accepts host sessions; empty for a one-shot run. endpoint: String, - /// Path to the synthetic OpenVMM outcome report. - report: PathBuf, + /// Path to the synthetic OpenVMM outcome report, which a one-shot run may omit. + report: Option, /// Backend name recorded in the synthetic report. hypervisor: String, /// Path that receives the parsed command-line dump. @@ -155,6 +186,8 @@ struct Options { maps_dump: PathBuf, /// Status with which to stop immediately after launch, when requested. exit_on_start: Option, + /// Status with which a one-shot workload ends instead of its own, when requested. + exit_status: Option, /// Artificial delay before the fake guest becomes ready. boot_delay: Duration, /// Artificial delay before the fake OpenVMM crashes, when requested. @@ -175,6 +208,28 @@ struct Options { children: Vec, /// Number of host mappings that the kernel command line announces. maps: usize, + /// The guest directories at which a sandbox's directory shares appear. + share_targets: Vec, + /// The entrypoint and arguments of a sandbox, from its kernel command line. + workload: Vec, + /// The network policy of the command line, as OpenVMM summarizes it in its report. + network: NetworkSummary, +} + +/// The network policy of a command line, as the `network_policy` section of OpenVMM's outcome +/// report summarizes it. +#[derive(Debug, Clone, Default)] +struct NetworkSummary { + /// Whether any network option was given. + requested: bool, + /// `rules`, `deny-all`, `allow-all`, or `none`. + mode: &'static str, + /// The number of `--network-egress-allow` rules. + allow: usize, + /// The number of `--network-egress-deny` rules. + deny: usize, + /// `allow`, `deny`, or `none`. + host_loopback: &'static str, } /// A host path mapped into the guest. @@ -210,6 +265,12 @@ pub fn main() -> ExitCode { eprintln!("{program}: failing the launch as requested"); return ExitCode::from(code); } + if options.lifecycle == Lifecycle::OneShot { + let status = run_one_shot(&options); + let _ = io::stdout().flush(); + let _ = io::stderr().flush(); + std::process::exit(status); + } let capability = match read_capability() { Ok(capability) => capability, Err(message) => { @@ -233,6 +294,51 @@ pub fn main() -> ExitCode { } } +/// Runs the entrypoint of a one-shot sandbox with its output on the fake's own, as OpenVMM runs +/// the guest on its console, writes the outcome report, and returns the status with which +/// OpenVMM exits: the workload's. +/// +/// The entrypoint is `/bin/echo`, which prints its arguments, `/bin/sleep`, which sleeps for its +/// argument in seconds, `/bin/true` or `/bin/false`, or any other program, which is not found; +/// the `fake_exit_status=STATUS` token replaces the status. +fn run_one_shot(options: &Options) -> i32 { + let (program, arguments) = options + .workload + .split_first() + .map_or(("", &[][..]), |(program, arguments)| { + (program.as_str(), arguments) + }); + let status = match program { + "/bin/echo" => { + println!("{}", arguments.join(" ")); + 0 + } + "/bin/sleep" => { + let seconds = arguments + .first() + .and_then(|value| value.parse::().ok()) + .filter(|seconds| seconds.is_finite()) + .unwrap_or(0.0); + thread::sleep(Duration::from_secs_f64(seconds.clamp(0.0, 3600.0))); + 0 + } + "/bin/true" => 0, + "/bin/false" => 1, + _ => { + eprintln!("{}: {program}: not found", options.program); + 127 + } + }; + let status = options.exit_status.unwrap_or(status); + let (category, status_code) = if status == 0 { + ("success", 0) + } else { + ("guest-exit", status) + }; + write_report(options, "run", category, status_code); + status +} + /// Returns the name of the running executable, such as `nvx-fake-openvmm`. fn program_name() -> String { std::env::args_os() @@ -247,17 +353,20 @@ fn program_name() -> String { /// Parses the OpenVMM command line plus fake controls, rejecting unsupported combinations. fn parse(program: &str, arguments: &[String]) -> Result { - const VALUED: [&str; 24] = [ + const VALUED: [&str; 28] = [ "--mount-aggregate", "--mount-child", "--mount-deny", "--mount-allow", "--mount-write", + "--mount", + "--mount-owner", "--network-egress-allow", "--network-egress-deny", "--machine", "--microvm-workload-identity", "--microvm-lifecycle", + "--microvm-sandbox-block", "--hypervisor", "--memory", "--kernel", @@ -270,6 +379,7 @@ fn parse(program: &str, arguments: &[String]) -> Result { "--network-profile", "--network-egress", "--network-ingress", + "--network-proxy", "--host-loopback", "--host-loopback-forward", ]; @@ -304,13 +414,46 @@ fn parse(program: &str, arguments: &[String]) -> Result { } }; expect("--machine", "microvm")?; - expect("--microvm-lifecycle", "managed")?; - expect("--virtio-console", "none")?; - for flag in ["--single-process", "--microvm-control-auth-stdin"] { - if !flags.contains(flag) { - return Err(format!("{flag} is required")); - } + // The sandbox profile of `nvx.py sandbox` puts its layers and scratch disk on sandbox block + // devices, and may run one-shot. + let sandbox = values.contains_key("--microvm-sandbox-block"); + let lifecycle = match single("--microvm-lifecycle")? { + "managed" => Lifecycle::Managed, + "one-shot" if sandbox => Lifecycle::OneShot, + other => return Err(format!("unsupported lifecycle {other}")), + }; + if !flags.contains("--single-process") { + return Err("--single-process is required".to_owned()); } + let (endpoint, report) = match lifecycle { + Lifecycle::Managed => { + expect("--virtio-console", "none")?; + if !flags.contains("--microvm-control-auth-stdin") { + return Err("--microvm-control-auth-stdin is required".to_owned()); + } + let endpoint = single("--microvm-control-console")? + .strip_prefix("listen=") + .ok_or("--microvm-control-console must use listen=")? + .to_owned(); + (endpoint, Some(PathBuf::from(single("--microvm-report")?))) + } + Lifecycle::OneShot => { + for option in ["--virtio-console", "--microvm-control-console"] { + if values.contains_key(option) { + return Err(format!("a one-shot run takes no {option}")); + } + } + if flags.contains("--microvm-control-auth-stdin") { + return Err("a one-shot run takes no --microvm-control-auth-stdin".to_owned()); + } + let report = match values.get("--microvm-report").map(Vec::as_slice) { + None => None, + Some([report]) => Some(PathBuf::from(report)), + Some(_) => return Err("--microvm-report must be given at most once".to_owned()), + }; + (String::new(), report) + } + }; let hypervisor = single("--hypervisor")?; if !["kvm", "mshv", "whp"].contains(&hypervisor) { return Err(format!("unsupported hypervisor {hypervisor}")); @@ -331,78 +474,74 @@ fn parse(program: &str, arguments: &[String]) -> Result { return Err(format!("{} does not exist", path.display())); } } - let endpoint = single("--microvm-control-console")? - .strip_prefix("listen=") - .ok_or("--microvm-control-console must use listen=")? - .to_owned(); - let report = PathBuf::from(single("--microvm-report")?); - if report.exists() { + if let Some(report) = &report + && report.exists() + { return Err(format!("report path {} already exists", report.display())); } let command_line = single("--cmdline")?; let tokens: Vec<&str> = command_line.split_whitespace().collect(); - if tokens.iter().any(|token| { - token.starts_with("nvx_") - && !token.starts_with(MAPS_TOKEN) - && *token != WORKLOAD_ACCOUNT_TOKEN - }) { - return Err("the kernel command line must not select a sandbox".to_owned()); - } - let maps = match tokens - .iter() - .filter_map(|token| token.strip_prefix(MAPS_TOKEN)) - .collect::>() - .as_slice() - { - [] => 0, - [count] => count - .parse::() - .ok() - .filter(|count| (1..=MAX_HOST_MAPPINGS).contains(count)) - .ok_or_else(|| format!("invalid {MAPS_TOKEN}{count}"))?, - _ => return Err(format!("{MAPS_TOKEN} must be given at most once")), + let token_values = |name: &str| -> Vec<&str> { + tokens + .iter() + .filter_map(|token| token.strip_prefix(name)) + .collect() }; - let children = match values.get("--mount-aggregate").map(Vec::as_slice) { - None if maps == 0 => Vec::new(), - Some([GUEST_EXPORT]) if maps > 0 => { - let mut children = Vec::new(); - for (index, child) in values - .get("--mount-child") - .into_iter() - .flatten() - .enumerate() - { - let invalid = || format!("invalid --mount-child {child}"); - let (name, rest) = child.split_once(',').ok_or_else(invalid)?; - // Like OpenVMM, a child exposes a directory or, with the `file` flag, a regular - // file, and refuses a host path of the other kind. - let (rest, file) = match rest.strip_suffix(",file") { - Some(rest) => (rest, true), - None => (rest, false), - }; - let (host, mode) = rest.rsplit_once(',').ok_or_else(invalid)?; - let kind_matches = if file { - Path::new(host).is_file() - } else { - Path::new(host).is_dir() - }; - if name != index.to_string() || !kind_matches || !["ro", "rw"].contains(&mode) { - return Err(invalid()); - } - children.push(PathBuf::from(host)); + let (maps, children, share_targets, workload) = if sandbox { + if !tokens.contains(&"nvx_sandbox=1") { + return Err("a sandbox block device requires nvx_sandbox=1".to_owned()); + } + check_sandbox_blocks(values.get("--microvm-sandbox-block").into_iter().flatten())?; + let mut workload: Vec = match token_values("nvx_entrypoint=").as_slice() { + [entrypoint] => vec![(*entrypoint).to_owned()], + _ => return Err("the kernel command line must set one nvx_entrypoint".to_owned()), + }; + workload.extend(token_values("nvx_arg=").into_iter().map(str::to_owned)); + if token_values("nvx_hostname=").len() != 1 { + return Err("the kernel command line must set one nvx_hostname".to_owned()); + } + if !token_values(MAPS_TOKEN).is_empty() { + return Err(format!("a sandbox takes no {MAPS_TOKEN}")); + } + let (children, mut share_targets) = sandbox_shares(&values)?; + // The guest binds each directory child of an aggregate at the target of its + // `nvx_share=NAME,TARGET,ACCESS[,file]` token. + for share in token_values("nvx_share=") { + match share.split(',').collect::>().as_slice() { + [_, target, _] => share_targets.push((*target).to_owned()), + [_, _, _, "file"] => {} + _ => return Err(format!("invalid nvx_share={share}")), } - if children.is_empty() { - return Err("--mount-aggregate requires --mount-child".to_owned()); + } + (0, children, share_targets, workload) + } else { + for option in ["--mount", "--mount-owner"] { + if values.contains_key(option) { + return Err(format!("unsupported argument {option}")); } - children } - _ => { - return Err(format!("--mount-aggregate and {MAPS_TOKEN} must agree")); + if tokens.iter().any(|token| { + token.starts_with("nvx_") + && !token.starts_with(MAPS_TOKEN) + && *token != WORKLOAD_ACCOUNT_TOKEN + }) { + return Err("the kernel command line must not select a sandbox".to_owned()); + } + if !tokens.iter().any(|token| token.starts_with("hostname=")) { + return Err("the kernel command line must set the hostname".to_owned()); } + let maps = match token_values(MAPS_TOKEN).as_slice() { + [] => 0, + [count] => count + .parse::() + .ok() + .filter(|count| (1..=MAX_HOST_MAPPINGS).contains(count)) + .ok_or_else(|| format!("invalid {MAPS_TOKEN}{count}"))?, + _ => return Err(format!("{MAPS_TOKEN} must be given at most once")), + }; + let children = mapped_children(&values, maps)?; + (maps, children, Vec::new(), Vec::new()) }; - if children.is_empty() && values.contains_key("--mount-child") { - return Err("--mount-child requires --mount-aggregate".to_owned()); - } for option in ["--mount-deny", "--mount-allow", "--mount-write"] { for path in values.get(option).into_iter().flatten() { let path = Path::new(path); @@ -427,9 +566,6 @@ fn parse(program: &str, arguments: &[String]) -> Result { { return Err("--host-loopback-forward requires explicit --host-loopback allow".to_owned()); } - if !tokens.iter().any(|token| token.starts_with("hostname=")) { - return Err("the kernel command line must set the hostname".to_owned()); - } if values.contains_key("--net") { expect("--network-profile", "portable")?; } @@ -444,18 +580,27 @@ fn parse(program: &str, arguments: &[String]) -> Result { .map(Duration::from_millis) }; let token = report - .parent() + .as_ref() + .and_then(|report| report.parent()) .and_then(Path::file_name) .map(|name| name.to_string_lossy().into_owned()) .unwrap_or_default(); + let default_features = if sandbox { + GuestFeatures::SANDBOX_AGENT + } else { + GUEST_FEATURES + }; Ok(Options { program: program.to_owned(), + lifecycle, + sandbox, endpoint, args_dump: kernel.with_file_name(format!("fake-openvmm-{token}.json")), maps_dump: kernel.with_file_name(format!("fake-openvmm-{token}-maps.json")), report, hypervisor: hypervisor.to_owned(), exit_on_start: knob("fake_exit_on_start").and_then(|value| value.parse().ok()), + exit_status: knob("fake_exit_status").and_then(|value| value.parse().ok()), boot_delay: millis("fake_boot_delay_ms").unwrap_or_default(), crash_after: millis("fake_crash_after_ms"), ignore_stop: knob("fake_ignore_stop") == Some("1"), @@ -465,12 +610,186 @@ fn parse(program: &str, arguments: &[String]) -> Result { workload_uid, guest_features: knob("fake_guest_features") .and_then(|value| value.parse().ok()) - .map_or(GUEST_FEATURES, GuestFeatures::from_bits), + .map_or(default_features, GuestFeatures::from_bits), children, maps, + share_targets, + workload, + network: network_summary(&values), }) } +/// Checks the sandbox block devices of a sandbox: each layer, `ROLE:file:PATH,ro` with a role of +/// `distro`, `runtime`, or `custom`, at most once each, and one scratch disk, `scratch:file:PATH`, +/// each an existing regular file. +fn check_sandbox_blocks<'a>(blocks: impl Iterator) -> Result<(), String> { + let mut roles = BTreeSet::new(); + for block in blocks { + let invalid = || format!("invalid --microvm-sandbox-block {block}"); + let (role, rest) = block.split_once(':').ok_or_else(invalid)?; + let path = rest.strip_prefix("file:").ok_or_else(invalid)?; + let path = match role { + "distro" | "runtime" | "custom" => path.strip_suffix(",ro").ok_or_else(invalid)?, + "scratch" => path, + _ => return Err(invalid()), + }; + if !roles.insert(role) || !Path::new(path).is_file() { + return Err(invalid()); + } + } + if !roles.contains("scratch") || roles.len() < 2 { + return Err("a sandbox needs a layer and a scratch disk".to_owned()); + } + Ok(()) +} + +/// Returns the host directories of a sandbox's shares, for the checks of its policy paths, and +/// the guest targets of its directory shares: one `--mount TARGET,HOST,MODE` of a directory, or +/// the `--mount-child NAME,HOST,MODE[,file]` children of an aggregate at `/run/nvx/shares`. +fn sandbox_shares( + values: &BTreeMap<&str, Vec<&str>>, +) -> Result<(Vec, Vec), String> { + let mut roots = Vec::new(); + let mut targets = Vec::new(); + for share in values.get("--mount").into_iter().flatten() { + let invalid = || format!("invalid --mount {share}"); + let (target, rest) = share.split_once(',').ok_or_else(invalid)?; + let (host, mode) = rest.rsplit_once(',').ok_or_else(invalid)?; + if !Path::new(host).is_dir() || !["ro", "rw"].contains(&mode) { + return Err(invalid()); + } + roots.push(PathBuf::from(host)); + targets.push(target.to_owned()); + } + match values.get("--mount-aggregate").map(Vec::as_slice) { + None => {} + Some(["/run/nvx/shares"]) if roots.is_empty() => { + for child in values.get("--mount-child").into_iter().flatten() { + let invalid = || format!("invalid --mount-child {child}"); + let (_, rest) = child.split_once(',').ok_or_else(invalid)?; + let (rest, file) = match rest.strip_suffix(",file") { + Some(rest) => (rest, true), + None => (rest, false), + }; + let (host, mode) = rest.rsplit_once(',').ok_or_else(invalid)?; + let kind_matches = if file { + Path::new(host).is_file() + } else { + Path::new(host).is_dir() + }; + if !kind_matches || !["ro", "rw"].contains(&mode) { + return Err(invalid()); + } + roots.push(PathBuf::from(host)); + } + } + Some(_) => return Err("invalid --mount-aggregate".to_owned()), + } + if values.contains_key("--mount-child") && !values.contains_key("--mount-aggregate") { + return Err("--mount-child requires --mount-aggregate".to_owned()); + } + Ok((roots, targets)) +} + +/// Returns the host directories that the aggregate export of the host mapping table shares, in +/// child order, which `--mount-aggregate` and the `nvx_maps=` count of the kernel command line +/// must agree on. +fn mapped_children( + values: &BTreeMap<&str, Vec<&str>>, + maps: usize, +) -> Result, String> { + let children = match values.get("--mount-aggregate").map(Vec::as_slice) { + None if maps == 0 => Vec::new(), + Some([GUEST_EXPORT]) if maps > 0 => { + let mut children = Vec::new(); + for (index, child) in values + .get("--mount-child") + .into_iter() + .flatten() + .enumerate() + { + let invalid = || format!("invalid --mount-child {child}"); + let (name, rest) = child.split_once(',').ok_or_else(invalid)?; + // Like OpenVMM, a child exposes a directory or, with the `file` flag, a regular + // file, and refuses a host path of the other kind. + let (rest, file) = match rest.strip_suffix(",file") { + Some(rest) => (rest, true), + None => (rest, false), + }; + let (host, mode) = rest.rsplit_once(',').ok_or_else(invalid)?; + let kind_matches = if file { + Path::new(host).is_file() + } else { + Path::new(host).is_dir() + }; + if name != index.to_string() || !kind_matches || !["ro", "rw"].contains(&mode) { + return Err(invalid()); + } + children.push(PathBuf::from(host)); + } + if children.is_empty() { + return Err("--mount-aggregate requires --mount-child".to_owned()); + } + children + } + _ => { + return Err(format!("--mount-aggregate and {MAPS_TOKEN} must agree")); + } + }; + if children.is_empty() && values.contains_key("--mount-child") { + return Err("--mount-child requires --mount-aggregate".to_owned()); + } + Ok(children) +} + +/// Summarizes the network policy of a command line as OpenVMM's outcome report does. +fn network_summary(values: &BTreeMap<&str, Vec<&str>>) -> NetworkSummary { + let count = |name: &str| values.get(name).map_or(0, Vec::len); + let requested = [ + "--net", + "--network-profile", + "--network-egress", + "--network-ingress", + "--network-egress-allow", + "--network-egress-deny", + "--host-loopback", + "--network-proxy", + "--host-loopback-forward", + ] + .iter() + .any(|name| values.contains_key(name)); + let egress = values + .get("--network-egress") + .and_then(|values| values.last()); + let mode = if count("--network-egress-allow") + count("--network-egress-deny") > 0 { + "rules" + } else if egress == Some(&"deny") { + "deny-all" + } else if requested { + "allow-all" + } else { + "none" + }; + let host_loopback = if !requested { + "none" + } else if values + .get("--host-loopback") + .and_then(|values| values.last()) + == Some(&"deny") + { + "deny" + } else { + "allow" + }; + NetworkSummary { + requested, + mode, + allow: count("--network-egress-allow"), + deny: count("--network-egress-deny"), + host_loopback, + } +} + /// Reads exactly one nonzero control capability from standard input. fn read_capability() -> Result<[u8; CAPABILITY_LEN], String> { let mut stdin = io::stdin().lock(); @@ -525,7 +844,11 @@ impl Guest { Self { values: BTreeMap::new(), default_environment, - directories: ["/", "/tmp", "/work"].map(str::to_owned).into(), + directories: ["/", "/tmp", "/work"] + .into_iter() + .map(str::to_owned) + .chain(options.share_targets.iter().cloned()) + .collect(), files: BTreeSet::new(), mapped: Vec::new(), } @@ -1034,28 +1357,66 @@ fn serve_client( }; match session.run(options, guest) { Ok(Flow::Exit) => { - write_report(options); + write_report(options, "managed", "success", 0); Flow::Exit } _ => Flow::Continue, } } -/// Writes the successful synthetic OpenVMM outcome report if the destination is new. -fn write_report(options: &Options) { - let report = serde_json::json!({ - "schema_version": 1, - "backend": options.hypervisor, - "outcome": { "operation": "managed", "category": "success", "status_code": 0 }, - "network_policy": { "status": "not-requested" }, - "teardown": { "workers": true, "memory": true }, - }); - if let Ok(mut file) = OpenOptions::new() - .write(true) - .create_new(true) - .open(&options.report) - { - let _ = file.write_all(report.to_string().as_bytes()); +/// Writes the synthetic OpenVMM outcome report of `operation` if a report was requested and its +/// destination is new. +/// +/// A sandbox's report takes the form of OpenVMM's, with the instance, the network policy that the +/// command line requested, and every step of the teardown; a direct launch's keeps a shorter +/// form. +fn write_report(options: &Options, operation: &str, category: &str, status_code: i32) { + let Some(path) = &options.report else { + return; + }; + let report = if options.sandbox { + let mut instance = [0u8; 16]; + let _ = getrandom::fill(&mut instance); + let instance: String = instance.iter().map(|byte| format!("{byte:02x}")).collect(); + let network = &options.network; + let teardown = serde_json::json!({ + "guest_workload_stopped": true, + "vm_stopped": true, + "openvmm_process_terminated": true, + "virtiofs_released": true, + "network_released": true, + "temporary_storage_removed": true, + "control_channels_closed": true, + }); + let text = serde_json::to_string_pretty(&serde_json::json!({ + "schema_version": 1, + "instance_id": instance, + "backend": options.hypervisor, + "outcome": { "operation": operation, "category": category, "status_code": status_code }, + "network_policy": { + "status": if network.requested { "applied" } else { "not-requested" }, + "status_code": 0, + "mode": network.mode, + "allow_rule_count": network.allow, + "deny_rule_count": network.deny, + "host_loopback": network.host_loopback, + }, + "teardown": teardown, + })) + .unwrap_or_default(); + format!("{text}\n") + } else { + serde_json::json!({ + "schema_version": 1, + "backend": options.hypervisor, + "outcome": { "operation": operation, "category": category, "status_code": status_code }, + "network_policy": { "status": "not-requested" }, + "teardown": { "workers": true, "memory": true }, + }) + .to_string() + }; + if let Ok(mut file) = OpenOptions::new().write(true).create_new(true).open(path) { + let _ = file.write_all(report.as_bytes()); } } diff --git a/nvx-rs/tests/cli.rs b/nvx-rs/tests/cli.rs new file mode 100644 index 000000000..a5d3cdf7a --- /dev/null +++ b/nvx-rs/tests/cli.rs @@ -0,0 +1,731 @@ +//! Replays the scenarios of `tests/fixtures/cli.json` with the `nvx-rs` binary, and checks what +//! `nvx-rs` adds to `nvx.py`. +//! +//! `scripts/nvx_rs_fixtures.py` records the scenarios from `nvx.py sandbox` and from the Python +//! functions behind `nvx-rs egress compile` and `nvx-rs artifacts`, with `nvx-fake-openvmm` as +//! OpenVMM, in a section for POSIX hosts and one for Windows. Each scenario runs here in a fresh +//! root, with `nvx-rs --root ROOT`, and every step must exit with the recorded status, print the +//! recorded output, and leave the recorded files, after the root, process IDs, and random +//! identifiers are replaced with the placeholders that the script uses. +#![cfg(all( + feature = "cli", + feature = "testing", + any(target_os = "linux", windows) +))] + +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output, Stdio}; +use std::thread; +use std::time::Duration; + +use serde_json::{Value, json}; + +/// The recorded scenarios. +const FIXTURE: &str = include_str!("fixtures/cli.json"); +/// The CLI under test. +const NVX_RS: &str = env!("CARGO_BIN_EXE_nvx-rs"); +/// The fake OpenVMM, which the root of every scenario holds as its OpenVMM executable. +const FAKE_OPENVMM: &str = env!("CARGO_BIN_EXE_nvx-fake-openvmm"); +/// The section of the fixture for this host. +const PLATFORM: &str = if cfg!(windows) { "windows" } else { "posix" }; +/// The files whose permissions the CLI sets, which the fixture records on POSIX hosts. +#[cfg(unix)] +const MODE_NAMES: [&str; 5] = [ + "state", + "config.json", + "runtime.json", + "control.capability", + "lifecycle.lock", +]; +/// How many scenarios run at once. +const PARALLELISM: usize = 4; + +/// A fresh scenario root, removed on drop after the fake OpenVMM processes that it records end. +struct Root { + /// The temporary directory. + _directory: tempfile::TempDir, + /// Its canonical path, as Python's `Path.resolve()` gives it. + path: PathBuf, +} + +impl Root { + fn new() -> Self { + let directory = tempfile::Builder::new() + .prefix("nvx-cli-") + .tempdir() + .unwrap(); + let path = canonical(directory.path()); + let text = path.to_str().expect("the temporary directory is Unicode"); + // The recorded commands quote no part of the root, as `shlex.join` or + // `subprocess.list2cmdline` would quote a path with spaces or other special characters. + let plain = if cfg!(windows) { + text.is_ascii() && !text.contains([' ', '\t', '"']) + } else { + text.chars() + .all(|c| c.is_ascii_alphanumeric() || "_@%+=:,./-".contains(c)) + }; + assert!( + plain, + "the golden CLI tests need a temporary directory without spaces or special \ + characters, not {text}; set TMPDIR or TEMP" + ); + Self { + _directory: directory, + path, + } + } + + fn text(&self) -> &str { + self.path.to_str().unwrap() + } + + /// Returns the path of `relative`, with `/` separators and placeholders, below the root. + fn below(&self, relative: &str) -> PathBuf { + self.expand(relative) + .split('/') + .fold(self.path.clone(), |path, part| path.join(part)) + } + + /// Replaces the `{root}` and `{exe}` placeholders of an input. + fn expand(&self, text: &str) -> String { + text.replace("{root}", self.text()) + .replace("{exe}", std::env::consts::EXE_SUFFIX) + } + + /// Replaces the root and the volatile values of an output with the fixture's placeholders. + fn normalize(&self, text: &str) -> String { + let raw = self.text(); + let escaped = python_json_escape(raw); + let mut text = text.to_owned(); + if escaped != raw { + text = text.replace(&escaped, "{root_json}"); + } + text = text.replace(raw, "{root}"); + for prefix in ["\"pid\": ", "\"start_time\": "] { + text = replace_after(&text, prefix, |c| c.is_ascii_digit(), None, "{int}"); + } + text = replace_after(&text, "openvmm-microvm-", is_hex, Some(32), "{hex32}"); + for prefix in ["\"operation_id\": \"", "\"instance_id\": \""] { + text = replace_after(&text, prefix, is_hex, Some(32), "{hex32}"); + } + text + } + + /// Creates the tree of the fixture. + fn populate(&self, tree: &[Value]) { + for entry in tree { + let path = self.below(entry["path"].as_str().unwrap()); + if entry.get("openvmm").is_some() { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::copy(FAKE_OPENVMM, &path).unwrap(); + } else if let Some(text) = entry.get("text") { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(&path, text.as_str().unwrap()).unwrap(); + } else { + fs::create_dir_all(&path).unwrap(); + } + } + } + + /// Returns what a step shows of a file or directory below the root, as the script records + /// it. + fn snapshot(&self, relative: &str) -> Value { + let path = self.below(relative); + let Ok(metadata) = fs::symlink_metadata(&path) else { + return Value::Null; + }; + let name = path.file_name().unwrap().to_str().unwrap(); + let mut entry = serde_json::Map::new(); + if metadata.is_dir() { + let mut names: Vec = fs::read_dir(&path) + .unwrap() + .map(|entry| entry.unwrap().file_name().into_string().unwrap()) + .collect(); + names.sort(); + entry.insert("entries".into(), json!(names)); + } else if name == "control.capability" { + entry.insert("size".into(), json!(metadata.len())); + } else { + let text = String::from_utf8(fs::read(&path).unwrap()).unwrap(); + entry.insert("text".into(), json!(self.normalize(&text))); + } + #[cfg(unix)] + if MODE_NAMES.contains(&name) || name.starts_with("exec") { + use std::os::unix::fs::PermissionsExt; + entry.insert("mode".into(), json!(metadata.permissions().mode() & 0o7777)); + } + Value::Object(entry) + } + + /// Ends the fake OpenVMM processes that the runtime records below the root name, so that the + /// root can be removed after a failure. + fn end_leftovers(&self) { + let mut pending = vec![self.path.clone()]; + while let Some(directory) = pending.pop() { + let Ok(entries) = fs::read_dir(&directory) else { + continue; + }; + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + pending.push(path); + } else if entry.file_name() == "runtime.json" + && let Some(pid) = fs::read_to_string(&path) + .ok() + .and_then(|text| serde_json::from_str::(&text).ok()) + .and_then(|record| record["pid"].as_u64()) + { + kill(pid); + } + } + } + } +} + +impl Drop for Root { + fn drop(&mut self) { + self.end_leftovers(); + } +} + +#[cfg(unix)] +fn kill(pid: u64) { + if let Ok(pid) = i32::try_from(pid) { + let _ = nix::sys::signal::kill( + nix::unistd::Pid::from_raw(pid), + nix::sys::signal::Signal::SIGKILL, + ); + thread::sleep(Duration::from_millis(200)); + } +} + +#[cfg(windows)] +fn kill(pid: u64) { + let _ = Command::new("taskkill") + .args(["/F", "/PID", &pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + thread::sleep(Duration::from_millis(200)); +} + +/// Returns the canonical form of `path`, without the `\\?\` prefix that Windows adds and Python's +/// `Path.resolve()` drops. +fn canonical(path: &Path) -> PathBuf { + let path = fs::canonicalize(path).unwrap(); + let text = path.to_str().unwrap(); + match text.strip_prefix(r"\\?\") { + Some(rest) if !rest.starts_with("UNC\\") => PathBuf::from(rest), + _ => path, + } +} + +fn is_hex(c: char) -> bool { + c.is_ascii_digit() || ('a'..='f').contains(&c) +} + +/// Replaces each run of characters of `class` after `prefix`, of exactly `length` characters if +/// given, with `placeholder`. +fn replace_after( + text: &str, + prefix: &str, + class: fn(char) -> bool, + length: Option, + placeholder: &str, +) -> String { + let mut output = String::with_capacity(text.len()); + let mut rest = text; + while let Some(position) = rest.find(prefix) { + let start = position + prefix.len(); + output.push_str(&rest[..start]); + let tail = &rest[start..]; + let run = tail.find(|c| !class(c)).unwrap_or(tail.len()); + if run > 0 && length.is_none_or(|length| run == length) { + output.push_str(placeholder); + rest = &tail[run..]; + } else { + rest = tail; + } + } + output.push_str(rest); + output +} + +/// Returns `json.dumps(text)` without its quotes: `ensure_ascii` escapes. +fn python_json_escape(text: &str) -> String { + let mut output = String::new(); + for c in text.chars() { + match c { + '"' => output.push_str("\\\""), + '\\' => output.push_str("\\\\"), + '\n' => output.push_str("\\n"), + '\r' => output.push_str("\\r"), + '\t' => output.push_str("\\t"), + '\u{8}' => output.push_str("\\b"), + '\u{c}' => output.push_str("\\f"), + ' '..='~' => output.push(c), + _ => { + let mut units = [0u16; 2]; + for unit in c.encode_utf16(&mut units) { + output.push_str(&format!("\\u{unit:04x}")); + } + } + } + } + output +} + +/// Runs `nvx-rs --root ROOT ARGUMENTS` in the root, without the variables that change its +/// output. +fn nvx_rs(root: &Root, arguments: &[String], environment: &BTreeMap) -> Output { + Command::new(NVX_RS) + .arg("--root") + .arg(&root.path) + .args(arguments) + .current_dir(&root.path) + .env_remove("NVX_OPENVMM") + .env_remove("COLUMNS") + .env_remove("LINES") + .envs(environment) + .stdin(Stdio::null()) + .output() + .unwrap() +} + +/// Writes a file of a step: text, `hex` bytes, or `text` then `fill` repeated to `size` bytes. +fn write_file(path: &Path, content: &Value) { + let data = match content { + Value::String(text) => text.clone().into_bytes(), + Value::Object(spec) => match spec.get("hex") { + Some(hex) => (0..hex.as_str().unwrap().len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex.as_str().unwrap()[i..i + 2], 16).unwrap()) + .collect(), + None => { + let mut data = spec + .get("text") + .and_then(Value::as_str) + .unwrap_or_default() + .as_bytes() + .to_vec(); + let fill = spec["fill"].as_str().unwrap().as_bytes(); + let size = usize::try_from(spec["size"].as_u64().unwrap()).unwrap(); + while data.len() < size { + data.extend_from_slice(fill); + } + data + } + }, + other => panic!("invalid file content {other}"), + }; + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, data).unwrap(); +} + +/// Plays a scenario and returns how its steps differ from the recording. +fn play(tree: &[Value], scenario: &Value) -> Vec { + let name = scenario["name"].as_str().unwrap(); + let root = Root::new(); + root.populate(tree); + let mut problems = Vec::new(); + for (index, step) in scenario["steps"].as_array().unwrap().iter().enumerate() { + for relative in step + .get("remove") + .and_then(Value::as_array) + .into_iter() + .flatten() + { + fs::remove_file(root.below(relative.as_str().unwrap())).unwrap(); + } + for (relative, content) in step + .get("write") + .and_then(Value::as_object) + .into_iter() + .flatten() + { + write_file(&root.below(relative), content); + } + let arguments: Vec = step["argv"] + .as_array() + .unwrap() + .iter() + .map(|argument| root.expand(argument.as_str().unwrap())) + .collect(); + let environment: BTreeMap = step + .get("env") + .and_then(Value::as_object) + .into_iter() + .flatten() + .map(|(name, value)| (name.clone(), root.expand(value.as_str().unwrap()))) + .collect(); + let output = nvx_rs(&root, &arguments, &environment); + let mut actual = json!({ + "status": output.status.code(), + "stdout": root.normalize(&String::from_utf8_lossy(&output.stdout)), + "stderr": root.normalize(&String::from_utf8_lossy(&output.stderr)), + }); + if let Some(checks) = step.get("check").and_then(Value::as_array) { + let files: serde_json::Map = checks + .iter() + .map(|relative| { + let relative = relative.as_str().unwrap(); + (relative.to_owned(), root.snapshot(relative)) + }) + .collect(); + actual["files"] = Value::Object(files); + } + for field in ["status", "stdout", "stderr", "files"] { + let expected = step.get(field).unwrap_or(&Value::Null); + let got = actual.get(field).unwrap_or(&Value::Null); + if expected != got { + problems.push(format!( + "{name}, step {} ({}), {field}:\n expected: {expected}\n actual: {got}", + index + 1, + arguments.join(" ") + )); + } + } + } + problems +} + +#[test] +fn nvx_rs_reproduces_the_recorded_scenarios() { + let fixture: Value = serde_json::from_str(FIXTURE).unwrap(); + let tree = fixture["tree"].as_array().unwrap().clone(); + let scenarios = fixture[PLATFORM].as_array().unwrap().clone(); + assert!( + !scenarios.is_empty(), + "tests/fixtures/cli.json has no {PLATFORM} scenarios" + ); + let chunks: Vec<&[Value]> = scenarios + .chunks(scenarios.len().div_ceil(PARALLELISM)) + .collect(); + let problems: Vec = thread::scope(|scope| { + let handles: Vec<_> = chunks + .iter() + .map(|chunk| { + let tree = &tree; + scope.spawn(move || { + chunk + .iter() + .flat_map(|scenario| play(tree, scenario)) + .collect::>() + }) + }) + .collect(); + handles + .into_iter() + .flat_map(|handle| handle.join().unwrap()) + .collect() + }); + assert!( + problems.is_empty(), + "{} differences from nvx.py:\n{}", + problems.len(), + problems.join("\n") + ); +} + +/// Returns the arguments that provision a minimal sandbox in `state` below the root. +fn provision(root: &Root, state: &str, command_line: &str) -> Vec { + let path = |name: &str| root.path.join(name).to_str().unwrap().to_owned(); + vec![ + "sandbox".into(), + "provision".into(), + "--state-dir".into(), + path(state), + "--layer".into(), + format!( + "distro,{},11111111-1111-1111-1111-111111111111", + path("distro.erofs") + ), + "--scratch".into(), + path("scratch.ext4"), + "--cmdline".into(), + command_line.into(), + ] +} + +/// Returns a root with the fixture's tree. +fn populated_root() -> Root { + let fixture: Value = serde_json::from_str(FIXTURE).unwrap(); + let root = Root::new(); + root.populate(fixture["tree"].as_array().unwrap()); + root +} + +fn run(root: &Root, arguments: &[String]) -> Output { + nvx_rs(root, arguments, &BTreeMap::new()) +} + +fn lifecycle(operation: &str, root: &Root, state: &str) -> Vec { + vec![ + "sandbox".into(), + operation.into(), + "--state-dir".into(), + root.path.join(state).to_str().unwrap().to_owned(), + ] +} + +fn text(data: &[u8]) -> String { + String::from_utf8_lossy(data).replace("\r\n", "\n") +} + +#[test] +fn start_requires_the_guest_features_of_layered_sandboxes() { + let root = populated_root(); + // A guest that lacks per-execution environments, or that predates feature negotiation, is + // stopped, and its start fails like any other. + for (state, command_line) in [ + ("cancel-only", "fake_guest_features=1"), + ("legacy", "fake_legacy_guest=1"), + ] { + let output = run(&root, &provision(&root, state, command_line)); + assert!(output.status.success(), "{}", text(&output.stderr)); + let output = run(&root, &lifecycle("start", &root, state)); + assert_eq!(output.status.code(), Some(1)); + assert_eq!( + text(&output.stderr), + "error: the guest image lacks control features that layered sandboxes need \ + (per-execution environments); use an NVX release or build that includes them\n" + ); + let mut names: Vec = fs::read_dir(root.path.join(state)) + .unwrap() + .map(|entry| entry.unwrap().file_name().into_string().unwrap()) + .collect(); + names.sort(); + assert_eq!(names, ["config.json", "lifecycle.lock", "openvmm.log"]); + let output = run(&root, &lifecycle("deprovision", &root, state)); + assert!(output.status.success(), "{}", text(&output.stderr)); + } + // Per-execution environments are all that the CLI needs: neither cancellation nor workload + // accounts. + let output = run( + &root, + &provision(&root, "environments", "fake_guest_features=16"), + ); + assert!(output.status.success(), "{}", text(&output.stderr)); + for operation in ["start", "stop", "deprovision"] { + let output = run(&root, &lifecycle(operation, &root, "environments")); + assert!( + output.status.success(), + "{operation}: {}", + text(&output.stderr) + ); + } +} + +#[test] +fn artifacts_default_to_the_checkout_that_holds_the_executable() { + let output = Command::new(NVX_RS) + .arg("artifacts") + .env_remove("NVX_OPENVMM") + .output() + .unwrap(); + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let in_checkout = fs::canonicalize(manifest_dir.join("target")) + .is_ok_and(|target| canonical(Path::new(NVX_RS)).starts_with(canonical(&target))); + if in_checkout { + assert!(output.status.success(), "{}", text(&output.stderr)); + let paths: Value = serde_json::from_slice(&output.stdout).unwrap(); + assert_eq!(paths["layout"], "repository"); + assert_eq!( + canonical(Path::new(paths["root"].as_str().unwrap())), + canonical(manifest_dir.parent().unwrap()) + ); + } else { + // A build elsewhere, as with CARGO_TARGET_DIR, needs --root. + assert_eq!(output.status.code(), Some(1)); + assert!(text(&output.stderr).starts_with("error: cannot locate the NVX artifacts")); + } +} + +#[test] +fn a_relative_openvmm_override_is_relative_to_the_working_directory() { + let root = Root::new(); + let output = Command::new(NVX_RS) + .args(["--root", ".", "artifacts"]) + .current_dir(&root.path) + .env("NVX_OPENVMM", Path::new("alt").join("openvmm")) + .output() + .unwrap(); + assert!(output.status.success(), "{}", text(&output.stderr)); + let paths: Value = serde_json::from_slice(&output.stdout).unwrap(); + assert_eq!(paths["root"], root.text()); + assert_eq!( + paths["openvmm"], + root.path.join("alt").join("openvmm").to_str().unwrap() + ); +} + +#[test] +fn top_level_usage_errors_exit_with_status_2() { + let usage = "usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\n"; + for (arguments, message) in [ + ( + &[][..], + "nvx-rs: error: the following arguments are required: command\n", + ), + ( + &["--root"][..], + "nvx-rs: error: argument --root: expected one argument\n", + ), + ( + &["build"][..], + "nvx-rs: error: argument command: invalid choice: 'build' (choose from \ + 'artifacts', 'egress', 'sandbox')\n", + ), + ] { + let output = Command::new(NVX_RS).args(arguments).output().unwrap(); + assert_eq!(output.status.code(), Some(2), "{arguments:?}"); + assert_eq!( + text(&output.stderr), + format!("{usage}{message}"), + "{arguments:?}" + ); + } + let output = Command::new(NVX_RS) + .args(["egress", "compile"]) + .output() + .unwrap(); + assert_eq!(output.status.code(), Some(2)); + assert_eq!( + text(&output.stderr), + "usage: nvx-rs egress compile [-h] PATH\n\ + nvx-rs egress compile: error: the following arguments are required: PATH\n" + ); +} + +#[cfg(unix)] +mod interrupts { + use std::time::Instant; + + use super::*; + + /// Sends SIGINT once OpenVMM has started, to the CLI alone or, as a terminal's Ctrl-C does, + /// to its whole process group, which a run's OpenVMM shares, and returns the CLI's output. + fn interrupt( + root: &Root, + arguments: &[String], + started: &Path, + group: bool, + ) -> (Output, Duration) { + use std::os::unix::process::CommandExt; + + let mut command = Command::new(NVX_RS); + command + .arg("--root") + .arg(&root.path) + .args(arguments) + .current_dir(&root.path) + .env_remove("NVX_OPENVMM") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if group { + command.process_group(0); + } + let child = command.spawn().unwrap(); + let deadline = Instant::now() + Duration::from_secs(30); + while !started.exists() && Instant::now() < deadline { + thread::sleep(Duration::from_millis(20)); + } + thread::sleep(Duration::from_millis(200)); + let interrupted = Instant::now(); + let pid = nix::unistd::Pid::from_raw(i32::try_from(child.id()).unwrap()); + if group { + nix::sys::signal::killpg(pid, nix::sys::signal::Signal::SIGINT).unwrap(); + } else { + nix::sys::signal::kill(pid, nix::sys::signal::Signal::SIGINT).unwrap(); + } + let output = child.wait_with_output().unwrap(); + (output, interrupted.elapsed()) + } + + /// Returns the arguments of a one-shot run of `/bin/sleep 60` in the fake OpenVMM, and the + /// file that the fake writes when it starts. + fn sleeping_run(root: &Root) -> (Vec, PathBuf) { + let path = |name: &str| root.path.join(name).to_str().unwrap().to_owned(); + let arguments: Vec = [ + "sandbox", + "run", + "--layer", + &format!( + "distro,{},11111111-1111-1111-1111-111111111111", + path("distro.erofs") + ), + "--scratch", + &path("scratch.ext4"), + "--entrypoint", + "/bin/sleep", + "--arg", + "60", + ] + .map(str::to_owned) + .to_vec(); + // The fake records its command line when it starts. + ( + arguments, + root.path.join("build").join("fake-openvmm-.json"), + ) + } + + #[test] + fn interrupting_a_run_kills_openvmm_and_exits_with_130() { + let root = populated_root(); + let (arguments, started) = sleeping_run(&root); + let (output, elapsed) = interrupt(&root, &arguments, &started, false); + assert_eq!(output.status.code(), Some(130)); + assert_eq!(text(&output.stderr), "Interrupted\n"); + assert!(text(&output.stdout).starts_with(">> ")); + assert!(elapsed < Duration::from_secs(10), "{elapsed:?}"); + } + + #[test] + fn interrupting_a_run_ends_it_as_interrupted_even_if_openvmm_exits_first() { + // A terminal's Ctrl-C reaches OpenVMM too, which the fake lets end it at once; Python's + // wait raises KeyboardInterrupt all the same, rather than return OpenVMM's status. + let root = populated_root(); + let (arguments, started) = sleeping_run(&root); + let (output, elapsed) = interrupt(&root, &arguments, &started, true); + assert_eq!(output.status.code(), Some(130)); + assert_eq!(text(&output.stderr), "Interrupted\n"); + assert!(elapsed < Duration::from_secs(10), "{elapsed:?}"); + } + + #[test] + fn interrupting_an_exec_leaves_the_sandbox_running() { + let root = populated_root(); + let output = run(&root, &provision(&root, "state", "")); + assert!(output.status.success(), "{}", text(&output.stderr)); + let output = run(&root, &lifecycle("start", &root, "state")); + assert!(output.status.success(), "{}", text(&output.stderr)); + let mut arguments = lifecycle("exec", &root, "state"); + arguments.extend( + [ + "--entrypoint", + "/bin/sh", + "--arg=-c", + "--arg", + "sleep 60000", + ] + .map(str::to_owned), + ); + let started = root.path.join("state").join("runtime.json"); + let (output, elapsed) = interrupt(&root, &arguments, &started, false); + assert_eq!(output.status.code(), Some(130)); + assert_eq!(text(&output.stderr), "Interrupted\n"); + assert!(elapsed < Duration::from_secs(10), "{elapsed:?}"); + for operation in ["stop", "deprovision"] { + let output = run(&root, &lifecycle(operation, &root, "state")); + assert!( + output.status.success(), + "{operation}: {}", + text(&output.stderr) + ); + } + } +} diff --git a/nvx-rs/tests/fixtures/cli.json b/nvx-rs/tests/fixtures/cli.json new file mode 100644 index 000000000..b2b30f0f2 --- /dev/null +++ b/nvx-rs/tests/fixtures/cli.json @@ -0,0 +1,303 @@ +{ +"generator": "scripts/nvx_rs_fixtures.py", +"python": "3.14", +"tree": [ +{"path":"build/vmlinux","text":"kernel\n"}, +{"path":"build/initramfs.cpio.gz","text":"initramfs\n"}, +{"path":"distro.erofs","text":"distro\n"}, +{"path":"runtime.erofs","text":"runtime\n"}, +{"path":"custom.erofs","text":"custom\n"}, +{"path":"scratch.ext4","text":"scratch\n"}, +{"path":"share/file.txt","text":"file\n"}, +{"path":"share/sub/data.txt","text":"data\n"}, +{"path":"share/hidden/shown/data.txt","text":"data\n"}, +{"path":"share/writable/data.txt","text":"data\n"}, +{"path":"share2/data.txt","text":"data\n"}, +{"path":"single.txt","text":"single\n"}, +{"path":"reports"}, +{"path":"openvmm/target/release/openvmm{exe}","openvmm":true} +], +"posix": [ +{"name":"egress compile: empty","steps":[{"argv":["egress","compile","{root}/policy.json"],"write":{"policy.json":"{}"},"status":0,"stdout":"{\n \"allow\": [],\n \"deny\": []\n}\n","stderr":""}]}, +{"name":"egress compile: allow","steps":[{"argv":["egress","compile","{root}/policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"}]}"},"status":0,"stdout":"{\n \"allow\": [\n \"192.0.2.0/24\"\n ],\n \"deny\": []\n}\n","stderr":""}]}, +{"name":"egress compile: both","steps":[{"argv":["egress","compile","{root}/policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\"]}, {\"to\": [{\"cidr\": \"2001:db8::/32\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 80, \"endPort\": 81}]}], \"deny\": [{\"cidr\": \"198.51.100.0/24\", \"protocol\": \"icmp\"}]}"},"status":0,"stdout":"{\n \"allow\": [\n \"192.0.2.0/25\",\n \"2001:db8::/32:tcp:80-81\"\n ],\n \"deny\": [\n \"198.51.100.0/24:icmp\"\n ]\n}\n","stderr":""}]}, +{"name":"egress compile: invalid JSON","steps":[{"argv":["egress","compile","{root}/policy.json"],"write":{"policy.json":"{"},"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}/policy.json\n"}]}, +{"name":"egress compile: invalid rule","steps":[{"argv":["egress","compile","{root}/policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"port\": 70000}]}"},"status":1,"stdout":"","stderr":"error: allow[0].port requires protocol\n"}]}, +{"name":"egress compile: unknown field","steps":[{"argv":["egress","compile","{root}/policy.json"],"write":{"policy.json":"{\"allow\": [], \"other\": []}"},"status":1,"stdout":"","stderr":"error: egress policy has unknown field 'other'\n"}]}, +{"name":"egress compile: missing file","steps":[{"argv":["egress","compile","{root}/missing.json"],"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}/missing.json\n"}]}, +{"name":"egress compile: directory","steps":[{"argv":["egress","compile","{root}/share"],"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}/share\n"}]}, +{"name":"artifacts: checkout","steps":[{"argv":["artifacts"],"status":0,"stdout":"{\n \"guests\": [\n {\n \"distribution\": \"Alpine Linux\",\n \"distro_layer\": null,\n \"distro_manifest\": null,\n \"initramfs\": \"{root}/build/initramfs.cpio.gz\",\n \"name\": \"alpine\",\n \"package_manifest\": \"{root}/build/initramfs.cpio.gz.packages.json\"\n },\n {\n \"distribution\": \"Ubuntu Base\",\n \"distro_layer\": \"{root}/build/ubuntu-distro.erofs\",\n \"distro_manifest\": \"{root}/build/ubuntu-distro.erofs.manifest.json\",\n \"initramfs\": \"{root}/build/initramfs-ubuntu.cpio.gz\",\n \"name\": \"ubuntu\",\n \"package_manifest\": \"{root}/build/initramfs-ubuntu.cpio.gz.packages.json\"\n },\n {\n \"distribution\": \"Azure Linux\",\n \"distro_layer\": null,\n \"distro_manifest\": null,\n \"initramfs\": \"{root}/build/initramfs-azurelinux.cpio.gz\",\n \"name\": \"azurelinux\",\n \"package_manifest\": \"{root}/build/initramfs-azurelinux.cpio.gz.packages.json\"\n }\n ],\n \"kernel\": \"{root}/build/vmlinux\",\n \"layout\": \"repository\",\n \"openvmm\": \"{root}/openvmm/target/release/openvmm\",\n \"root\": \"{root}\"\n}\n","stderr":""}]}, +{"name":"artifacts: OpenVMM override","steps":[{"argv":["artifacts"],"env":{"NVX_OPENVMM":"{root}/alt/openvmm{exe}"},"status":0,"stdout":"{\n \"guests\": [\n {\n \"distribution\": \"Alpine Linux\",\n \"distro_layer\": null,\n \"distro_manifest\": null,\n \"initramfs\": \"{root}/build/initramfs.cpio.gz\",\n \"name\": \"alpine\",\n \"package_manifest\": \"{root}/build/initramfs.cpio.gz.packages.json\"\n },\n {\n \"distribution\": \"Ubuntu Base\",\n \"distro_layer\": \"{root}/build/ubuntu-distro.erofs\",\n \"distro_manifest\": \"{root}/build/ubuntu-distro.erofs.manifest.json\",\n \"initramfs\": \"{root}/build/initramfs-ubuntu.cpio.gz\",\n \"name\": \"ubuntu\",\n \"package_manifest\": \"{root}/build/initramfs-ubuntu.cpio.gz.packages.json\"\n },\n {\n \"distribution\": \"Azure Linux\",\n \"distro_layer\": null,\n \"distro_manifest\": null,\n \"initramfs\": \"{root}/build/initramfs-azurelinux.cpio.gz\",\n \"name\": \"azurelinux\",\n \"package_manifest\": \"{root}/build/initramfs-azurelinux.cpio.gz.packages.json\"\n }\n ],\n \"kernel\": \"{root}/build/vmlinux\",\n \"layout\": \"repository\",\n \"openvmm\": \"{root}/alt/openvmm\",\n \"root\": \"{root}\"\n}\n","stderr":""}]}, +{"name":"usage: help","steps":[{"argv":["sandbox","-h"],"status":0,"stdout":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\n\npositional arguments:\n {run,provision,start,exec,stop,deprovision}\n\noptions:\n -h, --help show this help message and exit\n --layer ROLE,PATH,EROFS_UUID\n --scratch SCRATCH\n --state-dir STATE_DIR\n --entrypoint ENTRYPOINT\n --arg SANDBOX_ARG\n --hostname HOSTNAME\n --workload-user UID:GID\n fixed non-root workload identity (default:\n 65534:65534)\n --memory-max MEMORY_MAX\n --pids-max PIDS_MAX\n --memory-mib MEMORY_MIB\n --timeout TIMEOUT control operation timeout, and the longest wait for\n another lifecycle transition of the sandbox, in\n seconds (default: 60)\n --exec-timeout-ms EXEC_TIMEOUT_MS\n guest workload timeout in milliseconds; zero disables\n it\n --cwd CWD absolute guest working directory for managed exec\n (default: /)\n --environment KEY=VALUE\n set the exact managed exec environment; repeat for\n multiple entries\n --environment-file PATH\n read the exact managed exec environment from a JSON\n string array\n --inherit-default-environment\n layer the managed exec environment over the guest\n default environment instead of replacing it\n --hypervisor {auto,whp,kvm,mshv}\n --mount GUEST_TARGET,HOST_PATH[,ro|rw]\n live-share a host directory, or a single regular host\n file without its directory, inside the container\n rootfs; the mode follows the last comma, so HOST_PATH\n may contain commas when the mode is given; repeat to\n attach more shares, each with its own target and mode,\n as children of one virtio-fs device\n --mount-deny HOST_PATH\n hide one existing path inside a --mount host\n directory; with several --mount options, it applies to\n the --mount before it\n --mount-allow HOST_PATH\n expose one existing path inside a --mount-deny path of\n the same share again; the hidden directories on the\n way list only it\n --mount-write HOST_PATH\n make one existing path one of the only writable parts\n of a read-write --mount; the rest of the share is\n read-only\n --mount-owner {vmm,caller}\n host identity of the share's file operations: vmm\n (default) or caller, the workload identity with guest\n root squashed to the owner of the shared directory or\n file (Linux only)\n --net IPV4/PREFIX\n --network-profile {portable}\n --network-egress {allow,deny}\n --network-ingress {allow,deny}\n --network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]\n allow guest egress to an IPv4 or IPv6 CIDR, optionally\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\n udp only on PORT or on the inclusive range PORT-PORT;\n repeat to add rules\n --network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]\n deny guest egress to an IPv4 or IPv6 CIDR, optionally\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\n udp only on PORT or on the inclusive range PORT-PORT;\n repeat to add rules\n --network-egress-policy-file PATH\n load bounded IPv4 and IPv6 ranges and rule-local\n exclusions for run/provision\n --host-loopback {allow,deny}\n --network-proxy IPV4:TCP-PORT\n --host-loopback-forward HOST_LOOPBACK_FORWARD\n --outcome-report OUTCOME_REPORT\n write a bounded local JSON outcome report for run or\n exec\n --cmdline CMDLINE\n --dry-run print the one-shot run command without launching it\n","stderr":""}]}, +{"name":"usage: help after operation","steps":[{"argv":["sandbox","exec","--help"],"status":0,"stdout":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\n\npositional arguments:\n {run,provision,start,exec,stop,deprovision}\n\noptions:\n -h, --help show this help message and exit\n --layer ROLE,PATH,EROFS_UUID\n --scratch SCRATCH\n --state-dir STATE_DIR\n --entrypoint ENTRYPOINT\n --arg SANDBOX_ARG\n --hostname HOSTNAME\n --workload-user UID:GID\n fixed non-root workload identity (default:\n 65534:65534)\n --memory-max MEMORY_MAX\n --pids-max PIDS_MAX\n --memory-mib MEMORY_MIB\n --timeout TIMEOUT control operation timeout, and the longest wait for\n another lifecycle transition of the sandbox, in\n seconds (default: 60)\n --exec-timeout-ms EXEC_TIMEOUT_MS\n guest workload timeout in milliseconds; zero disables\n it\n --cwd CWD absolute guest working directory for managed exec\n (default: /)\n --environment KEY=VALUE\n set the exact managed exec environment; repeat for\n multiple entries\n --environment-file PATH\n read the exact managed exec environment from a JSON\n string array\n --inherit-default-environment\n layer the managed exec environment over the guest\n default environment instead of replacing it\n --hypervisor {auto,whp,kvm,mshv}\n --mount GUEST_TARGET,HOST_PATH[,ro|rw]\n live-share a host directory, or a single regular host\n file without its directory, inside the container\n rootfs; the mode follows the last comma, so HOST_PATH\n may contain commas when the mode is given; repeat to\n attach more shares, each with its own target and mode,\n as children of one virtio-fs device\n --mount-deny HOST_PATH\n hide one existing path inside a --mount host\n directory; with several --mount options, it applies to\n the --mount before it\n --mount-allow HOST_PATH\n expose one existing path inside a --mount-deny path of\n the same share again; the hidden directories on the\n way list only it\n --mount-write HOST_PATH\n make one existing path one of the only writable parts\n of a read-write --mount; the rest of the share is\n read-only\n --mount-owner {vmm,caller}\n host identity of the share's file operations: vmm\n (default) or caller, the workload identity with guest\n root squashed to the owner of the shared directory or\n file (Linux only)\n --net IPV4/PREFIX\n --network-profile {portable}\n --network-egress {allow,deny}\n --network-ingress {allow,deny}\n --network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]\n allow guest egress to an IPv4 or IPv6 CIDR, optionally\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\n udp only on PORT or on the inclusive range PORT-PORT;\n repeat to add rules\n --network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]\n deny guest egress to an IPv4 or IPv6 CIDR, optionally\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\n udp only on PORT or on the inclusive range PORT-PORT;\n repeat to add rules\n --network-egress-policy-file PATH\n load bounded IPv4 and IPv6 ranges and rule-local\n exclusions for run/provision\n --host-loopback {allow,deny}\n --network-proxy IPV4:TCP-PORT\n --host-loopback-forward HOST_LOOPBACK_FORWARD\n --outcome-report OUTCOME_REPORT\n write a bounded local JSON outcome report for run or\n exec\n --cmdline CMDLINE\n --dry-run print the one-shot run command without launching it\n","stderr":""}]}, +{"name":"usage: timeout not a number","steps":[{"argv":["sandbox","--timeout","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --timeout: invalid positive_float value: 'x'\n"}]}, +{"name":"usage: zero timeout","steps":[{"argv":["sandbox","--timeout","0"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\n"}]}, +{"name":"usage: negative timeout","steps":[{"argv":["sandbox","--timeout","-1"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\n"}]}, +{"name":"usage: infinite timeout","steps":[{"argv":["sandbox","--timeout","inf"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\n"}]}, +{"name":"usage: timeout not finite","steps":[{"argv":["sandbox","--timeout=1e400"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\n"}]}, +{"name":"usage: timeout not a number either","steps":[{"argv":["sandbox","--timeout","nan"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\n"}]}, +{"name":"usage: exec timeout not an integer","steps":[{"argv":["sandbox","--exec-timeout-ms","1.5"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --exec-timeout-ms: invalid int value: '1.5'\n"}]}, +{"name":"usage: memory not an integer","steps":[{"argv":["sandbox","--memory-mib","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --memory-mib: invalid int value: 'x'\n"}]}, +{"name":"usage: invalid hypervisor","steps":[{"argv":["sandbox","--hypervisor","hyperv"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --hypervisor: invalid choice: 'hyperv' (choose from 'auto', 'whp', 'kvm', 'mshv')\n"}]}, +{"name":"usage: invalid operation","steps":[{"argv":["sandbox","restart"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument sandbox_operation: invalid choice: 'restart' (choose from 'run', 'provision', 'start', 'exec', 'stop', 'deprovision')\n"}]}, +{"name":"usage: second operation","steps":[{"argv":["sandbox","run","start"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\nnvx-rs: error: unrecognized arguments: start\n"}]}, +{"name":"usage: invalid mount owner","steps":[{"argv":["sandbox","--mount-owner","root"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --mount-owner: invalid choice: 'root' (choose from 'vmm', 'caller')\n"}]}, +{"name":"usage: invalid network profile","steps":[{"argv":["sandbox","--network-profile","open"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --network-profile: invalid choice: 'open' (choose from 'portable')\n"}]}, +{"name":"usage: invalid egress default","steps":[{"argv":["sandbox","--network-egress","block"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --network-egress: invalid choice: 'block' (choose from 'allow', 'deny')\n"}]}, +{"name":"usage: invalid host loopback","steps":[{"argv":["sandbox","--host-loopback","yes"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --host-loopback: invalid choice: 'yes' (choose from 'allow', 'deny')\n"}]}, +{"name":"usage: missing value","steps":[{"argv":["sandbox","--layer"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --layer: expected one argument\n"}]}, +{"name":"usage: option as value","steps":[{"argv":["sandbox","--layer","--scratch"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --layer: expected one argument\n"}]}, +{"name":"usage: dash value","steps":[{"argv":["sandbox","--arg","-x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --arg: expected one argument\n"}]}, +{"name":"usage: flag with value","steps":[{"argv":["sandbox","--dry-run=1"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --dry-run: ignored explicit argument '1'\n"}]}, +{"name":"usage: unrecognized option","steps":[{"argv":["sandbox","--bogus"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\nnvx-rs: error: unrecognized arguments: --bogus\n"}]}, +{"name":"usage: unrecognized short option","steps":[{"argv":["sandbox","-x"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\nnvx-rs: error: unrecognized arguments: -x\n"}]}, +{"name":"usage: unrecognized after separator","steps":[{"argv":["sandbox","run","--","extra"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\nnvx-rs: error: unrecognized arguments: extra\n"}]}, +{"name":"validation: dry run outside run","steps":[{"argv":["sandbox","provision","--dry-run"],"status":1,"stdout":"","stderr":"error: --dry-run is only valid for sandbox run\n"}]}, +{"name":"validation: environment and environment file","steps":[{"argv":["sandbox","exec","--environment","A=1","--environment-file","{root}/env.json"],"write":{"env.json":"[\"A=1\"]"},"status":1,"stdout":"","stderr":"error: --environment and --environment-file are mutually exclusive\n"}]}, +{"name":"validation: cwd outside exec","steps":[{"argv":["sandbox","run","--cwd","/"],"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\n"}]}, +{"name":"validation: environment outside exec","steps":[{"argv":["sandbox","start","--environment","A=1"],"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\n"}]}, +{"name":"validation: environment file outside exec","steps":[{"argv":["sandbox","provision","--environment-file","{root}/env.json"],"write":{"env.json":"[\"A=1\"]"},"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\n"}]}, +{"name":"validation: inherited environment outside exec","steps":[{"argv":["sandbox","stop","--inherit-default-environment"],"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\n"}]}, +{"name":"validation: environment file too large","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":{"fill":" ","size":1048577}},"status":1,"stdout":"","stderr":"error: managed execution environment file exceeds 1048576-byte limit\n"}]}, +{"name":"validation: environment file at the limit","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":{"text":"[\"A=1\"]","fill":" ","size":1048576}},"status":1,"stdout":"","stderr":"error: sandbox exec requires --state-dir\n"}]}, +{"name":"validation: environment file missing","steps":[{"argv":["sandbox","exec","--environment-file","{root}/missing.json"],"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}/missing.json\n"}]}, +{"name":"validation: environment file directory","steps":[{"argv":["sandbox","exec","--environment-file","{root}/share"],"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}/share\n"}]}, +{"name":"validation: environment file not UTF-8","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":{"hex":"5b22ff225d"}},"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}/env.json\n"}]}, +{"name":"validation: environment file not JSON","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":"[\"A=1\""},"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}/env.json\n"}]}, +{"name":"validation: environment file object","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":"{\"A\": \"1\"}"},"status":1,"stdout":"","stderr":"error: managed execution environment file must contain a JSON array of KEY=VALUE strings\n"}]}, +{"name":"validation: environment file non-string","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":"[\"A=1\", 2]"},"status":1,"stdout":"","stderr":"error: managed execution environment file must contain a JSON array of KEY=VALUE strings\n"}]}, +{"name":"validation: environment file entry without equals","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":"[\"A\"]"},"status":1,"stdout":"","stderr":"error: managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes\n"}]}, +{"name":"validation: environment file entry with NUL","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":"[\"A=\\u0000\"]"},"status":1,"stdout":"","stderr":"error: managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes\n"}]}, +{"name":"validation: environment file duplicate entry","steps":[{"argv":["sandbox","exec","--environment-file","{root}/env.json"],"write":{"env.json":"[\"A=1\", \"A=2\"]"},"status":1,"stdout":"","stderr":"error: managed exec environment contains duplicate key: A\n"}]}, +{"name":"validation: environment entry without name","steps":[{"argv":["sandbox","exec","--environment","=x"],"status":1,"stdout":"","stderr":"error: managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes\n"}]}, +{"name":"validation: environment entry with space","steps":[{"argv":["sandbox","exec","--environment","A B=x"],"status":1,"stdout":"","stderr":"error: sandbox exec requires --state-dir\n"}]}, +{"name":"validation: network outside run","steps":[{"argv":["sandbox","start","--net","10.0.0.2/24"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\n"}]}, +{"name":"validation: egress rule outside run","steps":[{"argv":["sandbox","exec","--network-egress-allow","10.0.0.0/8"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\n"}]}, +{"name":"validation: forward outside run","steps":[{"argv":["sandbox","stop","--host-loopback-forward","80"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\n"}]}, +{"name":"validation: policy file outside run","steps":[{"argv":["sandbox","deprovision","--network-egress-policy-file","{root}/policy.json"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\n"}]}, +{"name":"validation: systemd entrypoint of run","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/usr/lib/systemd/systemd"],"status":1,"stdout":"","stderr":"error: systemd entrypoints are unsupported by the sandbox security profile\n"}]}, +{"name":"validation: systemd entrypoint of exec","steps":[{"argv":["sandbox","exec","--entrypoint","/lib/systemd/systemd"],"status":1,"stdout":"","stderr":"error: systemd entrypoints are unsupported by the sandbox security profile\n"}]}, +{"name":"validation: outcome report outside run","steps":[{"argv":["sandbox","provision","--outcome-report","{root}/reports/exec.json"],"status":1,"stdout":"","stderr":"error: --outcome-report is only valid for one-shot run or managed exec\n"}]}, +{"name":"validation: deny without mount","steps":[{"argv":["sandbox","run","--mount-deny","{root}/share/hidden"],"status":1,"stdout":"","stderr":"error: --mount-deny requires --mount\n"}]}, +{"name":"validation: allow without mount","steps":[{"argv":["sandbox","run","--mount-allow","{root}/share/hidden"],"status":1,"stdout":"","stderr":"error: --mount-allow requires --mount\n"}]}, +{"name":"validation: write without mount","steps":[{"argv":["sandbox","run","--mount-write","{root}/share/writable"],"status":1,"stdout":"","stderr":"error: --mount-write requires --mount\n"}]}, +{"name":"validation: owner without mount","steps":[{"argv":["sandbox","run","--mount-owner","vmm"],"status":1,"stdout":"","stderr":"error: --mount-owner requires --mount\n"}]}, +{"name":"validation: mount outside run","steps":[{"argv":["sandbox","start","--mount","/data,{root}/share"],"status":1,"stdout":"","stderr":"error: --mount is only valid for sandbox run or provision\n"}]}, +{"name":"validation: net without profile","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--net","10.0.0.2/24"],"status":1,"stdout":"","stderr":"error: --net and --network-profile must be specified together\n"}]}, +{"name":"validation: profile without net","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--network-profile","portable"],"status":1,"stdout":"","stderr":"error: --net and --network-profile must be specified together\n"}]}, +{"name":"validation: run without layers","steps":[{"argv":["sandbox","run"],"status":1,"stdout":"","stderr":"error: sandbox run requires --layer and --scratch\n"}]}, +{"name":"validation: provision without scratch","steps":[{"argv":["sandbox","provision","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111"],"status":1,"stdout":"","stderr":"error: sandbox provision requires --layer and --scratch\n"}]}, +{"name":"validation: run without layer","steps":[{"argv":["sandbox","run","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox run requires --layer and --scratch\n"}]}, +{"name":"validation: policy file with rules","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--network-egress","allow","--network-egress-policy-file","{root}/policy.json","--network-egress-deny","10.0.0.0/8"],"status":1,"stdout":"","stderr":"error: --network-egress-policy-file cannot be combined with --network-egress-allow or --network-egress-deny\n"}]}, +{"name":"validation: policy file without egress","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--network-egress-policy-file","{root}/policy.json"],"status":1,"stdout":"","stderr":"error: --network-egress is required with --network-egress-policy-file\n"}]}, +{"name":"validation: policy file missing","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--network-egress","deny","--network-egress-policy-file","{root}/missing.json"],"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}/missing.json\n"}]}, +{"name":"validation: policy file invalid","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--network-egress","deny","--network-egress-policy-file","{root}/policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"10.0.0.0/8\", \"port\": 0}]}"},"status":1,"stdout":"","stderr":"error: allow[0].port requires protocol\n"}]}, +{"name":"validation: provision without state directory","steps":[{"argv":["sandbox","provision","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox provision requires --state-dir\n"}]}, +{"name":"validation: start without state directory","steps":[{"argv":["sandbox","start"],"status":1,"stdout":"","stderr":"error: sandbox start requires --state-dir\n"}]}, +{"name":"validation: exec without state directory","steps":[{"argv":["sandbox","exec"],"status":1,"stdout":"","stderr":"error: sandbox exec requires --state-dir\n"}]}, +{"name":"validation: stop without state directory","steps":[{"argv":["sandbox","stop"],"status":1,"stdout":"","stderr":"error: sandbox stop requires --state-dir\n"}]}, +{"name":"validation: deprovision without state directory","steps":[{"argv":["sandbox","deprovision"],"status":1,"stdout":"","stderr":"error: sandbox deprovision requires --state-dir\n"}]}, +{"name":"validation: run with state directory","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: one-shot sandbox execution rejects persistent --state-dir settings\n"}]}, +{"name":"validation: invalid layer UUID","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,nope","--scratch","{root}/scratch.ext4"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --layer: distro layer UUID is invalid: 'nope'\n"}]}, +{"name":"validation: invalid layer role","steps":[{"argv":["sandbox","run","--layer","base,{root}/distro.erofs,11111111-1111-1111-1111-111111111111"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --layer: unsupported layer role 'base'; choose distro, runtime, custom\n"}]}, +{"name":"validation: missing layer","steps":[{"argv":["sandbox","run","--layer","distro,{root}/missing.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: distro EROFS layer not found: {root}/missing.erofs\n"}]}, +{"name":"validation: duplicate layer","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: duplicate sandbox layer role(s): distro\n"}]}, +{"name":"validation: missing scratch","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/missing.ext4"],"status":1,"stdout":"","stderr":"error: ext4 scratch image not found: {root}/missing.ext4\n"}]}, +{"name":"validation: relative entrypoint","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","bin/sh"],"status":1,"stdout":"","stderr":"error: sandbox entrypoint must be an absolute path without spaces\n"}]}, +{"name":"validation: invalid hostname","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--hostname","bad host"],"status":1,"stdout":"","stderr":"error: sandbox hostname must be a lowercase RFC 1123 label up to 63 characters\n"}]}, +{"name":"validation: argument with space","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--arg","a b"],"status":1,"stdout":"","stderr":"error: sandbox arguments must be nonempty and contain no spaces\n"}]}, +{"name":"validation: root workload","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--workload-user","0:0"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --workload-user: --workload-user UID must be between 1 and 4294967295\n"}]}, +{"name":"validation: invalid workload user","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--workload-user","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --workload-user: --workload-user must be UID:GID\n"}]}, +{"name":"validation: invalid memory limit","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--memory-max","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\n [--cwd CWD] [--environment KEY=VALUE]\n [--environment-file PATH]\n [--inherit-default-environment]\n [--hypervisor {auto,whp,kvm,mshv}]\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\n [--net IPV4/PREFIX] [--network-profile {portable}]\n [--network-egress {allow,deny}]\n [--network-ingress {allow,deny}]\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\n [--network-egress-policy-file PATH]\n [--host-loopback {allow,deny}]\n [--network-proxy IPV4:TCP-PORT]\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\n [--dry-run]\n [{run,provision,start,exec,stop,deprovision}]\nnvx-rs sandbox: error: argument --memory-max: invalid int value: 'x'\n"}]}, +{"name":"validation: invalid process limit","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--pids-max","0"],"status":1,"stdout":"","stderr":"error: --pids-max must be positive\n"}]}, +{"name":"validation: relative mount target","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","data,{root}/share"],"status":1,"stdout":"","stderr":"error: invalid sandbox mount target 'data': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','\n"}]}, +{"name":"validation: missing mount host","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/missing"],"status":1,"stdout":"","stderr":"error: sandbox mount host path is not a plain directory: {root}/missing\n"}]}, +{"name":"validation: invalid mount mode","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rx"],"status":1,"stdout":"","stderr":"error: unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)\n"}]}, +{"name":"validation: deny outside share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount-deny","{root}/share2"],"status":2,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}/share,ro --mount-deny {root}/share2 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":"openvmm: invalid --mount-deny {root}/share2\n"}]}, +{"name":"validation: allow outside denied path","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount-allow","{root}/share/hidden/shown"],"status":1,"stdout":"","stderr":"error: --mount-allow exposes a path inside a --mount-deny path of the same share, which has none\n"}]}, +{"name":"validation: write in read-only share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,ro","--mount-write","{root}/share/writable"],"status":1,"stdout":"","stderr":"error: --mount-write narrows the writes of a read-write share; --mount /data is read-only\n"}]}, +{"name":"validation: reserved command-line token","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--cmdline","nvx_sandbox=0"],"status":1,"stdout":"","stderr":"error: nvx_sandbox is owned by the sandbox profile\n"}]}, +{"name":"validation: systemd distro image","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"write":{"distro.erofs.manifest.json":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"8a475efd5fd711d481a9a19de6d12b88dff0cd81cb8135ffcd5b65b062c7bf04\", \"packages\": [{\"name\": \"busybox\", \"version\": \"1\"}, {\"name\": \"systemd\", \"version\": \"1\"}]}"},"status":1,"stdout":"","stderr":"error: systemd images are unsupported by the sandbox security profile\n"}]}, +{"name":"validation: mismatched distro manifest","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"write":{"distro.erofs.manifest.json":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0000000000000000000000000000000000000000000000000000000000000000\", \"packages\": []}"},"status":1,"stdout":"","stderr":"error: sandbox distro manifest does not match its artifact\n"}]}, +{"name":"validation: invalid distro manifest","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"write":{"distro.erofs.manifest.json":"{"},"status":1,"stdout":"","stderr":"error: invalid sandbox distro manifest: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)\n"}]}, +{"name":"validation: distro manifest without packages","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"write":{"distro.erofs.manifest.json":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"8a475efd5fd711d481a9a19de6d12b88dff0cd81cb8135ffcd5b65b062c7bf04\"}"},"status":1,"stdout":"","stderr":"error: sandbox distro manifest has invalid package metadata\n"}]}, +{"name":"dry run: minimal","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"dry run: every layer and option","steps":[{"argv":["sandbox","run","--layer","custom,{root}/custom.erofs,33333333-3333-3333-3333-333333333333","--layer","runtime,{root}/runtime.erofs,22222222-2222-2222-2222-222222222222","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/server","--arg=--port","--arg","8080","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","512","--hypervisor","mshv","--cmdline","quiet loglevel=3","--outcome-report","{root}/reports/openvmm-run.json","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block runtime:file:{root}/runtime.erofs,ro --microvm-sandbox-block custom:file:{root}/custom.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 1000:1000 --microvm-lifecycle one-shot --single-process --hypervisor mshv --memory 512M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'quiet loglevel=3 nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_layer=runtime,0xd0004000,22222222-2222-2222-2222-222222222222 nvx_layer=custom,0xd0005000,33333333-3333-3333-3333-333333333333 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/server nvx_hostname=worker nvx_arg=--port nvx_arg=8080 nvx_memory_max=268435456 nvx_pids_max=65' --microvm-report {root}/reports/openvmm-run.json\n","stderr":""}]}, +{"name":"dry run: explicit hypervisor","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--hypervisor","kvm","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"dry run: one share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}/share,ro --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"dry run: writable share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}/share,rw --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"dry run: file share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/etc/single.txt,{root}/single.txt,ro","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount-aggregate /run/nvx/shares --mount-child 0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root}/single.txt,ro,file --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file'\n","stderr":""}]}, +{"name":"dry run: several shares with policies","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--mount-deny","{root}/share/hidden","--mount-allow","{root}/share/hidden/shown","--mount-write","{root}/share/writable","--mount","/other,{root}/share2","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount-aggregate /run/nvx/shares --mount-child 0-bd47413b5c03dfc6e4b5b8143fe205ea,{root}/share,rw --mount-child 1-bf2faee2596026620b5cbc997c2d7d45,{root}/share2,ro --mount-deny {root}/share/hidden --mount-allow {root}/share/hidden/shown --mount-write {root}/share/writable --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,rw nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,ro'\n","stderr":""}]}, +{"name":"dry run: network","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-ingress","allow","--network-egress-allow","10.0.0.0/8:tcp:443","--network-egress-deny","10.1.0.0/16","--host-loopback","allow","--network-proxy","10.0.0.1:3128","--host-loopback-forward","8080","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox' --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress allow --network-egress-allow 10.0.0.0/8:tcp:443 --network-egress-deny 10.1.0.0/16 --host-loopback allow --network-proxy 10.0.0.1:3128 --host-loopback-forward 8080\n","stderr":""}]}, +{"name":"dry run: egress policy file","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--network-egress","deny","--network-egress-policy-file","{root}/policy.json","--dry-run"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\"]}, {\"cidr\": \"2001:db8::/32\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"198.51.100.0/24\", \"protocol\": \"udp\"}]}"},"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox' --network-egress deny --network-egress-allow 192.0.2.0/25 --network-egress-allow 2001:db8::/32:tcp:443 --network-egress-deny 198.51.100.0/24:udp\n","stderr":""}]}, +{"name":"dry run: quoted arguments","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--cmdline","a=\"b c\" d","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'a=\"b c\" d nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"dry run: caller-owned share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount-owner","caller","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}/share,ro --mount-owner caller --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"run: echo","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world'\nhello world\n","stderr":""}]}, +{"name":"run: default operation","steps":[{"argv":["sandbox","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world'\nhello world\n","stderr":""}]}, +{"name":"run: failure","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/false"],"status":1,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/false nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"run: missing program","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/missing"],"status":127,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/missing nvx_hostname=nvx-sandbox'\n","stderr":"openvmm: /bin/missing: not found\n"}]}, +{"name":"run: exit status","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/true","--cmdline","fake_exit_status=7"],"status":7,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'fake_exit_status=7 nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"run: outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world","--outcome-report","{root}/reports/openvmm-run.json"],"check":["reports/openvmm-run.json","build/fake-openvmm-reports.json"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world' --microvm-report {root}/reports/openvmm-run.json\nhello world\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"build/fake-openvmm-reports.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"one-shot\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\",\"--microvm-report\",\"{root}/reports/openvmm-run.json\"]"}}}]}, +{"name":"run: network outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/true","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-allow","10.0.0.0/8:tcp:443","--outcome-report","{root}/reports/openvmm-run.json"],"check":["reports/openvmm-run.json"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox' --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-egress-allow 10.0.0.0/8:tcp:443 --microvm-report {root}/reports/openvmm-run.json\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"}}}]}, +{"name":"run: OpenVMM refuses its options","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--host-loopback-forward","8080"],"status":2,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox' --host-loopback-forward 8080\n","stderr":"openvmm: --host-loopback-forward requires explicit --host-loopback allow\n"}]}, +{"name":"run: missing OpenVMM","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"remove":["openvmm/target/release/openvmm{exe}"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}/openvmm/target/release/openvmm\n"}]}, +{"name":"run: missing kernel","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"remove":["build/vmlinux"],"status":1,"stdout":"","stderr":"error: Linux direct kernel not found: {root}/build/vmlinux\n"}]}, +{"name":"run: missing initramfs","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"remove":["build/initramfs.cpio.gz"],"status":1,"stdout":"","stderr":"error: initramfs not found: {root}/build/initramfs.cpio.gz\n"}]}, +{"name":"lifecycle: minimal","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":"hello world\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/tmp"],"status":0,"stdout":"/tmp\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","echo out; echoerr err; exit 3","--outcome-report","{root}/reports/exec.json"],"check":["reports/exec.json"],"status":3,"stdout":"out\n","stderr":"err\n","files":{"reports/exec.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"exit\",\n \"operation\": \"exec\",\n \"status_code\": 3\n },\n \"schema_version\": 1\n}\n","mode":384}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment","A=1","--environment","B=two"],"status":0,"stdout":"A=1\nB=two\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/printenv","--arg","PATH","--inherit-default-environment","--environment","A=1"],"status":0,"stdout":"/usr/sbin:/usr/bin:/sbin:/bin\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment-file","{root}/env.json"],"write":{"env.json":"[\"X=1\", \"Y=two words\"]"},"status":0,"stdout":"X=1\nY=two words\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment-file","{root}/empty.json"],"write":{"empty.json":"[]"},"status":0,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","sleep 5000","--exec-timeout-ms","50","--outcome-report","{root}/reports/exec-timeout.json"],"check":["reports/exec-timeout.json"],"status":124,"stdout":"","stderr":"","files":{"reports/exec-timeout.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"timeout\",\n \"operation\": \"exec\",\n \"status_code\": 124\n },\n \"schema_version\": 1\n}\n","mode":384}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","signal 9"],"status":137,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/missing"],"status":127,"stdout":"","stderr":"openvmm: /bin/missing: not found\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/missing"],"status":1,"stdout":"","stderr":"error: managed guest rejected exec (status=2, category=cwd-failed)\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","relative"],"status":1,"stdout":"","stderr":"error: managed exec working directory must be an absolute path of at most 4096 bytes\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--outcome-report","{root}/reports/exec.json"],"status":1,"stdout":"","stderr":"error: outcome report already exists: {root}/reports/exec.json\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--outcome-report","{root}/missing/exec.json"],"status":1,"stdout":"","stderr":"error: outcome report parent is not a plain directory: {root}/missing\n"},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: one share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 2,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 2,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,ro\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/data"],"status":0,"stdout":"/data\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 2,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 2,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 2,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: caller-owned share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--mount-owner","caller"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 3,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 3,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,rw\",\"--mount-owner\",\"caller\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 3,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 3,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 3,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: several shares","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount","/other,{root}/share2,rw"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 4,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 4,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-bd47413b5c03dfc6e4b5b8143fe205ea,{root}/share,ro\",\"--mount-child\",\"1-bf2faee2596026620b5cbc997c2d7d45,{root}/share2,rw\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,ro nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,rw\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/other"],"status":0,"stdout":"/other\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 4,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 4,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 4,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: share policies","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--mount-deny","{root}/share/hidden","--mount-allow","{root}/share/hidden/shown","--mount-write","{root}/share/writable"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 5,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 5,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,rw\",\"--mount-deny\",\"{root}/share/hidden\",\"--mount-allow\",\"{root}/share/hidden/shown\",\"--mount-write\",\"{root}/share/writable\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 5,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 5,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 5,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: file share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/etc/single.txt,{root}/single.txt,ro"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 6,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 6,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root}/single.txt,ro,file\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 6,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 6,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 6,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: network and launch options","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--layer","runtime,{root}/runtime.erofs,22222222-2222-2222-2222-222222222222","--entrypoint","/bin/server","--arg=--serve","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","384","--hypervisor","auto","--cmdline","quiet","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-ingress","deny","--network-egress-policy-file","{root}/policy.json","--host-loopback","allow","--network-proxy","10.0.0.1:3128","--host-loopback-forward","8080","--host-loopback-forward","9090"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"192.0.2.128/25\"}]}"},"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"quiet\",\n \"format\": 1,\n \"host_loopback\": \"allow\",\n \"host_loopback_forward\": [\n \"8080\",\n \"9090\"\n ],\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n },\n {\n \"path\": \"{root}/runtime.erofs\",\n \"role\": \"runtime\",\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"net\": \"10.0.0.2/24\",\n \"network_egress\": \"deny\",\n \"network_egress_allow\": [\n \"192.0.2.0/24:tcp:443\"\n ],\n \"network_egress_deny\": [\n \"192.0.2.128/25\"\n ],\n \"network_ingress\": \"deny\",\n \"network_profile\": \"portable\",\n \"network_proxy\": \"10.0.0.1:3128\",\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"quiet\",\n \"format\": 1,\n \"host_loopback\": \"allow\",\n \"host_loopback_forward\": [\n \"8080\",\n \"9090\"\n ],\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n },\n {\n \"path\": \"{root}/runtime.erofs\",\n \"role\": \"runtime\",\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"net\": \"10.0.0.2/24\",\n \"network_egress\": \"deny\",\n \"network_egress_allow\": [\n \"192.0.2.0/24:tcp:443\"\n ],\n \"network_egress_deny\": [\n \"192.0.2.128/25\"\n ],\n \"network_ingress\": \"deny\",\n \"network_profile\": \"portable\",\n \"network_proxy\": \"10.0.0.1:3128\",\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"runtime:file:{root}/runtime.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"1000:1000\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"384M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_layer=runtime,0xd0004000,22222222-2222-2222-2222-222222222222 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=worker nvx_memory_max=268435456 nvx_pids_max=65\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"deny\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"allow\",\"--network-egress-allow\",\"192.0.2.0/24:tcp:443\",\"--network-egress-deny\",\"192.0.2.128/25\",\"--host-loopback-forward\",\"8080\",\"--host-loopback-forward\",\"9090\",\"--network-proxy\",\"10.0.0.1:3128\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"quiet\",\n \"format\": 1,\n \"host_loopback\": \"allow\",\n \"host_loopback_forward\": [\n \"8080\",\n \"9090\"\n ],\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n },\n {\n \"path\": \"{root}/runtime.erofs\",\n \"role\": \"runtime\",\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"net\": \"10.0.0.2/24\",\n \"network_egress\": \"deny\",\n \"network_egress_allow\": [\n \"192.0.2.0/24:tcp:443\"\n ],\n \"network_egress_deny\": [\n \"192.0.2.128/25\"\n ],\n \"network_ingress\": \"deny\",\n \"network_profile\": \"portable\",\n \"network_proxy\": \"10.0.0.1:3128\",\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","lifecycle.lock","openvmm.log","runtime.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"quiet\",\n \"format\": 1,\n \"host_loopback\": \"allow\",\n \"host_loopback_forward\": [\n \"8080\",\n \"9090\"\n ],\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n },\n {\n \"path\": \"{root}/runtime.erofs\",\n \"role\": \"runtime\",\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"net\": \"10.0.0.2/24\",\n \"network_egress\": \"deny\",\n \"network_egress_allow\": [\n \"192.0.2.0/24:tcp:443\"\n ],\n \"network_egress_deny\": [\n \"192.0.2.128/25\"\n ],\n \"network_ingress\": \"deny\",\n \"network_profile\": \"portable\",\n \"network_proxy\": \"10.0.0.1:3128\",\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"control_endpoint\": \"{root}/state/control.sock\",\n \"format\": 1,\n \"pid\": {int},\n \"start_time\": {int}\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"quiet\",\n \"format\": 1,\n \"host_loopback\": \"allow\",\n \"host_loopback_forward\": [\n \"8080\",\n \"9090\"\n ],\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n },\n {\n \"path\": \"{root}/runtime.erofs\",\n \"role\": \"runtime\",\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"net\": \"10.0.0.2/24\",\n \"network_egress\": \"deny\",\n \"network_egress_allow\": [\n \"192.0.2.0/24:tcp:443\"\n ],\n \"network_egress_deny\": [\n \"192.0.2.128/25\"\n ],\n \"network_ingress\": \"deny\",\n \"network_profile\": \"portable\",\n \"network_proxy\": \"10.0.0.1:3128\",\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: operations without a sandbox","steps":[{"argv":["sandbox","start","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"},{"argv":["sandbox","exec","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"}]}, +{"name":"state: refusals of a running sandbox","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox is already provisioned\n"},{"argv":["sandbox","start","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox is already running or has stale runtime state\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox must be stopped before deprovision\n"},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\n"},{"argv":["sandbox","exec","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: relative and nested state directory","steps":[{"argv":["sandbox","provision","--state-dir","nested/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"check":["nested/state","nested/state/config.json"],"status":0,"stdout":"","stderr":"","files":{"nested/state":{"entries":["config.json","lifecycle.lock"],"mode":448},"nested/state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384}}},{"argv":["sandbox","deprovision","--state-dir","nested/state"],"check":["nested/state"],"status":0,"stdout":"","stderr":"","files":{"nested/state":null}}]}, +{"name":"state: failed start","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--cmdline","fake_exit_on_start=5"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\n","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"fake_exit_on_start=5\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","start","--state-dir","{root}/state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\n","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"fake_exit_on_start=5\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\nopenvmm: failing the launch as requested\n"}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: start without OpenVMM","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state"],"remove":["openvmm/target/release/openvmm{exe}"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}/openvmm/target/release/openvmm\n","files":{"state":{"entries":["config.json","lifecycle.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"format\": 1,\n \"host_loopback\": null,\n \"host_loopback_forward\": [],\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"net\": null,\n \"network_egress\": null,\n \"network_egress_allow\": [],\n \"network_egress_deny\": [],\n \"network_ingress\": null,\n \"network_profile\": null,\n \"network_proxy\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""}]}, +{"name":"state: malformed configuration","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state"],"write":{"state/config.json":"{\"format\": 7}"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}/state/config.json\n"},{"argv":["sandbox","start","--state-dir","{root}/state"],"write":{"state/config.json":"[]"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}/state/config.json\n"},{"argv":["sandbox","start","--state-dir","{root}/state"],"write":{"state/config.json":"{\"format\": 1}"},"status":1,"stdout":"","stderr":"error: sandbox configuration is malformed\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, +{"name":"state: not a directory","steps":[{"argv":["sandbox","start","--state-dir","{root}/single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/single.txt\n"},{"argv":["sandbox","provision","--state-dir","{root}/single.txt","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: [Errno 17] File exists: '{root}/single.txt'\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/single.txt\n"}]}, +{"name":"state: foreign file","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"write":{"state/foreign.txt":"x"},"check":["state"],"status":1,"stdout":"","stderr":"error: sandbox state directory contains files not owned by NVX: foreign.txt\n","files":{"state":{"entries":["foreign.txt"],"mode":448}}}]} +], +"windows": [ +{"name":"egress compile: empty","steps":[{"argv":["egress","compile","{root}\\policy.json"],"write":{"policy.json":"{}"},"status":0,"stdout":"{\r\n \"allow\": [],\r\n \"deny\": []\r\n}\r\n","stderr":""}]}, +{"name":"egress compile: allow","steps":[{"argv":["egress","compile","{root}\\policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"}]}"},"status":0,"stdout":"{\r\n \"allow\": [\r\n \"192.0.2.0/24\"\r\n ],\r\n \"deny\": []\r\n}\r\n","stderr":""}]}, +{"name":"egress compile: both","steps":[{"argv":["egress","compile","{root}\\policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\"]}, {\"to\": [{\"cidr\": \"2001:db8::/32\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 80, \"endPort\": 81}]}], \"deny\": [{\"cidr\": \"198.51.100.0/24\", \"protocol\": \"icmp\"}]}"},"status":0,"stdout":"{\r\n \"allow\": [\r\n \"192.0.2.0/25\",\r\n \"2001:db8::/32:tcp:80-81\"\r\n ],\r\n \"deny\": [\r\n \"198.51.100.0/24:icmp\"\r\n ]\r\n}\r\n","stderr":""}]}, +{"name":"egress compile: invalid JSON","steps":[{"argv":["egress","compile","{root}\\policy.json"],"write":{"policy.json":"{"},"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}\\policy.json\r\n"}]}, +{"name":"egress compile: invalid rule","steps":[{"argv":["egress","compile","{root}\\policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"port\": 70000}]}"},"status":1,"stdout":"","stderr":"error: allow[0].port requires protocol\r\n"}]}, +{"name":"egress compile: unknown field","steps":[{"argv":["egress","compile","{root}\\policy.json"],"write":{"policy.json":"{\"allow\": [], \"other\": []}"},"status":1,"stdout":"","stderr":"error: egress policy has unknown field 'other'\r\n"}]}, +{"name":"egress compile: missing file","steps":[{"argv":["egress","compile","{root}\\missing.json"],"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}\\missing.json\r\n"}]}, +{"name":"egress compile: directory","steps":[{"argv":["egress","compile","{root}\\share"],"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}\\share\r\n"}]}, +{"name":"artifacts: checkout","steps":[{"argv":["artifacts"],"status":0,"stdout":"{\r\n \"guests\": [\r\n {\r\n \"distribution\": \"Alpine Linux\",\r\n \"distro_layer\": null,\r\n \"distro_manifest\": null,\r\n \"initramfs\": \"{root_json}\\\\build\\\\initramfs.cpio.gz\",\r\n \"name\": \"alpine\",\r\n \"package_manifest\": \"{root_json}\\\\build\\\\initramfs.cpio.gz.packages.json\"\r\n },\r\n {\r\n \"distribution\": \"Ubuntu Base\",\r\n \"distro_layer\": \"{root_json}\\\\build\\\\ubuntu-distro.erofs\",\r\n \"distro_manifest\": \"{root_json}\\\\build\\\\ubuntu-distro.erofs.manifest.json\",\r\n \"initramfs\": \"{root_json}\\\\build\\\\initramfs-ubuntu.cpio.gz\",\r\n \"name\": \"ubuntu\",\r\n \"package_manifest\": \"{root_json}\\\\build\\\\initramfs-ubuntu.cpio.gz.packages.json\"\r\n },\r\n {\r\n \"distribution\": \"Azure Linux\",\r\n \"distro_layer\": null,\r\n \"distro_manifest\": null,\r\n \"initramfs\": \"{root_json}\\\\build\\\\initramfs-azurelinux.cpio.gz\",\r\n \"name\": \"azurelinux\",\r\n \"package_manifest\": \"{root_json}\\\\build\\\\initramfs-azurelinux.cpio.gz.packages.json\"\r\n }\r\n ],\r\n \"kernel\": \"{root_json}\\\\build\\\\vmlinux\",\r\n \"layout\": \"repository\",\r\n \"openvmm\": \"{root_json}\\\\openvmm\\\\target\\\\release\\\\openvmm.exe\",\r\n \"root\": \"{root_json}\"\r\n}\r\n","stderr":""}]}, +{"name":"artifacts: OpenVMM override","steps":[{"argv":["artifacts"],"env":{"NVX_OPENVMM":"{root}\\alt\\openvmm{exe}"},"status":0,"stdout":"{\r\n \"guests\": [\r\n {\r\n \"distribution\": \"Alpine Linux\",\r\n \"distro_layer\": null,\r\n \"distro_manifest\": null,\r\n \"initramfs\": \"{root_json}\\\\build\\\\initramfs.cpio.gz\",\r\n \"name\": \"alpine\",\r\n \"package_manifest\": \"{root_json}\\\\build\\\\initramfs.cpio.gz.packages.json\"\r\n },\r\n {\r\n \"distribution\": \"Ubuntu Base\",\r\n \"distro_layer\": \"{root_json}\\\\build\\\\ubuntu-distro.erofs\",\r\n \"distro_manifest\": \"{root_json}\\\\build\\\\ubuntu-distro.erofs.manifest.json\",\r\n \"initramfs\": \"{root_json}\\\\build\\\\initramfs-ubuntu.cpio.gz\",\r\n \"name\": \"ubuntu\",\r\n \"package_manifest\": \"{root_json}\\\\build\\\\initramfs-ubuntu.cpio.gz.packages.json\"\r\n },\r\n {\r\n \"distribution\": \"Azure Linux\",\r\n \"distro_layer\": null,\r\n \"distro_manifest\": null,\r\n \"initramfs\": \"{root_json}\\\\build\\\\initramfs-azurelinux.cpio.gz\",\r\n \"name\": \"azurelinux\",\r\n \"package_manifest\": \"{root_json}\\\\build\\\\initramfs-azurelinux.cpio.gz.packages.json\"\r\n }\r\n ],\r\n \"kernel\": \"{root_json}\\\\build\\\\vmlinux\",\r\n \"layout\": \"repository\",\r\n \"openvmm\": \"{root_json}\\\\alt\\\\openvmm.exe\",\r\n \"root\": \"{root_json}\"\r\n}\r\n","stderr":""}]}, +{"name":"usage: help","steps":[{"argv":["sandbox","-h"],"status":0,"stdout":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\n\r\npositional arguments:\r\n {run,provision,start,exec,stop,deprovision}\r\n\r\noptions:\r\n -h, --help show this help message and exit\r\n --layer ROLE,PATH,EROFS_UUID\r\n --scratch SCRATCH\r\n --state-dir STATE_DIR\r\n --entrypoint ENTRYPOINT\r\n --arg SANDBOX_ARG\r\n --hostname HOSTNAME\r\n --workload-user UID:GID\r\n fixed non-root workload identity (default:\r\n 65534:65534)\r\n --memory-max MEMORY_MAX\r\n --pids-max PIDS_MAX\r\n --memory-mib MEMORY_MIB\r\n --timeout TIMEOUT control operation timeout, and the longest wait for\r\n another lifecycle transition of the sandbox, in\r\n seconds (default: 60)\r\n --exec-timeout-ms EXEC_TIMEOUT_MS\r\n guest workload timeout in milliseconds; zero disables\r\n it\r\n --cwd CWD absolute guest working directory for managed exec\r\n (default: /)\r\n --environment KEY=VALUE\r\n set the exact managed exec environment; repeat for\r\n multiple entries\r\n --environment-file PATH\r\n read the exact managed exec environment from a JSON\r\n string array\r\n --inherit-default-environment\r\n layer the managed exec environment over the guest\r\n default environment instead of replacing it\r\n --hypervisor {auto,whp,kvm,mshv}\r\n --mount GUEST_TARGET,HOST_PATH[,ro|rw]\r\n live-share a host directory, or a single regular host\r\n file without its directory, inside the container\r\n rootfs; the mode follows the last comma, so HOST_PATH\r\n may contain commas when the mode is given; repeat to\r\n attach more shares, each with its own target and mode,\r\n as children of one virtio-fs device\r\n --mount-deny HOST_PATH\r\n hide one existing path inside a --mount host\r\n directory; with several --mount options, it applies to\r\n the --mount before it\r\n --mount-allow HOST_PATH\r\n expose one existing path inside a --mount-deny path of\r\n the same share again; the hidden directories on the\r\n way list only it\r\n --mount-write HOST_PATH\r\n make one existing path one of the only writable parts\r\n of a read-write --mount; the rest of the share is\r\n read-only\r\n --mount-owner {vmm,caller}\r\n host identity of the share's file operations: vmm\r\n (default) or caller, the workload identity with guest\r\n root squashed to the owner of the shared directory or\r\n file (Linux only)\r\n --net IPV4/PREFIX\r\n --network-profile {portable}\r\n --network-egress {allow,deny}\r\n --network-ingress {allow,deny}\r\n --network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]\r\n allow guest egress to an IPv4 or IPv6 CIDR, optionally\r\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\r\n udp only on PORT or on the inclusive range PORT-PORT;\r\n repeat to add rules\r\n --network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]\r\n deny guest egress to an IPv4 or IPv6 CIDR, optionally\r\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\r\n udp only on PORT or on the inclusive range PORT-PORT;\r\n repeat to add rules\r\n --network-egress-policy-file PATH\r\n load bounded IPv4 and IPv6 ranges and rule-local\r\n exclusions for run/provision\r\n --host-loopback {allow,deny}\r\n --network-proxy IPV4:TCP-PORT\r\n --host-loopback-forward HOST_LOOPBACK_FORWARD\r\n --outcome-report OUTCOME_REPORT\r\n write a bounded local JSON outcome report for run or\r\n exec\r\n --cmdline CMDLINE\r\n --dry-run print the one-shot run command without launching it\r\n","stderr":""}]}, +{"name":"usage: help after operation","steps":[{"argv":["sandbox","exec","--help"],"status":0,"stdout":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\n\r\npositional arguments:\r\n {run,provision,start,exec,stop,deprovision}\r\n\r\noptions:\r\n -h, --help show this help message and exit\r\n --layer ROLE,PATH,EROFS_UUID\r\n --scratch SCRATCH\r\n --state-dir STATE_DIR\r\n --entrypoint ENTRYPOINT\r\n --arg SANDBOX_ARG\r\n --hostname HOSTNAME\r\n --workload-user UID:GID\r\n fixed non-root workload identity (default:\r\n 65534:65534)\r\n --memory-max MEMORY_MAX\r\n --pids-max PIDS_MAX\r\n --memory-mib MEMORY_MIB\r\n --timeout TIMEOUT control operation timeout, and the longest wait for\r\n another lifecycle transition of the sandbox, in\r\n seconds (default: 60)\r\n --exec-timeout-ms EXEC_TIMEOUT_MS\r\n guest workload timeout in milliseconds; zero disables\r\n it\r\n --cwd CWD absolute guest working directory for managed exec\r\n (default: /)\r\n --environment KEY=VALUE\r\n set the exact managed exec environment; repeat for\r\n multiple entries\r\n --environment-file PATH\r\n read the exact managed exec environment from a JSON\r\n string array\r\n --inherit-default-environment\r\n layer the managed exec environment over the guest\r\n default environment instead of replacing it\r\n --hypervisor {auto,whp,kvm,mshv}\r\n --mount GUEST_TARGET,HOST_PATH[,ro|rw]\r\n live-share a host directory, or a single regular host\r\n file without its directory, inside the container\r\n rootfs; the mode follows the last comma, so HOST_PATH\r\n may contain commas when the mode is given; repeat to\r\n attach more shares, each with its own target and mode,\r\n as children of one virtio-fs device\r\n --mount-deny HOST_PATH\r\n hide one existing path inside a --mount host\r\n directory; with several --mount options, it applies to\r\n the --mount before it\r\n --mount-allow HOST_PATH\r\n expose one existing path inside a --mount-deny path of\r\n the same share again; the hidden directories on the\r\n way list only it\r\n --mount-write HOST_PATH\r\n make one existing path one of the only writable parts\r\n of a read-write --mount; the rest of the share is\r\n read-only\r\n --mount-owner {vmm,caller}\r\n host identity of the share's file operations: vmm\r\n (default) or caller, the workload identity with guest\r\n root squashed to the owner of the shared directory or\r\n file (Linux only)\r\n --net IPV4/PREFIX\r\n --network-profile {portable}\r\n --network-egress {allow,deny}\r\n --network-ingress {allow,deny}\r\n --network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]\r\n allow guest egress to an IPv4 or IPv6 CIDR, optionally\r\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\r\n udp only on PORT or on the inclusive range PORT-PORT;\r\n repeat to add rules\r\n --network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]\r\n deny guest egress to an IPv4 or IPv6 CIDR, optionally\r\n only for PROTOCOL tcp, udp, or icmp, and for tcp or\r\n udp only on PORT or on the inclusive range PORT-PORT;\r\n repeat to add rules\r\n --network-egress-policy-file PATH\r\n load bounded IPv4 and IPv6 ranges and rule-local\r\n exclusions for run/provision\r\n --host-loopback {allow,deny}\r\n --network-proxy IPV4:TCP-PORT\r\n --host-loopback-forward HOST_LOOPBACK_FORWARD\r\n --outcome-report OUTCOME_REPORT\r\n write a bounded local JSON outcome report for run or\r\n exec\r\n --cmdline CMDLINE\r\n --dry-run print the one-shot run command without launching it\r\n","stderr":""}]}, +{"name":"usage: timeout not a number","steps":[{"argv":["sandbox","--timeout","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --timeout: invalid positive_float value: 'x'\r\n"}]}, +{"name":"usage: zero timeout","steps":[{"argv":["sandbox","--timeout","0"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\r\n"}]}, +{"name":"usage: negative timeout","steps":[{"argv":["sandbox","--timeout","-1"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\r\n"}]}, +{"name":"usage: infinite timeout","steps":[{"argv":["sandbox","--timeout","inf"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\r\n"}]}, +{"name":"usage: timeout not finite","steps":[{"argv":["sandbox","--timeout=1e400"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\r\n"}]}, +{"name":"usage: timeout not a number either","steps":[{"argv":["sandbox","--timeout","nan"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --timeout: must be finite and greater than 0\r\n"}]}, +{"name":"usage: exec timeout not an integer","steps":[{"argv":["sandbox","--exec-timeout-ms","1.5"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --exec-timeout-ms: invalid int value: '1.5'\r\n"}]}, +{"name":"usage: memory not an integer","steps":[{"argv":["sandbox","--memory-mib","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --memory-mib: invalid int value: 'x'\r\n"}]}, +{"name":"usage: invalid hypervisor","steps":[{"argv":["sandbox","--hypervisor","hyperv"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --hypervisor: invalid choice: 'hyperv' (choose from 'auto', 'whp', 'kvm', 'mshv')\r\n"}]}, +{"name":"usage: invalid operation","steps":[{"argv":["sandbox","restart"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument sandbox_operation: invalid choice: 'restart' (choose from 'run', 'provision', 'start', 'exec', 'stop', 'deprovision')\r\n"}]}, +{"name":"usage: second operation","steps":[{"argv":["sandbox","run","start"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\r\nnvx-rs: error: unrecognized arguments: start\r\n"}]}, +{"name":"usage: invalid mount owner","steps":[{"argv":["sandbox","--mount-owner","root"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --mount-owner: invalid choice: 'root' (choose from 'vmm', 'caller')\r\n"}]}, +{"name":"usage: invalid network profile","steps":[{"argv":["sandbox","--network-profile","open"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --network-profile: invalid choice: 'open' (choose from 'portable')\r\n"}]}, +{"name":"usage: invalid egress default","steps":[{"argv":["sandbox","--network-egress","block"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --network-egress: invalid choice: 'block' (choose from 'allow', 'deny')\r\n"}]}, +{"name":"usage: invalid host loopback","steps":[{"argv":["sandbox","--host-loopback","yes"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --host-loopback: invalid choice: 'yes' (choose from 'allow', 'deny')\r\n"}]}, +{"name":"usage: missing value","steps":[{"argv":["sandbox","--layer"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --layer: expected one argument\r\n"}]}, +{"name":"usage: option as value","steps":[{"argv":["sandbox","--layer","--scratch"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --layer: expected one argument\r\n"}]}, +{"name":"usage: dash value","steps":[{"argv":["sandbox","--arg","-x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --arg: expected one argument\r\n"}]}, +{"name":"usage: flag with value","steps":[{"argv":["sandbox","--dry-run=1"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --dry-run: ignored explicit argument '1'\r\n"}]}, +{"name":"usage: unrecognized option","steps":[{"argv":["sandbox","--bogus"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\r\nnvx-rs: error: unrecognized arguments: --bogus\r\n"}]}, +{"name":"usage: unrecognized short option","steps":[{"argv":["sandbox","-x"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\r\nnvx-rs: error: unrecognized arguments: -x\r\n"}]}, +{"name":"usage: unrecognized after separator","steps":[{"argv":["sandbox","run","--","extra"],"status":2,"stdout":"","stderr":"usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ...\r\nnvx-rs: error: unrecognized arguments: extra\r\n"}]}, +{"name":"validation: dry run outside run","steps":[{"argv":["sandbox","provision","--dry-run"],"status":1,"stdout":"","stderr":"error: --dry-run is only valid for sandbox run\r\n"}]}, +{"name":"validation: environment and environment file","steps":[{"argv":["sandbox","exec","--environment","A=1","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"A=1\"]"},"status":1,"stdout":"","stderr":"error: --environment and --environment-file are mutually exclusive\r\n"}]}, +{"name":"validation: cwd outside exec","steps":[{"argv":["sandbox","run","--cwd","/"],"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\r\n"}]}, +{"name":"validation: environment outside exec","steps":[{"argv":["sandbox","start","--environment","A=1"],"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\r\n"}]}, +{"name":"validation: environment file outside exec","steps":[{"argv":["sandbox","provision","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"A=1\"]"},"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\r\n"}]}, +{"name":"validation: inherited environment outside exec","steps":[{"argv":["sandbox","stop","--inherit-default-environment"],"status":1,"stdout":"","stderr":"error: managed execution options require the sandbox exec operation\r\n"}]}, +{"name":"validation: environment file too large","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":{"fill":" ","size":1048577}},"status":1,"stdout":"","stderr":"error: managed execution environment file exceeds 1048576-byte limit\r\n"}]}, +{"name":"validation: environment file at the limit","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":{"text":"[\"A=1\"]","fill":" ","size":1048576}},"status":1,"stdout":"","stderr":"error: sandbox exec requires --state-dir\r\n"}]}, +{"name":"validation: environment file missing","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\missing.json"],"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}\\missing.json\r\n"}]}, +{"name":"validation: environment file directory","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\share"],"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}\\share\r\n"}]}, +{"name":"validation: environment file not UTF-8","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":{"hex":"5b22ff225d"}},"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}\\env.json\r\n"}]}, +{"name":"validation: environment file not JSON","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"A=1\""},"status":1,"stdout":"","stderr":"error: failed to read managed execution environment: {root}\\env.json\r\n"}]}, +{"name":"validation: environment file object","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":"{\"A\": \"1\"}"},"status":1,"stdout":"","stderr":"error: managed execution environment file must contain a JSON array of KEY=VALUE strings\r\n"}]}, +{"name":"validation: environment file non-string","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"A=1\", 2]"},"status":1,"stdout":"","stderr":"error: managed execution environment file must contain a JSON array of KEY=VALUE strings\r\n"}]}, +{"name":"validation: environment file entry without equals","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"A\"]"},"status":1,"stdout":"","stderr":"error: managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes\r\n"}]}, +{"name":"validation: environment file entry with NUL","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"A=\\u0000\"]"},"status":1,"stdout":"","stderr":"error: managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes\r\n"}]}, +{"name":"validation: environment file duplicate entry","steps":[{"argv":["sandbox","exec","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"A=1\", \"A=2\"]"},"status":1,"stdout":"","stderr":"error: managed exec environment contains duplicate key: A\r\n"}]}, +{"name":"validation: environment entry without name","steps":[{"argv":["sandbox","exec","--environment","=x"],"status":1,"stdout":"","stderr":"error: managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes\r\n"}]}, +{"name":"validation: environment entry with space","steps":[{"argv":["sandbox","exec","--environment","A B=x"],"status":1,"stdout":"","stderr":"error: sandbox exec requires --state-dir\r\n"}]}, +{"name":"validation: network outside run","steps":[{"argv":["sandbox","start","--net","10.0.0.2/24"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\r\n"}]}, +{"name":"validation: egress rule outside run","steps":[{"argv":["sandbox","exec","--network-egress-allow","10.0.0.0/8"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\r\n"}]}, +{"name":"validation: forward outside run","steps":[{"argv":["sandbox","stop","--host-loopback-forward","80"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\r\n"}]}, +{"name":"validation: policy file outside run","steps":[{"argv":["sandbox","deprovision","--network-egress-policy-file","{root}\\policy.json"],"status":1,"stdout":"","stderr":"error: network policy options are only valid for sandbox run or provision\r\n"}]}, +{"name":"validation: systemd entrypoint of run","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/usr/lib/systemd/systemd"],"status":1,"stdout":"","stderr":"error: systemd entrypoints are unsupported by the sandbox security profile\r\n"}]}, +{"name":"validation: systemd entrypoint of exec","steps":[{"argv":["sandbox","exec","--entrypoint","/lib/systemd/systemd"],"status":1,"stdout":"","stderr":"error: systemd entrypoints are unsupported by the sandbox security profile\r\n"}]}, +{"name":"validation: outcome report outside run","steps":[{"argv":["sandbox","provision","--outcome-report","{root}\\reports\\exec.json"],"status":1,"stdout":"","stderr":"error: --outcome-report is only valid for one-shot run or managed exec\r\n"}]}, +{"name":"validation: deny without mount","steps":[{"argv":["sandbox","run","--mount-deny","{root}\\share\\hidden"],"status":1,"stdout":"","stderr":"error: --mount-deny requires --mount\r\n"}]}, +{"name":"validation: allow without mount","steps":[{"argv":["sandbox","run","--mount-allow","{root}\\share\\hidden"],"status":1,"stdout":"","stderr":"error: --mount-allow requires --mount\r\n"}]}, +{"name":"validation: write without mount","steps":[{"argv":["sandbox","run","--mount-write","{root}\\share\\writable"],"status":1,"stdout":"","stderr":"error: --mount-write requires --mount\r\n"}]}, +{"name":"validation: owner without mount","steps":[{"argv":["sandbox","run","--mount-owner","vmm"],"status":1,"stdout":"","stderr":"error: --mount-owner requires --mount\r\n"}]}, +{"name":"validation: mount outside run","steps":[{"argv":["sandbox","start","--mount","/data,{root}\\share"],"status":1,"stdout":"","stderr":"error: --mount is only valid for sandbox run or provision\r\n"}]}, +{"name":"validation: net without profile","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--net","10.0.0.2/24"],"status":1,"stdout":"","stderr":"error: --net and --network-profile must be specified together\r\n"}]}, +{"name":"validation: profile without net","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--network-profile","portable"],"status":1,"stdout":"","stderr":"error: --net and --network-profile must be specified together\r\n"}]}, +{"name":"validation: run without layers","steps":[{"argv":["sandbox","run"],"status":1,"stdout":"","stderr":"error: sandbox run requires --layer and --scratch\r\n"}]}, +{"name":"validation: provision without scratch","steps":[{"argv":["sandbox","provision","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111"],"status":1,"stdout":"","stderr":"error: sandbox provision requires --layer and --scratch\r\n"}]}, +{"name":"validation: run without layer","steps":[{"argv":["sandbox","run","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox run requires --layer and --scratch\r\n"}]}, +{"name":"validation: policy file with rules","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--network-egress","allow","--network-egress-policy-file","{root}\\policy.json","--network-egress-deny","10.0.0.0/8"],"status":1,"stdout":"","stderr":"error: --network-egress-policy-file cannot be combined with --network-egress-allow or --network-egress-deny\r\n"}]}, +{"name":"validation: policy file without egress","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--network-egress-policy-file","{root}\\policy.json"],"status":1,"stdout":"","stderr":"error: --network-egress is required with --network-egress-policy-file\r\n"}]}, +{"name":"validation: policy file missing","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--network-egress","deny","--network-egress-policy-file","{root}\\missing.json"],"status":1,"stdout":"","stderr":"error: failed to read egress policy file: {root}\\missing.json\r\n"}]}, +{"name":"validation: policy file invalid","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--network-egress","deny","--network-egress-policy-file","{root}\\policy.json"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"10.0.0.0/8\", \"port\": 0}]}"},"status":1,"stdout":"","stderr":"error: allow[0].port requires protocol\r\n"}]}, +{"name":"validation: provision without state directory","steps":[{"argv":["sandbox","provision","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox provision requires --state-dir\r\n"}]}, +{"name":"validation: start without state directory","steps":[{"argv":["sandbox","start"],"status":1,"stdout":"","stderr":"error: sandbox start requires --state-dir\r\n"}]}, +{"name":"validation: exec without state directory","steps":[{"argv":["sandbox","exec"],"status":1,"stdout":"","stderr":"error: sandbox exec requires --state-dir\r\n"}]}, +{"name":"validation: stop without state directory","steps":[{"argv":["sandbox","stop"],"status":1,"stdout":"","stderr":"error: sandbox stop requires --state-dir\r\n"}]}, +{"name":"validation: deprovision without state directory","steps":[{"argv":["sandbox","deprovision"],"status":1,"stdout":"","stderr":"error: sandbox deprovision requires --state-dir\r\n"}]}, +{"name":"validation: run with state directory","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: one-shot sandbox execution rejects persistent --state-dir settings\r\n"}]}, +{"name":"validation: invalid layer UUID","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,nope","--scratch","{root}\\scratch.ext4"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --layer: distro layer UUID is invalid: 'nope'\r\n"}]}, +{"name":"validation: invalid layer role","steps":[{"argv":["sandbox","run","--layer","base,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --layer: unsupported layer role 'base'; choose distro, runtime, custom\r\n"}]}, +{"name":"validation: missing layer","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\missing.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: distro EROFS layer not found: {root}\\missing.erofs\r\n"}]}, +{"name":"validation: duplicate layer","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: duplicate sandbox layer role(s): distro\r\n"}]}, +{"name":"validation: missing scratch","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\missing.ext4"],"status":1,"stdout":"","stderr":"error: ext4 scratch image not found: {root}\\missing.ext4\r\n"}]}, +{"name":"validation: relative entrypoint","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","bin/sh"],"status":1,"stdout":"","stderr":"error: sandbox entrypoint must be an absolute path without spaces\r\n"}]}, +{"name":"validation: invalid hostname","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--hostname","bad host"],"status":1,"stdout":"","stderr":"error: sandbox hostname must be a lowercase RFC 1123 label up to 63 characters\r\n"}]}, +{"name":"validation: argument with space","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--arg","a b"],"status":1,"stdout":"","stderr":"error: sandbox arguments must be nonempty and contain no spaces\r\n"}]}, +{"name":"validation: root workload","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--workload-user","0:0"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --workload-user: --workload-user UID must be between 1 and 4294967295\r\n"}]}, +{"name":"validation: invalid workload user","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--workload-user","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --workload-user: --workload-user must be UID:GID\r\n"}]}, +{"name":"validation: invalid memory limit","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--memory-max","x"],"status":2,"stdout":"","stderr":"usage: nvx-rs sandbox [-h] [--layer ROLE,PATH,EROFS_UUID] [--scratch SCRATCH]\r\n [--state-dir STATE_DIR] [--entrypoint ENTRYPOINT]\r\n [--arg SANDBOX_ARG] [--hostname HOSTNAME]\r\n [--workload-user UID:GID] [--memory-max MEMORY_MAX]\r\n [--pids-max PIDS_MAX] [--memory-mib MEMORY_MIB]\r\n [--timeout TIMEOUT] [--exec-timeout-ms EXEC_TIMEOUT_MS]\r\n [--cwd CWD] [--environment KEY=VALUE]\r\n [--environment-file PATH]\r\n [--inherit-default-environment]\r\n [--hypervisor {auto,whp,kvm,mshv}]\r\n [--mount GUEST_TARGET,HOST_PATH[,ro|rw]]\r\n [--mount-deny HOST_PATH] [--mount-allow HOST_PATH]\r\n [--mount-write HOST_PATH] [--mount-owner {vmm,caller}]\r\n [--net IPV4/PREFIX] [--network-profile {portable}]\r\n [--network-egress {allow,deny}]\r\n [--network-ingress {allow,deny}]\r\n [--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]\r\n [--network-egress-policy-file PATH]\r\n [--host-loopback {allow,deny}]\r\n [--network-proxy IPV4:TCP-PORT]\r\n [--host-loopback-forward HOST_LOOPBACK_FORWARD]\r\n [--outcome-report OUTCOME_REPORT] [--cmdline CMDLINE]\r\n [--dry-run]\r\n [{run,provision,start,exec,stop,deprovision}]\r\nnvx-rs sandbox: error: argument --memory-max: invalid int value: 'x'\r\n"}]}, +{"name":"validation: invalid process limit","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--pids-max","0"],"status":1,"stdout":"","stderr":"error: --pids-max must be positive\r\n"}]}, +{"name":"validation: relative mount target","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","data,{root}\\share"],"status":1,"stdout":"","stderr":"error: invalid sandbox mount target 'data': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','\r\n"}]}, +{"name":"validation: missing mount host","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\missing"],"status":1,"stdout":"","stderr":"error: sandbox mount host path is not a plain directory: {root}\\missing\r\n"}]}, +{"name":"validation: invalid mount mode","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,rx"],"status":1,"stdout":"","stderr":"error: unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)\r\n"}]}, +{"name":"validation: deny outside share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount-deny","{root}\\share2"],"status":2,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}\\share,ro --mount-deny {root}\\share2 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":"openvmm: invalid --mount-deny {root}\\share2\n"}]}, +{"name":"validation: allow outside denied path","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount-allow","{root}\\share\\hidden\\shown"],"status":1,"stdout":"","stderr":"error: --mount-allow exposes a path inside a --mount-deny path of the same share, which has none\r\n"}]}, +{"name":"validation: write in read-only share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,ro","--mount-write","{root}\\share\\writable"],"status":1,"stdout":"","stderr":"error: --mount-write narrows the writes of a read-write share; --mount /data is read-only\r\n"}]}, +{"name":"validation: reserved command-line token","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--cmdline","nvx_sandbox=0"],"status":1,"stdout":"","stderr":"error: nvx_sandbox is owned by the sandbox profile\r\n"}]}, +{"name":"validation: systemd distro image","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"write":{"distro.erofs.manifest.json":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"8a475efd5fd711d481a9a19de6d12b88dff0cd81cb8135ffcd5b65b062c7bf04\", \"packages\": [{\"name\": \"busybox\", \"version\": \"1\"}, {\"name\": \"systemd\", \"version\": \"1\"}]}"},"status":1,"stdout":"","stderr":"error: systemd images are unsupported by the sandbox security profile\r\n"}]}, +{"name":"validation: mismatched distro manifest","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"write":{"distro.erofs.manifest.json":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0000000000000000000000000000000000000000000000000000000000000000\", \"packages\": []}"},"status":1,"stdout":"","stderr":"error: sandbox distro manifest does not match its artifact\r\n"}]}, +{"name":"validation: invalid distro manifest","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"write":{"distro.erofs.manifest.json":"{"},"status":1,"stdout":"","stderr":"error: invalid sandbox distro manifest: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)\r\n"}]}, +{"name":"validation: distro manifest without packages","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"write":{"distro.erofs.manifest.json":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"8a475efd5fd711d481a9a19de6d12b88dff0cd81cb8135ffcd5b65b062c7bf04\"}"},"status":1,"stdout":"","stderr":"error: sandbox distro manifest has invalid package metadata\r\n"}]}, +{"name":"validation: caller-owned share on Windows","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount-owner","caller"],"status":1,"stdout":"","stderr":"error: --mount-owner caller requires a Linux host; Windows has no per-request POSIX identity for OpenVMM to switch to\r\n"}]}, +{"name":"dry run: minimal","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"dry run: every layer and option","steps":[{"argv":["sandbox","run","--layer","custom,{root}\\custom.erofs,33333333-3333-3333-3333-333333333333","--layer","runtime,{root}\\runtime.erofs,22222222-2222-2222-2222-222222222222","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/server","--arg=--port","--arg","8080","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","512","--hypervisor","mshv","--cmdline","quiet loglevel=3","--outcome-report","{root}\\reports\\openvmm-run.json","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block runtime:file:{root}\\runtime.erofs,ro --microvm-sandbox-block custom:file:{root}\\custom.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 1000:1000 --microvm-lifecycle one-shot --single-process --hypervisor mshv --memory 512M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"quiet loglevel=3 nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_layer=runtime,0xd0004000,22222222-2222-2222-2222-222222222222 nvx_layer=custom,0xd0005000,33333333-3333-3333-3333-333333333333 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/server nvx_hostname=worker nvx_arg=--port nvx_arg=8080 nvx_memory_max=268435456 nvx_pids_max=65\" --microvm-report {root}\\reports\\openvmm-run.json\r\n","stderr":""}]}, +{"name":"dry run: explicit hypervisor","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--hypervisor","kvm","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"dry run: one share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}\\share,ro --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"dry run: writable share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,rw","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}\\share,rw --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"dry run: file share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/etc/single.txt,{root}\\single.txt,ro","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --mount-aggregate /run/nvx/shares --mount-child 0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root}\\single.txt,ro,file --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file\"\r\n","stderr":""}]}, +{"name":"dry run: several shares with policies","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,rw","--mount-deny","{root}\\share\\hidden","--mount-allow","{root}\\share\\hidden\\shown","--mount-write","{root}\\share\\writable","--mount","/other,{root}\\share2","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --mount-aggregate /run/nvx/shares --mount-child 0-bd47413b5c03dfc6e4b5b8143fe205ea,{root}\\share,rw --mount-child 1-bf2faee2596026620b5cbc997c2d7d45,{root}\\share2,ro --mount-deny {root}\\share\\hidden --mount-allow {root}\\share\\hidden\\shown --mount-write {root}\\share\\writable --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,rw nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,ro\"\r\n","stderr":""}]}, +{"name":"dry run: network","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-ingress","allow","--network-egress-allow","10.0.0.0/8:tcp:443","--network-egress-deny","10.1.0.0/16","--host-loopback","allow","--network-proxy","10.0.0.1:3128","--host-loopback-forward","8080","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\" --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress allow --network-egress-allow 10.0.0.0/8:tcp:443 --network-egress-deny 10.1.0.0/16 --host-loopback allow --network-proxy 10.0.0.1:3128 --host-loopback-forward 8080\r\n","stderr":""}]}, +{"name":"dry run: egress policy file","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--network-egress","deny","--network-egress-policy-file","{root}\\policy.json","--dry-run"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\"]}, {\"cidr\": \"2001:db8::/32\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"198.51.100.0/24\", \"protocol\": \"udp\"}]}"},"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\" --network-egress deny --network-egress-allow 192.0.2.0/25 --network-egress-allow 2001:db8::/32:tcp:443 --network-egress-deny 198.51.100.0/24:udp\r\n","stderr":""}]}, +{"name":"dry run: quoted arguments","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--cmdline","a=\"b c\" d","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"a=\\\"b c\\\" d nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"run: echo","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\"\r\nhello world\n","stderr":""}]}, +{"name":"run: default operation","steps":[{"argv":["sandbox","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\"\r\nhello world\n","stderr":""}]}, +{"name":"run: failure","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/false"],"status":1,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/false nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"run: missing program","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/missing"],"status":127,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/missing nvx_hostname=nvx-sandbox\"\r\n","stderr":"openvmm: /bin/missing: not found\n"}]}, +{"name":"run: exit status","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/true","--cmdline","fake_exit_status=7"],"status":7,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"fake_exit_status=7 nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"run: outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world","--outcome-report","{root}\\reports\\openvmm-run.json"],"check":["reports/openvmm-run.json","build/fake-openvmm-reports.json"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\" --microvm-report {root}\\reports\\openvmm-run.json\r\nhello world\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"build/fake-openvmm-reports.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"one-shot\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\",\"--microvm-report\",\"{root_json}\\\\reports\\\\openvmm-run.json\"]"}}}]}, +{"name":"run: network outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/true","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-allow","10.0.0.0/8:tcp:443","--outcome-report","{root}\\reports\\openvmm-run.json"],"check":["reports/openvmm-run.json"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox\" --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-egress-allow 10.0.0.0/8:tcp:443 --microvm-report {root}\\reports\\openvmm-run.json\r\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"}}}]}, +{"name":"run: OpenVMM refuses its options","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--host-loopback-forward","8080"],"status":2,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\" --host-loopback-forward 8080\r\n","stderr":"openvmm: --host-loopback-forward requires explicit --host-loopback allow\n"}]}, +{"name":"run: missing OpenVMM","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"remove":["openvmm/target/release/openvmm{exe}"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}\\openvmm\\target\\release\\openvmm.exe\r\n"}]}, +{"name":"run: missing kernel","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"remove":["build/vmlinux"],"status":1,"stdout":"","stderr":"error: Linux direct kernel not found: {root}\\build\\vmlinux\r\n"}]}, +{"name":"run: missing initramfs","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"remove":["build/initramfs.cpio.gz"],"status":1,"stdout":"","stderr":"error: initramfs not found: {root}\\build\\initramfs.cpio.gz\r\n"}]}, +{"name":"lifecycle: minimal","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":"hello world\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/tmp"],"status":0,"stdout":"/tmp\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","echo out; echoerr err; exit 3","--outcome-report","{root}\\reports\\exec.json"],"check":["reports/exec.json"],"status":3,"stdout":"out\n","stderr":"err\n","files":{"reports/exec.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"exit\",\n \"operation\": \"exec\",\n \"status_code\": 3\n },\n \"schema_version\": 1\n}\n"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment","A=1","--environment","B=two"],"status":0,"stdout":"A=1\nB=two\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/printenv","--arg","PATH","--inherit-default-environment","--environment","A=1"],"status":0,"stdout":"/usr/sbin:/usr/bin:/sbin:/bin\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"X=1\", \"Y=two words\"]"},"status":0,"stdout":"X=1\nY=two words\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment-file","{root}\\empty.json"],"write":{"empty.json":"[]"},"status":0,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","sleep 5000","--exec-timeout-ms","50","--outcome-report","{root}\\reports\\exec-timeout.json"],"check":["reports/exec-timeout.json"],"status":124,"stdout":"","stderr":"","files":{"reports/exec-timeout.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"timeout\",\n \"operation\": \"exec\",\n \"status_code\": 124\n },\n \"schema_version\": 1\n}\n"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","signal 9"],"status":137,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/missing"],"status":127,"stdout":"","stderr":"openvmm: /bin/missing: not found\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/missing"],"status":1,"stdout":"","stderr":"error: managed guest rejected exec (status=2, category=cwd-failed)\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","relative"],"status":1,"stdout":"","stderr":"error: managed exec working directory must be an absolute path of at most 4096 bytes\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--outcome-report","{root}\\reports\\exec.json"],"status":1,"stdout":"","stderr":"error: outcome report already exists: {root}\\reports\\exec.json\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--outcome-report","{root}\\missing\\exec.json"],"status":1,"stdout":"","stderr":"error: outcome report parent is not a plain directory: {root}\\missing\r\n"},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: one share","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 2,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 2,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root_json}\\\\share,ro\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/data"],"status":0,"stdout":"/data\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 2,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 2,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 2,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: several shares","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount","/other,{root}\\share2,rw"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 4,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 4,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-bd47413b5c03dfc6e4b5b8143fe205ea,{root_json}\\\\share,ro\",\"--mount-child\",\"1-bf2faee2596026620b5cbc997c2d7d45,{root_json}\\\\share2,rw\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,ro nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,rw\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/other"],"status":0,"stdout":"/other\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 4,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 4,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 4,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: share policies","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,rw","--mount-deny","{root}\\share\\hidden","--mount-allow","{root}\\share\\hidden\\shown","--mount-write","{root}\\share\\writable"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 5,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 5,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root_json}\\\\share,rw\",\"--mount-deny\",\"{root_json}\\\\share\\\\hidden\",\"--mount-allow\",\"{root_json}\\\\share\\\\hidden\\\\shown\",\"--mount-write\",\"{root_json}\\\\share\\\\writable\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 5,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 5,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 5,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: file share","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/etc/single.txt,{root}\\single.txt,ro"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 6,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 6,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root_json}\\\\single.txt,ro,file\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 6,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 6,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 6,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: network and launch options","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--layer","runtime,{root}\\runtime.erofs,22222222-2222-2222-2222-222222222222","--entrypoint","/bin/server","--arg=--serve","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","384","--hypervisor","auto","--cmdline","quiet","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-ingress","deny","--network-egress-policy-file","{root}\\policy.json","--host-loopback","allow","--network-proxy","10.0.0.1:3128","--host-loopback-forward","8080","--host-loopback-forward","9090"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"192.0.2.128/25\"}]}"},"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"format\": 1,\r\n \"host_loopback\": \"allow\",\r\n \"host_loopback_forward\": [\r\n \"8080\",\r\n \"9090\"\r\n ],\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n },\r\n {\r\n \"path\": \"{root_json}\\\\runtime.erofs\",\r\n \"role\": \"runtime\",\r\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"net\": \"10.0.0.2/24\",\r\n \"network_egress\": \"deny\",\r\n \"network_egress_allow\": [\r\n \"192.0.2.0/24:tcp:443\"\r\n ],\r\n \"network_egress_deny\": [\r\n \"192.0.2.128/25\"\r\n ],\r\n \"network_ingress\": \"deny\",\r\n \"network_profile\": \"portable\",\r\n \"network_proxy\": \"10.0.0.1:3128\",\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"format\": 1,\r\n \"host_loopback\": \"allow\",\r\n \"host_loopback_forward\": [\r\n \"8080\",\r\n \"9090\"\r\n ],\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n },\r\n {\r\n \"path\": \"{root_json}\\\\runtime.erofs\",\r\n \"role\": \"runtime\",\r\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"net\": \"10.0.0.2/24\",\r\n \"network_egress\": \"deny\",\r\n \"network_egress_allow\": [\r\n \"192.0.2.0/24:tcp:443\"\r\n ],\r\n \"network_egress_deny\": [\r\n \"192.0.2.128/25\"\r\n ],\r\n \"network_ingress\": \"deny\",\r\n \"network_profile\": \"portable\",\r\n \"network_proxy\": \"10.0.0.1:3128\",\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"runtime:file:{root_json}\\\\runtime.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"1000:1000\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"384M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_layer=runtime,0xd0004000,22222222-2222-2222-2222-222222222222 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=worker nvx_memory_max=268435456 nvx_pids_max=65\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"deny\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"allow\",\"--network-egress-allow\",\"192.0.2.0/24:tcp:443\",\"--network-egress-deny\",\"192.0.2.128/25\",\"--host-loopback-forward\",\"8080\",\"--host-loopback-forward\",\"9090\",\"--network-proxy\",\"10.0.0.1:3128\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"format\": 1,\r\n \"host_loopback\": \"allow\",\r\n \"host_loopback_forward\": [\r\n \"8080\",\r\n \"9090\"\r\n ],\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n },\r\n {\r\n \"path\": \"{root_json}\\\\runtime.erofs\",\r\n \"role\": \"runtime\",\r\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"net\": \"10.0.0.2/24\",\r\n \"network_egress\": \"deny\",\r\n \"network_egress_allow\": [\r\n \"192.0.2.0/24:tcp:443\"\r\n ],\r\n \"network_egress_deny\": [\r\n \"192.0.2.128/25\"\r\n ],\r\n \"network_ingress\": \"deny\",\r\n \"network_profile\": \"portable\",\r\n \"network_proxy\": \"10.0.0.1:3128\",\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","lifecycle.lock","openvmm.log","runtime.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"format\": 1,\r\n \"host_loopback\": \"allow\",\r\n \"host_loopback_forward\": [\r\n \"8080\",\r\n \"9090\"\r\n ],\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n },\r\n {\r\n \"path\": \"{root_json}\\\\runtime.erofs\",\r\n \"role\": \"runtime\",\r\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"net\": \"10.0.0.2/24\",\r\n \"network_egress\": \"deny\",\r\n \"network_egress_allow\": [\r\n \"192.0.2.0/24:tcp:443\"\r\n ],\r\n \"network_egress_deny\": [\r\n \"192.0.2.128/25\"\r\n ],\r\n \"network_ingress\": \"deny\",\r\n \"network_profile\": \"portable\",\r\n \"network_proxy\": \"10.0.0.1:3128\",\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":{"text":"{\r\n \"control_endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\r\n \"format\": 1,\r\n \"pid\": {int},\r\n \"start_time\": {int}\r\n}\r\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log","outcome.json"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"format\": 1,\r\n \"host_loopback\": \"allow\",\r\n \"host_loopback_forward\": [\r\n \"8080\",\r\n \"9090\"\r\n ],\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n },\r\n {\r\n \"path\": \"{root_json}\\\\runtime.erofs\",\r\n \"role\": \"runtime\",\r\n \"uuid\": \"22222222-2222-2222-2222-222222222222\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"net\": \"10.0.0.2/24\",\r\n \"network_egress\": \"deny\",\r\n \"network_egress_allow\": [\r\n \"192.0.2.0/24:tcp:443\"\r\n ],\r\n \"network_egress_deny\": [\r\n \"192.0.2.128/25\"\r\n ],\r\n \"network_ingress\": \"deny\",\r\n \"network_profile\": \"portable\",\r\n \"network_proxy\": \"10.0.0.1:3128\",\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: operations without a sandbox","steps":[{"argv":["sandbox","start","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"}]}, +{"name":"state: refusals of a running sandbox","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox is already provisioned\r\n"},{"argv":["sandbox","start","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox is already running or has stale runtime state\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox must be stopped before deprovision\r\n"},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: relative and nested state directory","steps":[{"argv":["sandbox","provision","--state-dir","nested/state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"check":["nested/state","nested/state/config.json"],"status":0,"stdout":"","stderr":"","files":{"nested/state":{"entries":["config.json","lifecycle.lock"]},"nested/state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"}}},{"argv":["sandbox","deprovision","--state-dir","nested/state"],"check":["nested/state"],"status":0,"stdout":"","stderr":"","files":{"nested/state":null}}]}, +{"name":"state: failed start","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--cmdline","fake_exit_on_start=5"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\r\n","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"fake_exit_on_start=5\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","start","--state-dir","{root}\\state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\r\n","files":{"state":{"entries":["config.json","lifecycle.lock","openvmm.log"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"fake_exit_on_start=5\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\nopenvmm: failing the launch as requested\n"}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: start without OpenVMM","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state"],"remove":["openvmm/target/release/openvmm{exe}"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}\\openvmm\\target\\release\\openvmm.exe\r\n","files":{"state":{"entries":["config.json","lifecycle.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"format\": 1,\r\n \"host_loopback\": null,\r\n \"host_loopback_forward\": [],\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"net\": null,\r\n \"network_egress\": null,\r\n \"network_egress_allow\": [],\r\n \"network_egress_deny\": [],\r\n \"network_ingress\": null,\r\n \"network_profile\": null,\r\n \"network_proxy\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""}]}, +{"name":"state: malformed configuration","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state"],"write":{"state/config.json":"{\"format\": 7}"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}\\state\\config.json\r\n"},{"argv":["sandbox","start","--state-dir","{root}\\state"],"write":{"state/config.json":"[]"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}\\state\\config.json\r\n"},{"argv":["sandbox","start","--state-dir","{root}\\state"],"write":{"state/config.json":"{\"format\": 1}"},"status":1,"stdout":"","stderr":"error: sandbox configuration is malformed\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, +{"name":"state: not a directory","steps":[{"argv":["sandbox","start","--state-dir","{root}\\single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\single.txt\r\n"},{"argv":["sandbox","provision","--state-dir","{root}\\single.txt","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: [WinError 183] Cannot create a file when that file already exists: '{root_json}\\\\single.txt'\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\single.txt\r\n"}]}, +{"name":"state: foreign file","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"write":{"state/foreign.txt":"x"},"check":["state"],"status":1,"stdout":"","stderr":"error: sandbox state directory contains files not owned by NVX: foreign.txt\r\n","files":{"state":{"entries":["foreign.txt"]}}}]} +] +} diff --git a/nvx-rs/tests/fixtures/egress-policy.json b/nvx-rs/tests/fixtures/egress-policy.json new file mode 100644 index 000000000..646e3ade2 --- /dev/null +++ b/nvx-rs/tests/fixtures/egress-policy.json @@ -0,0 +1,850 @@ +{ + "comment": "Generated by scripts/nvx_rs_fixtures.py; do not edit.", + "cases": [ + {"name":"vector: test_accepts_exact_256_rule_boundaries #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 2}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 4, \"endPort\": 5}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 7, \"endPort\": 8}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 10, \"endPort\": 11}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 13, \"endPort\": 14}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 16, \"endPort\": 17}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 19, \"endPort\": 20}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 22, \"endPort\": 23}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 25, \"endPort\": 26}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 28, \"endPort\": 29}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 31, \"endPort\": 32}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 34, \"endPort\": 35}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 37, \"endPort\": 38}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 40, \"endPort\": 41}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 43, \"endPort\": 44}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 46, \"endPort\": 47}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 49, \"endPort\": 50}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 52, \"endPort\": 53}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 55, \"endPort\": 56}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 58, \"endPort\": 59}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 61, \"endPort\": 62}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 64, \"endPort\": 65}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 67, \"endPort\": 68}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 70, \"endPort\": 71}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 73, \"endPort\": 74}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 76, \"endPort\": 77}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 79, \"endPort\": 80}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 82, \"endPort\": 83}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 85, \"endPort\": 86}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 88, \"endPort\": 89}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 91, \"endPort\": 92}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 94, \"endPort\": 95}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 97, \"endPort\": 98}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 100, \"endPort\": 101}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 103, \"endPort\": 104}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 106, \"endPort\": 107}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 109, \"endPort\": 110}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 112, \"endPort\": 113}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 115, \"endPort\": 116}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 118, \"endPort\": 119}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 121, \"endPort\": 122}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 124, \"endPort\": 125}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 127, \"endPort\": 128}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 130, \"endPort\": 131}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 133, \"endPort\": 134}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 136, \"endPort\": 137}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 139, \"endPort\": 140}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 142, \"endPort\": 143}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 145, \"endPort\": 146}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 148, \"endPort\": 149}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 151, \"endPort\": 152}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 154, \"endPort\": 155}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 157, \"endPort\": 158}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 160, \"endPort\": 161}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 163, \"endPort\": 164}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 166, \"endPort\": 167}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 169, \"endPort\": 170}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 172, \"endPort\": 173}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 175, \"endPort\": 176}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 178, \"endPort\": 179}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 181, \"endPort\": 182}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 184, \"endPort\": 185}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 187, \"endPort\": 188}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 190, \"endPort\": 191}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 193, \"endPort\": 194}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 196, \"endPort\": 197}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 199, \"endPort\": 200}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 202, \"endPort\": 203}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 205, \"endPort\": 206}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 208, \"endPort\": 209}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 211, \"endPort\": 212}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 214, \"endPort\": 215}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 217, \"endPort\": 218}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 220, \"endPort\": 221}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 223, \"endPort\": 224}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 226, \"endPort\": 227}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 229, \"endPort\": 230}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 232, \"endPort\": 233}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 235, \"endPort\": 236}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 238, \"endPort\": 239}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 241, \"endPort\": 242}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 244, \"endPort\": 245}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 247, \"endPort\": 248}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 250, \"endPort\": 251}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 253, \"endPort\": 254}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 256, \"endPort\": 257}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 259, \"endPort\": 260}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 262, \"endPort\": 263}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 265, \"endPort\": 266}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 268, \"endPort\": 269}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 271, \"endPort\": 272}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 274, \"endPort\": 275}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 277, \"endPort\": 278}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 280, \"endPort\": 281}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 283, \"endPort\": 284}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 286, \"endPort\": 287}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 289, \"endPort\": 290}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 292, \"endPort\": 293}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 295, \"endPort\": 296}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 298, \"endPort\": 299}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 301, \"endPort\": 302}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 304, \"endPort\": 305}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 307, \"endPort\": 308}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 310, \"endPort\": 311}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 313, \"endPort\": 314}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 316, \"endPort\": 317}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 319, \"endPort\": 320}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 322, \"endPort\": 323}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 325, \"endPort\": 326}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 328, \"endPort\": 329}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 331, \"endPort\": 332}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 334, \"endPort\": 335}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 337, \"endPort\": 338}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 340, \"endPort\": 341}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 343, \"endPort\": 344}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 346, \"endPort\": 347}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 349, \"endPort\": 350}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 352, \"endPort\": 353}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 355, \"endPort\": 356}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 358, \"endPort\": 359}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 361, \"endPort\": 362}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 364, \"endPort\": 365}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 367, \"endPort\": 368}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 370, \"endPort\": 371}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 373, \"endPort\": 374}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 376, \"endPort\": 377}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 379, \"endPort\": 380}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 382, \"endPort\": 383}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 385, \"endPort\": 386}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 388, \"endPort\": 389}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 391, \"endPort\": 392}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 394, \"endPort\": 395}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 397, \"endPort\": 398}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 400, \"endPort\": 401}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 403, \"endPort\": 404}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 406, \"endPort\": 407}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 409, \"endPort\": 410}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 412, \"endPort\": 413}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 415, \"endPort\": 416}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 418, \"endPort\": 419}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 421, \"endPort\": 422}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 424, \"endPort\": 425}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 427, \"endPort\": 428}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 430, \"endPort\": 431}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 433, \"endPort\": 434}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 436, \"endPort\": 437}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 439, \"endPort\": 440}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 442, \"endPort\": 443}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 445, \"endPort\": 446}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 448, \"endPort\": 449}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 451, \"endPort\": 452}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 454, \"endPort\": 455}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 457, \"endPort\": 458}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 460, \"endPort\": 461}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 463, \"endPort\": 464}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 466, \"endPort\": 467}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 469, \"endPort\": 470}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 472, \"endPort\": 473}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 475, \"endPort\": 476}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 478, \"endPort\": 479}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 481, \"endPort\": 482}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 484, \"endPort\": 485}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 487, \"endPort\": 488}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 490, \"endPort\": 491}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 493, \"endPort\": 494}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 496, \"endPort\": 497}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 499, \"endPort\": 500}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 502, \"endPort\": 503}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 505, \"endPort\": 506}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 508, \"endPort\": 509}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 511, \"endPort\": 512}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 514, \"endPort\": 515}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 517, \"endPort\": 518}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 520, \"endPort\": 521}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 523, \"endPort\": 524}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 526, \"endPort\": 527}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 529, \"endPort\": 530}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 532, \"endPort\": 533}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 535, \"endPort\": 536}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 538, \"endPort\": 539}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 541, \"endPort\": 542}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 544, \"endPort\": 545}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 547, \"endPort\": 548}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 550, \"endPort\": 551}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 553, \"endPort\": 554}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 556, \"endPort\": 557}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 559, \"endPort\": 560}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 562, \"endPort\": 563}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 565, \"endPort\": 566}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 568, \"endPort\": 569}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 571, \"endPort\": 572}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 574, \"endPort\": 575}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 577, \"endPort\": 578}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 580, \"endPort\": 581}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 583, \"endPort\": 584}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 586, \"endPort\": 587}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 589, \"endPort\": 590}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 592, \"endPort\": 593}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 595, \"endPort\": 596}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 598, \"endPort\": 599}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 601, \"endPort\": 602}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 604, \"endPort\": 605}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 607, \"endPort\": 608}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 610, \"endPort\": 611}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 613, \"endPort\": 614}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 616, \"endPort\": 617}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 619, \"endPort\": 620}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 622, \"endPort\": 623}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 625, \"endPort\": 626}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 628, \"endPort\": 629}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 631, \"endPort\": 632}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 634, \"endPort\": 635}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 637, \"endPort\": 638}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 640, \"endPort\": 641}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 643, \"endPort\": 644}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 646, \"endPort\": 647}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 649, \"endPort\": 650}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 652, \"endPort\": 653}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 655, \"endPort\": 656}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 658, \"endPort\": 659}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 661, \"endPort\": 662}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 664, \"endPort\": 665}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 667, \"endPort\": 668}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 670, \"endPort\": 671}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 673, \"endPort\": 674}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 676, \"endPort\": 677}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 679, \"endPort\": 680}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 682, \"endPort\": 683}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 685, \"endPort\": 686}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 688, \"endPort\": 689}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 691, \"endPort\": 692}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 694, \"endPort\": 695}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 697, \"endPort\": 698}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 700, \"endPort\": 701}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 703, \"endPort\": 704}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 706, \"endPort\": 707}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 709, \"endPort\": 710}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 712, \"endPort\": 713}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 715, \"endPort\": 716}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 718, \"endPort\": 719}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 721, \"endPort\": 722}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 724, \"endPort\": 725}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 727, \"endPort\": 728}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 730, \"endPort\": 731}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 733, \"endPort\": 734}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 736, \"endPort\": 737}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 739, \"endPort\": 740}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 742, \"endPort\": 743}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 745, \"endPort\": 746}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 748, \"endPort\": 749}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 751, \"endPort\": 752}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 754, \"endPort\": 755}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 757, \"endPort\": 758}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 760, \"endPort\": 761}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 763, \"endPort\": 764}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 766, \"endPort\": 767}], \"deny\": [{\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 1, \"endPort\": 2}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 4, \"endPort\": 5}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 7, \"endPort\": 8}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 10, \"endPort\": 11}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 13, \"endPort\": 14}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 16, \"endPort\": 17}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 19, \"endPort\": 20}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 22, \"endPort\": 23}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 25, \"endPort\": 26}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 28, \"endPort\": 29}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 31, \"endPort\": 32}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 34, \"endPort\": 35}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 37, \"endPort\": 38}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 40, \"endPort\": 41}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 43, \"endPort\": 44}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 46, \"endPort\": 47}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 49, \"endPort\": 50}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 52, \"endPort\": 53}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 55, \"endPort\": 56}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 58, \"endPort\": 59}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 61, \"endPort\": 62}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 64, \"endPort\": 65}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 67, \"endPort\": 68}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 70, \"endPort\": 71}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 73, \"endPort\": 74}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 76, \"endPort\": 77}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 79, \"endPort\": 80}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 82, \"endPort\": 83}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 85, \"endPort\": 86}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 88, \"endPort\": 89}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 91, \"endPort\": 92}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 94, \"endPort\": 95}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 97, \"endPort\": 98}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 100, \"endPort\": 101}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 103, \"endPort\": 104}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 106, \"endPort\": 107}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 109, \"endPort\": 110}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 112, \"endPort\": 113}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 115, \"endPort\": 116}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 118, \"endPort\": 119}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 121, \"endPort\": 122}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 124, \"endPort\": 125}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 127, \"endPort\": 128}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 130, \"endPort\": 131}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 133, \"endPort\": 134}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 136, \"endPort\": 137}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 139, \"endPort\": 140}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 142, \"endPort\": 143}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 145, \"endPort\": 146}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 148, \"endPort\": 149}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 151, \"endPort\": 152}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 154, \"endPort\": 155}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 157, \"endPort\": 158}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 160, \"endPort\": 161}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 163, \"endPort\": 164}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 166, \"endPort\": 167}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 169, \"endPort\": 170}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 172, \"endPort\": 173}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 175, \"endPort\": 176}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 178, \"endPort\": 179}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 181, \"endPort\": 182}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 184, \"endPort\": 185}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 187, \"endPort\": 188}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 190, \"endPort\": 191}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 193, \"endPort\": 194}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 196, \"endPort\": 197}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 199, \"endPort\": 200}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 202, \"endPort\": 203}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 205, \"endPort\": 206}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 208, \"endPort\": 209}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 211, \"endPort\": 212}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 214, \"endPort\": 215}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 217, \"endPort\": 218}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 220, \"endPort\": 221}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 223, \"endPort\": 224}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 226, \"endPort\": 227}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 229, \"endPort\": 230}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 232, \"endPort\": 233}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 235, \"endPort\": 236}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 238, \"endPort\": 239}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 241, \"endPort\": 242}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 244, \"endPort\": 245}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 247, \"endPort\": 248}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 250, \"endPort\": 251}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 253, \"endPort\": 254}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 256, \"endPort\": 257}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 259, \"endPort\": 260}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 262, \"endPort\": 263}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 265, \"endPort\": 266}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 268, \"endPort\": 269}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 271, \"endPort\": 272}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 274, \"endPort\": 275}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 277, \"endPort\": 278}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 280, \"endPort\": 281}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 283, \"endPort\": 284}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 286, \"endPort\": 287}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 289, \"endPort\": 290}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 292, \"endPort\": 293}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 295, \"endPort\": 296}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 298, \"endPort\": 299}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 301, \"endPort\": 302}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 304, \"endPort\": 305}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 307, \"endPort\": 308}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 310, \"endPort\": 311}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 313, \"endPort\": 314}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 316, \"endPort\": 317}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 319, \"endPort\": 320}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 322, \"endPort\": 323}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 325, \"endPort\": 326}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 328, \"endPort\": 329}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 331, \"endPort\": 332}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 334, \"endPort\": 335}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 337, \"endPort\": 338}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 340, \"endPort\": 341}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 343, \"endPort\": 344}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 346, \"endPort\": 347}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 349, \"endPort\": 350}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 352, \"endPort\": 353}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 355, \"endPort\": 356}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 358, \"endPort\": 359}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 361, \"endPort\": 362}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 364, \"endPort\": 365}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 367, \"endPort\": 368}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 370, \"endPort\": 371}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 373, \"endPort\": 374}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 376, \"endPort\": 377}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 379, \"endPort\": 380}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 382, \"endPort\": 383}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 385, \"endPort\": 386}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 388, \"endPort\": 389}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 391, \"endPort\": 392}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 394, \"endPort\": 395}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 397, \"endPort\": 398}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 400, \"endPort\": 401}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 403, \"endPort\": 404}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 406, \"endPort\": 407}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 409, \"endPort\": 410}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 412, \"endPort\": 413}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 415, \"endPort\": 416}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 418, \"endPort\": 419}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 421, \"endPort\": 422}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 424, \"endPort\": 425}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 427, \"endPort\": 428}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 430, \"endPort\": 431}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 433, \"endPort\": 434}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 436, \"endPort\": 437}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 439, \"endPort\": 440}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 442, \"endPort\": 443}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 445, \"endPort\": 446}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 448, \"endPort\": 449}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 451, \"endPort\": 452}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 454, \"endPort\": 455}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 457, \"endPort\": 458}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 460, \"endPort\": 461}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 463, \"endPort\": 464}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 466, \"endPort\": 467}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 469, \"endPort\": 470}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 472, \"endPort\": 473}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 475, \"endPort\": 476}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 478, \"endPort\": 479}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 481, \"endPort\": 482}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 484, \"endPort\": 485}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 487, \"endPort\": 488}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 490, \"endPort\": 491}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 493, \"endPort\": 494}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 496, \"endPort\": 497}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 499, \"endPort\": 500}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 502, \"endPort\": 503}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 505, \"endPort\": 506}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 508, \"endPort\": 509}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 511, \"endPort\": 512}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 514, \"endPort\": 515}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 517, \"endPort\": 518}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 520, \"endPort\": 521}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 523, \"endPort\": 524}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 526, \"endPort\": 527}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 529, \"endPort\": 530}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 532, \"endPort\": 533}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 535, \"endPort\": 536}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 538, \"endPort\": 539}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 541, \"endPort\": 542}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 544, \"endPort\": 545}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 547, \"endPort\": 548}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 550, \"endPort\": 551}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 553, \"endPort\": 554}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 556, \"endPort\": 557}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 559, \"endPort\": 560}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 562, \"endPort\": 563}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 565, \"endPort\": 566}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 568, \"endPort\": 569}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 571, \"endPort\": 572}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 574, \"endPort\": 575}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 577, \"endPort\": 578}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 580, \"endPort\": 581}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 583, \"endPort\": 584}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 586, \"endPort\": 587}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 589, \"endPort\": 590}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 592, \"endPort\": 593}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 595, \"endPort\": 596}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 598, \"endPort\": 599}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 601, \"endPort\": 602}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 604, \"endPort\": 605}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 607, \"endPort\": 608}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 610, \"endPort\": 611}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 613, \"endPort\": 614}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 616, \"endPort\": 617}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 619, \"endPort\": 620}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 622, \"endPort\": 623}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 625, \"endPort\": 626}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 628, \"endPort\": 629}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 631, \"endPort\": 632}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 634, \"endPort\": 635}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 637, \"endPort\": 638}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 640, \"endPort\": 641}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 643, \"endPort\": 644}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 646, \"endPort\": 647}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 649, \"endPort\": 650}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 652, \"endPort\": 653}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 655, \"endPort\": 656}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 658, \"endPort\": 659}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 661, \"endPort\": 662}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 664, \"endPort\": 665}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 667, \"endPort\": 668}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 670, \"endPort\": 671}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 673, \"endPort\": 674}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 676, \"endPort\": 677}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 679, \"endPort\": 680}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 682, \"endPort\": 683}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 685, \"endPort\": 686}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 688, \"endPort\": 689}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 691, \"endPort\": 692}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 694, \"endPort\": 695}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 697, \"endPort\": 698}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 700, \"endPort\": 701}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 703, \"endPort\": 704}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 706, \"endPort\": 707}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 709, \"endPort\": 710}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 712, \"endPort\": 713}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 715, \"endPort\": 716}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 718, \"endPort\": 719}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 721, \"endPort\": 722}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 724, \"endPort\": 725}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 727, \"endPort\": 728}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 730, \"endPort\": 731}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 733, \"endPort\": 734}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 736, \"endPort\": 737}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 739, \"endPort\": 740}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 742, \"endPort\": 743}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 745, \"endPort\": 746}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 748, \"endPort\": 749}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 751, \"endPort\": 752}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 754, \"endPort\": 755}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 757, \"endPort\": 758}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 760, \"endPort\": 761}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 763, \"endPort\": 764}, {\"cidr\": \"198.51.100.1\", \"protocol\": \"udp\", \"port\": 766, \"endPort\": 767}]}"}],"expected":{"allow":["192.0.2.1/32:tcp:1-2","192.0.2.1/32:tcp:4-5","192.0.2.1/32:tcp:7-8","192.0.2.1/32:tcp:10-11","192.0.2.1/32:tcp:13-14","192.0.2.1/32:tcp:16-17","192.0.2.1/32:tcp:19-20","192.0.2.1/32:tcp:22-23","192.0.2.1/32:tcp:25-26","192.0.2.1/32:tcp:28-29","192.0.2.1/32:tcp:31-32","192.0.2.1/32:tcp:34-35","192.0.2.1/32:tcp:37-38","192.0.2.1/32:tcp:40-41","192.0.2.1/32:tcp:43-44","192.0.2.1/32:tcp:46-47","192.0.2.1/32:tcp:49-50","192.0.2.1/32:tcp:52-53","192.0.2.1/32:tcp:55-56","192.0.2.1/32:tcp:58-59","192.0.2.1/32:tcp:61-62","192.0.2.1/32:tcp:64-65","192.0.2.1/32:tcp:67-68","192.0.2.1/32:tcp:70-71","192.0.2.1/32:tcp:73-74","192.0.2.1/32:tcp:76-77","192.0.2.1/32:tcp:79-80","192.0.2.1/32:tcp:82-83","192.0.2.1/32:tcp:85-86","192.0.2.1/32:tcp:88-89","192.0.2.1/32:tcp:91-92","192.0.2.1/32:tcp:94-95","192.0.2.1/32:tcp:97-98","192.0.2.1/32:tcp:100-101","192.0.2.1/32:tcp:103-104","192.0.2.1/32:tcp:106-107","192.0.2.1/32:tcp:109-110","192.0.2.1/32:tcp:112-113","192.0.2.1/32:tcp:115-116","192.0.2.1/32:tcp:118-119","192.0.2.1/32:tcp:121-122","192.0.2.1/32:tcp:124-125","192.0.2.1/32:tcp:127-128","192.0.2.1/32:tcp:130-131","192.0.2.1/32:tcp:133-134","192.0.2.1/32:tcp:136-137","192.0.2.1/32:tcp:139-140","192.0.2.1/32:tcp:142-143","192.0.2.1/32:tcp:145-146","192.0.2.1/32:tcp:148-149","192.0.2.1/32:tcp:151-152","192.0.2.1/32:tcp:154-155","192.0.2.1/32:tcp:157-158","192.0.2.1/32:tcp:160-161","192.0.2.1/32:tcp:163-164","192.0.2.1/32:tcp:166-167","192.0.2.1/32:tcp:169-170","192.0.2.1/32:tcp:172-173","192.0.2.1/32:tcp:175-176","192.0.2.1/32:tcp:178-179","192.0.2.1/32:tcp:181-182","192.0.2.1/32:tcp:184-185","192.0.2.1/32:tcp:187-188","192.0.2.1/32:tcp:190-191","192.0.2.1/32:tcp:193-194","192.0.2.1/32:tcp:196-197","192.0.2.1/32:tcp:199-200","192.0.2.1/32:tcp:202-203","192.0.2.1/32:tcp:205-206","192.0.2.1/32:tcp:208-209","192.0.2.1/32:tcp:211-212","192.0.2.1/32:tcp:214-215","192.0.2.1/32:tcp:217-218","192.0.2.1/32:tcp:220-221","192.0.2.1/32:tcp:223-224","192.0.2.1/32:tcp:226-227","192.0.2.1/32:tcp:229-230","192.0.2.1/32:tcp:232-233","192.0.2.1/32:tcp:235-236","192.0.2.1/32:tcp:238-239","192.0.2.1/32:tcp:241-242","192.0.2.1/32:tcp:244-245","192.0.2.1/32:tcp:247-248","192.0.2.1/32:tcp:250-251","192.0.2.1/32:tcp:253-254","192.0.2.1/32:tcp:256-257","192.0.2.1/32:tcp:259-260","192.0.2.1/32:tcp:262-263","192.0.2.1/32:tcp:265-266","192.0.2.1/32:tcp:268-269","192.0.2.1/32:tcp:271-272","192.0.2.1/32:tcp:274-275","192.0.2.1/32:tcp:277-278","192.0.2.1/32:tcp:280-281","192.0.2.1/32:tcp:283-284","192.0.2.1/32:tcp:286-287","192.0.2.1/32:tcp:289-290","192.0.2.1/32:tcp:292-293","192.0.2.1/32:tcp:295-296","192.0.2.1/32:tcp:298-299","192.0.2.1/32:tcp:301-302","192.0.2.1/32:tcp:304-305","192.0.2.1/32:tcp:307-308","192.0.2.1/32:tcp:310-311","192.0.2.1/32:tcp:313-314","192.0.2.1/32:tcp:316-317","192.0.2.1/32:tcp:319-320","192.0.2.1/32:tcp:322-323","192.0.2.1/32:tcp:325-326","192.0.2.1/32:tcp:328-329","192.0.2.1/32:tcp:331-332","192.0.2.1/32:tcp:334-335","192.0.2.1/32:tcp:337-338","192.0.2.1/32:tcp:340-341","192.0.2.1/32:tcp:343-344","192.0.2.1/32:tcp:346-347","192.0.2.1/32:tcp:349-350","192.0.2.1/32:tcp:352-353","192.0.2.1/32:tcp:355-356","192.0.2.1/32:tcp:358-359","192.0.2.1/32:tcp:361-362","192.0.2.1/32:tcp:364-365","192.0.2.1/32:tcp:367-368","192.0.2.1/32:tcp:370-371","192.0.2.1/32:tcp:373-374","192.0.2.1/32:tcp:376-377","192.0.2.1/32:tcp:379-380","192.0.2.1/32:tcp:382-383","192.0.2.1/32:tcp:385-386","192.0.2.1/32:tcp:388-389","192.0.2.1/32:tcp:391-392","192.0.2.1/32:tcp:394-395","192.0.2.1/32:tcp:397-398","192.0.2.1/32:tcp:400-401","192.0.2.1/32:tcp:403-404","192.0.2.1/32:tcp:406-407","192.0.2.1/32:tcp:409-410","192.0.2.1/32:tcp:412-413","192.0.2.1/32:tcp:415-416","192.0.2.1/32:tcp:418-419","192.0.2.1/32:tcp:421-422","192.0.2.1/32:tcp:424-425","192.0.2.1/32:tcp:427-428","192.0.2.1/32:tcp:430-431","192.0.2.1/32:tcp:433-434","192.0.2.1/32:tcp:436-437","192.0.2.1/32:tcp:439-440","192.0.2.1/32:tcp:442-443","192.0.2.1/32:tcp:445-446","192.0.2.1/32:tcp:448-449","192.0.2.1/32:tcp:451-452","192.0.2.1/32:tcp:454-455","192.0.2.1/32:tcp:457-458","192.0.2.1/32:tcp:460-461","192.0.2.1/32:tcp:463-464","192.0.2.1/32:tcp:466-467","192.0.2.1/32:tcp:469-470","192.0.2.1/32:tcp:472-473","192.0.2.1/32:tcp:475-476","192.0.2.1/32:tcp:478-479","192.0.2.1/32:tcp:481-482","192.0.2.1/32:tcp:484-485","192.0.2.1/32:tcp:487-488","192.0.2.1/32:tcp:490-491","192.0.2.1/32:tcp:493-494","192.0.2.1/32:tcp:496-497","192.0.2.1/32:tcp:499-500","192.0.2.1/32:tcp:502-503","192.0.2.1/32:tcp:505-506","192.0.2.1/32:tcp:508-509","192.0.2.1/32:tcp:511-512","192.0.2.1/32:tcp:514-515","192.0.2.1/32:tcp:517-518","192.0.2.1/32:tcp:520-521","192.0.2.1/32:tcp:523-524","192.0.2.1/32:tcp:526-527","192.0.2.1/32:tcp:529-530","192.0.2.1/32:tcp:532-533","192.0.2.1/32:tcp:535-536","192.0.2.1/32:tcp:538-539","192.0.2.1/32:tcp:541-542","192.0.2.1/32:tcp:544-545","192.0.2.1/32:tcp:547-548","192.0.2.1/32:tcp:550-551","192.0.2.1/32:tcp:553-554","192.0.2.1/32:tcp:556-557","192.0.2.1/32:tcp:559-560","192.0.2.1/32:tcp:562-563","192.0.2.1/32:tcp:565-566","192.0.2.1/32:tcp:568-569","192.0.2.1/32:tcp:571-572","192.0.2.1/32:tcp:574-575","192.0.2.1/32:tcp:577-578","192.0.2.1/32:tcp:580-581","192.0.2.1/32:tcp:583-584","192.0.2.1/32:tcp:586-587","192.0.2.1/32:tcp:589-590","192.0.2.1/32:tcp:592-593","192.0.2.1/32:tcp:595-596","192.0.2.1/32:tcp:598-599","192.0.2.1/32:tcp:601-602","192.0.2.1/32:tcp:604-605","192.0.2.1/32:tcp:607-608","192.0.2.1/32:tcp:610-611","192.0.2.1/32:tcp:613-614","192.0.2.1/32:tcp:616-617","192.0.2.1/32:tcp:619-620","192.0.2.1/32:tcp:622-623","192.0.2.1/32:tcp:625-626","192.0.2.1/32:tcp:628-629","192.0.2.1/32:tcp:631-632","192.0.2.1/32:tcp:634-635","192.0.2.1/32:tcp:637-638","192.0.2.1/32:tcp:640-641","192.0.2.1/32:tcp:643-644","192.0.2.1/32:tcp:646-647","192.0.2.1/32:tcp:649-650","192.0.2.1/32:tcp:652-653","192.0.2.1/32:tcp:655-656","192.0.2.1/32:tcp:658-659","192.0.2.1/32:tcp:661-662","192.0.2.1/32:tcp:664-665","192.0.2.1/32:tcp:667-668","192.0.2.1/32:tcp:670-671","192.0.2.1/32:tcp:673-674","192.0.2.1/32:tcp:676-677","192.0.2.1/32:tcp:679-680","192.0.2.1/32:tcp:682-683","192.0.2.1/32:tcp:685-686","192.0.2.1/32:tcp:688-689","192.0.2.1/32:tcp:691-692","192.0.2.1/32:tcp:694-695","192.0.2.1/32:tcp:697-698","192.0.2.1/32:tcp:700-701","192.0.2.1/32:tcp:703-704","192.0.2.1/32:tcp:706-707","192.0.2.1/32:tcp:709-710","192.0.2.1/32:tcp:712-713","192.0.2.1/32:tcp:715-716","192.0.2.1/32:tcp:718-719","192.0.2.1/32:tcp:721-722","192.0.2.1/32:tcp:724-725","192.0.2.1/32:tcp:727-728","192.0.2.1/32:tcp:730-731","192.0.2.1/32:tcp:733-734","192.0.2.1/32:tcp:736-737","192.0.2.1/32:tcp:739-740","192.0.2.1/32:tcp:742-743","192.0.2.1/32:tcp:745-746","192.0.2.1/32:tcp:748-749","192.0.2.1/32:tcp:751-752","192.0.2.1/32:tcp:754-755","192.0.2.1/32:tcp:757-758","192.0.2.1/32:tcp:760-761","192.0.2.1/32:tcp:763-764","192.0.2.1/32:tcp:766-767"],"deny":["198.51.100.1/32:udp:1-2","198.51.100.1/32:udp:4-5","198.51.100.1/32:udp:7-8","198.51.100.1/32:udp:10-11","198.51.100.1/32:udp:13-14","198.51.100.1/32:udp:16-17","198.51.100.1/32:udp:19-20","198.51.100.1/32:udp:22-23","198.51.100.1/32:udp:25-26","198.51.100.1/32:udp:28-29","198.51.100.1/32:udp:31-32","198.51.100.1/32:udp:34-35","198.51.100.1/32:udp:37-38","198.51.100.1/32:udp:40-41","198.51.100.1/32:udp:43-44","198.51.100.1/32:udp:46-47","198.51.100.1/32:udp:49-50","198.51.100.1/32:udp:52-53","198.51.100.1/32:udp:55-56","198.51.100.1/32:udp:58-59","198.51.100.1/32:udp:61-62","198.51.100.1/32:udp:64-65","198.51.100.1/32:udp:67-68","198.51.100.1/32:udp:70-71","198.51.100.1/32:udp:73-74","198.51.100.1/32:udp:76-77","198.51.100.1/32:udp:79-80","198.51.100.1/32:udp:82-83","198.51.100.1/32:udp:85-86","198.51.100.1/32:udp:88-89","198.51.100.1/32:udp:91-92","198.51.100.1/32:udp:94-95","198.51.100.1/32:udp:97-98","198.51.100.1/32:udp:100-101","198.51.100.1/32:udp:103-104","198.51.100.1/32:udp:106-107","198.51.100.1/32:udp:109-110","198.51.100.1/32:udp:112-113","198.51.100.1/32:udp:115-116","198.51.100.1/32:udp:118-119","198.51.100.1/32:udp:121-122","198.51.100.1/32:udp:124-125","198.51.100.1/32:udp:127-128","198.51.100.1/32:udp:130-131","198.51.100.1/32:udp:133-134","198.51.100.1/32:udp:136-137","198.51.100.1/32:udp:139-140","198.51.100.1/32:udp:142-143","198.51.100.1/32:udp:145-146","198.51.100.1/32:udp:148-149","198.51.100.1/32:udp:151-152","198.51.100.1/32:udp:154-155","198.51.100.1/32:udp:157-158","198.51.100.1/32:udp:160-161","198.51.100.1/32:udp:163-164","198.51.100.1/32:udp:166-167","198.51.100.1/32:udp:169-170","198.51.100.1/32:udp:172-173","198.51.100.1/32:udp:175-176","198.51.100.1/32:udp:178-179","198.51.100.1/32:udp:181-182","198.51.100.1/32:udp:184-185","198.51.100.1/32:udp:187-188","198.51.100.1/32:udp:190-191","198.51.100.1/32:udp:193-194","198.51.100.1/32:udp:196-197","198.51.100.1/32:udp:199-200","198.51.100.1/32:udp:202-203","198.51.100.1/32:udp:205-206","198.51.100.1/32:udp:208-209","198.51.100.1/32:udp:211-212","198.51.100.1/32:udp:214-215","198.51.100.1/32:udp:217-218","198.51.100.1/32:udp:220-221","198.51.100.1/32:udp:223-224","198.51.100.1/32:udp:226-227","198.51.100.1/32:udp:229-230","198.51.100.1/32:udp:232-233","198.51.100.1/32:udp:235-236","198.51.100.1/32:udp:238-239","198.51.100.1/32:udp:241-242","198.51.100.1/32:udp:244-245","198.51.100.1/32:udp:247-248","198.51.100.1/32:udp:250-251","198.51.100.1/32:udp:253-254","198.51.100.1/32:udp:256-257","198.51.100.1/32:udp:259-260","198.51.100.1/32:udp:262-263","198.51.100.1/32:udp:265-266","198.51.100.1/32:udp:268-269","198.51.100.1/32:udp:271-272","198.51.100.1/32:udp:274-275","198.51.100.1/32:udp:277-278","198.51.100.1/32:udp:280-281","198.51.100.1/32:udp:283-284","198.51.100.1/32:udp:286-287","198.51.100.1/32:udp:289-290","198.51.100.1/32:udp:292-293","198.51.100.1/32:udp:295-296","198.51.100.1/32:udp:298-299","198.51.100.1/32:udp:301-302","198.51.100.1/32:udp:304-305","198.51.100.1/32:udp:307-308","198.51.100.1/32:udp:310-311","198.51.100.1/32:udp:313-314","198.51.100.1/32:udp:316-317","198.51.100.1/32:udp:319-320","198.51.100.1/32:udp:322-323","198.51.100.1/32:udp:325-326","198.51.100.1/32:udp:328-329","198.51.100.1/32:udp:331-332","198.51.100.1/32:udp:334-335","198.51.100.1/32:udp:337-338","198.51.100.1/32:udp:340-341","198.51.100.1/32:udp:343-344","198.51.100.1/32:udp:346-347","198.51.100.1/32:udp:349-350","198.51.100.1/32:udp:352-353","198.51.100.1/32:udp:355-356","198.51.100.1/32:udp:358-359","198.51.100.1/32:udp:361-362","198.51.100.1/32:udp:364-365","198.51.100.1/32:udp:367-368","198.51.100.1/32:udp:370-371","198.51.100.1/32:udp:373-374","198.51.100.1/32:udp:376-377","198.51.100.1/32:udp:379-380","198.51.100.1/32:udp:382-383","198.51.100.1/32:udp:385-386","198.51.100.1/32:udp:388-389","198.51.100.1/32:udp:391-392","198.51.100.1/32:udp:394-395","198.51.100.1/32:udp:397-398","198.51.100.1/32:udp:400-401","198.51.100.1/32:udp:403-404","198.51.100.1/32:udp:406-407","198.51.100.1/32:udp:409-410","198.51.100.1/32:udp:412-413","198.51.100.1/32:udp:415-416","198.51.100.1/32:udp:418-419","198.51.100.1/32:udp:421-422","198.51.100.1/32:udp:424-425","198.51.100.1/32:udp:427-428","198.51.100.1/32:udp:430-431","198.51.100.1/32:udp:433-434","198.51.100.1/32:udp:436-437","198.51.100.1/32:udp:439-440","198.51.100.1/32:udp:442-443","198.51.100.1/32:udp:445-446","198.51.100.1/32:udp:448-449","198.51.100.1/32:udp:451-452","198.51.100.1/32:udp:454-455","198.51.100.1/32:udp:457-458","198.51.100.1/32:udp:460-461","198.51.100.1/32:udp:463-464","198.51.100.1/32:udp:466-467","198.51.100.1/32:udp:469-470","198.51.100.1/32:udp:472-473","198.51.100.1/32:udp:475-476","198.51.100.1/32:udp:478-479","198.51.100.1/32:udp:481-482","198.51.100.1/32:udp:484-485","198.51.100.1/32:udp:487-488","198.51.100.1/32:udp:490-491","198.51.100.1/32:udp:493-494","198.51.100.1/32:udp:496-497","198.51.100.1/32:udp:499-500","198.51.100.1/32:udp:502-503","198.51.100.1/32:udp:505-506","198.51.100.1/32:udp:508-509","198.51.100.1/32:udp:511-512","198.51.100.1/32:udp:514-515","198.51.100.1/32:udp:517-518","198.51.100.1/32:udp:520-521","198.51.100.1/32:udp:523-524","198.51.100.1/32:udp:526-527","198.51.100.1/32:udp:529-530","198.51.100.1/32:udp:532-533","198.51.100.1/32:udp:535-536","198.51.100.1/32:udp:538-539","198.51.100.1/32:udp:541-542","198.51.100.1/32:udp:544-545","198.51.100.1/32:udp:547-548","198.51.100.1/32:udp:550-551","198.51.100.1/32:udp:553-554","198.51.100.1/32:udp:556-557","198.51.100.1/32:udp:559-560","198.51.100.1/32:udp:562-563","198.51.100.1/32:udp:565-566","198.51.100.1/32:udp:568-569","198.51.100.1/32:udp:571-572","198.51.100.1/32:udp:574-575","198.51.100.1/32:udp:577-578","198.51.100.1/32:udp:580-581","198.51.100.1/32:udp:583-584","198.51.100.1/32:udp:586-587","198.51.100.1/32:udp:589-590","198.51.100.1/32:udp:592-593","198.51.100.1/32:udp:595-596","198.51.100.1/32:udp:598-599","198.51.100.1/32:udp:601-602","198.51.100.1/32:udp:604-605","198.51.100.1/32:udp:607-608","198.51.100.1/32:udp:610-611","198.51.100.1/32:udp:613-614","198.51.100.1/32:udp:616-617","198.51.100.1/32:udp:619-620","198.51.100.1/32:udp:622-623","198.51.100.1/32:udp:625-626","198.51.100.1/32:udp:628-629","198.51.100.1/32:udp:631-632","198.51.100.1/32:udp:634-635","198.51.100.1/32:udp:637-638","198.51.100.1/32:udp:640-641","198.51.100.1/32:udp:643-644","198.51.100.1/32:udp:646-647","198.51.100.1/32:udp:649-650","198.51.100.1/32:udp:652-653","198.51.100.1/32:udp:655-656","198.51.100.1/32:udp:658-659","198.51.100.1/32:udp:661-662","198.51.100.1/32:udp:664-665","198.51.100.1/32:udp:667-668","198.51.100.1/32:udp:670-671","198.51.100.1/32:udp:673-674","198.51.100.1/32:udp:676-677","198.51.100.1/32:udp:679-680","198.51.100.1/32:udp:682-683","198.51.100.1/32:udp:685-686","198.51.100.1/32:udp:688-689","198.51.100.1/32:udp:691-692","198.51.100.1/32:udp:694-695","198.51.100.1/32:udp:697-698","198.51.100.1/32:udp:700-701","198.51.100.1/32:udp:703-704","198.51.100.1/32:udp:706-707","198.51.100.1/32:udp:709-710","198.51.100.1/32:udp:712-713","198.51.100.1/32:udp:715-716","198.51.100.1/32:udp:718-719","198.51.100.1/32:udp:721-722","198.51.100.1/32:udp:724-725","198.51.100.1/32:udp:727-728","198.51.100.1/32:udp:730-731","198.51.100.1/32:udp:733-734","198.51.100.1/32:udp:736-737","198.51.100.1/32:udp:739-740","198.51.100.1/32:udp:742-743","198.51.100.1/32:udp:745-746","198.51.100.1/32:udp:748-749","198.51.100.1/32:udp:751-752","198.51.100.1/32:udp:754-755","198.51.100.1/32:udp:757-758","198.51.100.1/32:udp:760-761","198.51.100.1/32:udp:763-764","198.51.100.1/32:udp:766-767"]}}, + {"name":"vector: test_address_only_rule_covers_large_protocol_range #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}, {\"cidr\": \"192.0.2.0/24\"}]}"}],"expected":{"allow":["192.0.2.0/24"],"deny":[]}}, + {"name":"vector: test_address_only_rule_covers_large_protocol_range #2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}]}"}],"expected":{"allow":["192.0.2.0/24"],"deny":[]}}, + {"name":"vector: test_address_only_rules_cover_protocol_wide_rules #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"udp\"}, {\"cidr\": \"192.0.2.0/25\"}, {\"cidr\": \"192.0.2.0/25\", \"protocol\": \"icmp\"}, {\"cidr\": \"192.0.2.128/25\", \"protocol\": \"udp\", \"port\": 53}]}"}],"expected":{"allow":["192.0.2.0/24:udp","192.0.2.0/25"],"deny":[]}}, + {"name":"vector: test_both_families_share_the_native_rule_budget #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1}, {\"protocol\": \"tcp\", \"port\": 3}, {\"protocol\": \"tcp\", \"port\": 5}, {\"protocol\": \"tcp\", \"port\": 7}, {\"protocol\": \"tcp\", \"port\": 9}, {\"protocol\": \"tcp\", \"port\": 11}, {\"protocol\": \"tcp\", \"port\": 13}, {\"protocol\": \"tcp\", \"port\": 15}, {\"protocol\": \"tcp\", \"port\": 17}, {\"protocol\": \"tcp\", \"port\": 19}, {\"protocol\": \"tcp\", \"port\": 21}, {\"protocol\": \"tcp\", \"port\": 23}, {\"protocol\": \"tcp\", \"port\": 25}, {\"protocol\": \"tcp\", \"port\": 27}, {\"protocol\": \"tcp\", \"port\": 29}, {\"protocol\": \"tcp\", \"port\": 31}, {\"protocol\": \"tcp\", \"port\": 33}, {\"protocol\": \"tcp\", \"port\": 35}, {\"protocol\": \"tcp\", \"port\": 37}, {\"protocol\": \"tcp\", \"port\": 39}, {\"protocol\": \"tcp\", \"port\": 41}, {\"protocol\": \"tcp\", \"port\": 43}, {\"protocol\": \"tcp\", \"port\": 45}, {\"protocol\": \"tcp\", \"port\": 47}, {\"protocol\": \"tcp\", \"port\": 49}, {\"protocol\": \"tcp\", \"port\": 51}, {\"protocol\": \"tcp\", \"port\": 53}, {\"protocol\": \"tcp\", \"port\": 55}, {\"protocol\": \"tcp\", \"port\": 57}, {\"protocol\": \"tcp\", \"port\": 59}, {\"protocol\": \"tcp\", \"port\": 61}, {\"protocol\": \"tcp\", \"port\": 63}, {\"protocol\": \"tcp\", \"port\": 65}, {\"protocol\": \"tcp\", \"port\": 67}, {\"protocol\": \"tcp\", \"port\": 69}, {\"protocol\": \"tcp\", \"port\": 71}, {\"protocol\": \"tcp\", \"port\": 73}, {\"protocol\": \"tcp\", \"port\": 75}, {\"protocol\": \"tcp\", \"port\": 77}, {\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 83}, {\"protocol\": \"tcp\", \"port\": 85}, {\"protocol\": \"tcp\", \"port\": 87}, {\"protocol\": \"tcp\", \"port\": 89}, {\"protocol\": \"tcp\", \"port\": 91}, {\"protocol\": \"tcp\", \"port\": 93}, {\"protocol\": \"tcp\", \"port\": 95}, {\"protocol\": \"tcp\", \"port\": 97}, {\"protocol\": \"tcp\", \"port\": 99}, {\"protocol\": \"tcp\", \"port\": 101}, {\"protocol\": \"tcp\", \"port\": 103}, {\"protocol\": \"tcp\", \"port\": 105}, {\"protocol\": \"tcp\", \"port\": 107}, {\"protocol\": \"tcp\", \"port\": 109}, {\"protocol\": \"tcp\", \"port\": 111}, {\"protocol\": \"tcp\", \"port\": 113}, {\"protocol\": \"tcp\", \"port\": 115}, {\"protocol\": \"tcp\", \"port\": 117}, {\"protocol\": \"tcp\", \"port\": 119}, {\"protocol\": \"tcp\", \"port\": 121}, {\"protocol\": \"tcp\", \"port\": 123}, {\"protocol\": \"tcp\", \"port\": 125}, {\"protocol\": \"tcp\", \"port\": 127}, {\"protocol\": \"tcp\", \"port\": 129}, {\"protocol\": \"tcp\", \"port\": 131}, {\"protocol\": \"tcp\", \"port\": 133}, {\"protocol\": \"tcp\", \"port\": 135}, {\"protocol\": \"tcp\", \"port\": 137}, {\"protocol\": \"tcp\", \"port\": 139}, {\"protocol\": \"tcp\", \"port\": 141}, {\"protocol\": \"tcp\", \"port\": 143}, {\"protocol\": \"tcp\", \"port\": 145}, {\"protocol\": \"tcp\", \"port\": 147}, {\"protocol\": \"tcp\", \"port\": 149}, {\"protocol\": \"tcp\", \"port\": 151}, {\"protocol\": \"tcp\", \"port\": 153}, {\"protocol\": \"tcp\", \"port\": 155}, {\"protocol\": \"tcp\", \"port\": 157}, {\"protocol\": \"tcp\", \"port\": 159}, {\"protocol\": \"tcp\", \"port\": 161}, {\"protocol\": \"tcp\", \"port\": 163}, {\"protocol\": \"tcp\", \"port\": 165}, {\"protocol\": \"tcp\", \"port\": 167}, {\"protocol\": \"tcp\", \"port\": 169}, {\"protocol\": \"tcp\", \"port\": 171}, {\"protocol\": \"tcp\", \"port\": 173}, {\"protocol\": \"tcp\", \"port\": 175}, {\"protocol\": \"tcp\", \"port\": 177}, {\"protocol\": \"tcp\", \"port\": 179}, {\"protocol\": \"tcp\", \"port\": 181}, {\"protocol\": \"tcp\", \"port\": 183}, {\"protocol\": \"tcp\", \"port\": 185}, {\"protocol\": \"tcp\", \"port\": 187}, {\"protocol\": \"tcp\", \"port\": 189}, {\"protocol\": \"tcp\", \"port\": 191}, {\"protocol\": \"tcp\", \"port\": 193}, {\"protocol\": \"tcp\", \"port\": 195}, {\"protocol\": \"tcp\", \"port\": 197}, {\"protocol\": \"tcp\", \"port\": 199}, {\"protocol\": \"tcp\", \"port\": 201}, {\"protocol\": \"tcp\", \"port\": 203}, {\"protocol\": \"tcp\", \"port\": 205}, {\"protocol\": \"tcp\", \"port\": 207}, {\"protocol\": \"tcp\", \"port\": 209}, {\"protocol\": \"tcp\", \"port\": 211}, {\"protocol\": \"tcp\", \"port\": 213}, {\"protocol\": \"tcp\", \"port\": 215}, {\"protocol\": \"tcp\", \"port\": 217}, {\"protocol\": \"tcp\", \"port\": 219}, {\"protocol\": \"tcp\", \"port\": 221}, {\"protocol\": \"tcp\", \"port\": 223}, {\"protocol\": \"tcp\", \"port\": 225}, {\"protocol\": \"tcp\", \"port\": 227}, {\"protocol\": \"tcp\", \"port\": 229}, {\"protocol\": \"tcp\", \"port\": 231}, {\"protocol\": \"tcp\", \"port\": 233}, {\"protocol\": \"tcp\", \"port\": 235}, {\"protocol\": \"tcp\", \"port\": 237}, {\"protocol\": \"tcp\", \"port\": 239}, {\"protocol\": \"tcp\", \"port\": 241}, {\"protocol\": \"tcp\", \"port\": 243}, {\"protocol\": \"tcp\", \"port\": 245}, {\"protocol\": \"tcp\", \"port\": 247}, {\"protocol\": \"tcp\", \"port\": 249}, {\"protocol\": \"tcp\", \"port\": 251}, {\"protocol\": \"tcp\", \"port\": 253}, {\"protocol\": \"tcp\", \"port\": 255}]}, {\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1}, {\"protocol\": \"tcp\", \"port\": 3}, {\"protocol\": \"tcp\", \"port\": 5}, {\"protocol\": \"tcp\", \"port\": 7}, {\"protocol\": \"tcp\", \"port\": 9}, {\"protocol\": \"tcp\", \"port\": 11}, {\"protocol\": \"tcp\", \"port\": 13}, {\"protocol\": \"tcp\", \"port\": 15}, {\"protocol\": \"tcp\", \"port\": 17}, {\"protocol\": \"tcp\", \"port\": 19}, {\"protocol\": \"tcp\", \"port\": 21}, {\"protocol\": \"tcp\", \"port\": 23}, {\"protocol\": \"tcp\", \"port\": 25}, {\"protocol\": \"tcp\", \"port\": 27}, {\"protocol\": \"tcp\", \"port\": 29}, {\"protocol\": \"tcp\", \"port\": 31}, {\"protocol\": \"tcp\", \"port\": 33}, {\"protocol\": \"tcp\", \"port\": 35}, {\"protocol\": \"tcp\", \"port\": 37}, {\"protocol\": \"tcp\", \"port\": 39}, {\"protocol\": \"tcp\", \"port\": 41}, {\"protocol\": \"tcp\", \"port\": 43}, {\"protocol\": \"tcp\", \"port\": 45}, {\"protocol\": \"tcp\", \"port\": 47}, {\"protocol\": \"tcp\", \"port\": 49}, {\"protocol\": \"tcp\", \"port\": 51}, {\"protocol\": \"tcp\", \"port\": 53}, {\"protocol\": \"tcp\", \"port\": 55}, {\"protocol\": \"tcp\", \"port\": 57}, {\"protocol\": \"tcp\", \"port\": 59}, {\"protocol\": \"tcp\", \"port\": 61}, {\"protocol\": \"tcp\", \"port\": 63}, {\"protocol\": \"tcp\", \"port\": 65}, {\"protocol\": \"tcp\", \"port\": 67}, {\"protocol\": \"tcp\", \"port\": 69}, {\"protocol\": \"tcp\", \"port\": 71}, {\"protocol\": \"tcp\", \"port\": 73}, {\"protocol\": \"tcp\", \"port\": 75}, {\"protocol\": \"tcp\", \"port\": 77}, {\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 83}, {\"protocol\": \"tcp\", \"port\": 85}, {\"protocol\": \"tcp\", \"port\": 87}, {\"protocol\": \"tcp\", \"port\": 89}, {\"protocol\": \"tcp\", \"port\": 91}, {\"protocol\": \"tcp\", \"port\": 93}, {\"protocol\": \"tcp\", \"port\": 95}, {\"protocol\": \"tcp\", \"port\": 97}, {\"protocol\": \"tcp\", \"port\": 99}, {\"protocol\": \"tcp\", \"port\": 101}, {\"protocol\": \"tcp\", \"port\": 103}, {\"protocol\": \"tcp\", \"port\": 105}, {\"protocol\": \"tcp\", \"port\": 107}, {\"protocol\": \"tcp\", \"port\": 109}, {\"protocol\": \"tcp\", \"port\": 111}, {\"protocol\": \"tcp\", \"port\": 113}, {\"protocol\": \"tcp\", \"port\": 115}, {\"protocol\": \"tcp\", \"port\": 117}, {\"protocol\": \"tcp\", \"port\": 119}, {\"protocol\": \"tcp\", \"port\": 121}, {\"protocol\": \"tcp\", \"port\": 123}, {\"protocol\": \"tcp\", \"port\": 125}, {\"protocol\": \"tcp\", \"port\": 127}, {\"protocol\": \"tcp\", \"port\": 129}, {\"protocol\": \"tcp\", \"port\": 131}, {\"protocol\": \"tcp\", \"port\": 133}, {\"protocol\": \"tcp\", \"port\": 135}, {\"protocol\": \"tcp\", \"port\": 137}, {\"protocol\": \"tcp\", \"port\": 139}, {\"protocol\": \"tcp\", \"port\": 141}, {\"protocol\": \"tcp\", \"port\": 143}, {\"protocol\": \"tcp\", \"port\": 145}, {\"protocol\": \"tcp\", \"port\": 147}, {\"protocol\": \"tcp\", \"port\": 149}, {\"protocol\": \"tcp\", \"port\": 151}, {\"protocol\": \"tcp\", \"port\": 153}, {\"protocol\": \"tcp\", \"port\": 155}, {\"protocol\": \"tcp\", \"port\": 157}, {\"protocol\": \"tcp\", \"port\": 159}, {\"protocol\": \"tcp\", \"port\": 161}, {\"protocol\": \"tcp\", \"port\": 163}, {\"protocol\": \"tcp\", \"port\": 165}, {\"protocol\": \"tcp\", \"port\": 167}, {\"protocol\": \"tcp\", \"port\": 169}, {\"protocol\": \"tcp\", \"port\": 171}, {\"protocol\": \"tcp\", \"port\": 173}, {\"protocol\": \"tcp\", \"port\": 175}, {\"protocol\": \"tcp\", \"port\": 177}, {\"protocol\": \"tcp\", \"port\": 179}, {\"protocol\": \"tcp\", \"port\": 181}, {\"protocol\": \"tcp\", \"port\": 183}, {\"protocol\": \"tcp\", \"port\": 185}, {\"protocol\": \"tcp\", \"port\": 187}, {\"protocol\": \"tcp\", \"port\": 189}, {\"protocol\": \"tcp\", \"port\": 191}, {\"protocol\": \"tcp\", \"port\": 193}, {\"protocol\": \"tcp\", \"port\": 195}, {\"protocol\": \"tcp\", \"port\": 197}, {\"protocol\": \"tcp\", \"port\": 199}, {\"protocol\": \"tcp\", \"port\": 201}, {\"protocol\": \"tcp\", \"port\": 203}, {\"protocol\": \"tcp\", \"port\": 205}, {\"protocol\": \"tcp\", \"port\": 207}, {\"protocol\": \"tcp\", \"port\": 209}, {\"protocol\": \"tcp\", \"port\": 211}, {\"protocol\": \"tcp\", \"port\": 213}, {\"protocol\": \"tcp\", \"port\": 215}, {\"protocol\": \"tcp\", \"port\": 217}, {\"protocol\": \"tcp\", \"port\": 219}, {\"protocol\": \"tcp\", \"port\": 221}, {\"protocol\": \"tcp\", \"port\": 223}, {\"protocol\": \"tcp\", \"port\": 225}, {\"protocol\": \"tcp\", \"port\": 227}, {\"protocol\": \"tcp\", \"port\": 229}, {\"protocol\": \"tcp\", \"port\": 231}, {\"protocol\": \"tcp\", \"port\": 233}, {\"protocol\": \"tcp\", \"port\": 235}, {\"protocol\": \"tcp\", \"port\": 237}, {\"protocol\": \"tcp\", \"port\": 239}, {\"protocol\": \"tcp\", \"port\": 241}, {\"protocol\": \"tcp\", \"port\": 243}, {\"protocol\": \"tcp\", \"port\": 245}, {\"protocol\": \"tcp\", \"port\": 247}, {\"protocol\": \"tcp\", \"port\": 249}, {\"protocol\": \"tcp\", \"port\": 251}, {\"protocol\": \"tcp\", \"port\": 253}, {\"protocol\": \"tcp\", \"port\": 255}]}]}"}],"expected":{"allow":["192.0.2.1/32:tcp:1","192.0.2.1/32:tcp:3","192.0.2.1/32:tcp:5","192.0.2.1/32:tcp:7","192.0.2.1/32:tcp:9","192.0.2.1/32:tcp:11","192.0.2.1/32:tcp:13","192.0.2.1/32:tcp:15","192.0.2.1/32:tcp:17","192.0.2.1/32:tcp:19","192.0.2.1/32:tcp:21","192.0.2.1/32:tcp:23","192.0.2.1/32:tcp:25","192.0.2.1/32:tcp:27","192.0.2.1/32:tcp:29","192.0.2.1/32:tcp:31","192.0.2.1/32:tcp:33","192.0.2.1/32:tcp:35","192.0.2.1/32:tcp:37","192.0.2.1/32:tcp:39","192.0.2.1/32:tcp:41","192.0.2.1/32:tcp:43","192.0.2.1/32:tcp:45","192.0.2.1/32:tcp:47","192.0.2.1/32:tcp:49","192.0.2.1/32:tcp:51","192.0.2.1/32:tcp:53","192.0.2.1/32:tcp:55","192.0.2.1/32:tcp:57","192.0.2.1/32:tcp:59","192.0.2.1/32:tcp:61","192.0.2.1/32:tcp:63","192.0.2.1/32:tcp:65","192.0.2.1/32:tcp:67","192.0.2.1/32:tcp:69","192.0.2.1/32:tcp:71","192.0.2.1/32:tcp:73","192.0.2.1/32:tcp:75","192.0.2.1/32:tcp:77","192.0.2.1/32:tcp:79","192.0.2.1/32:tcp:81","192.0.2.1/32:tcp:83","192.0.2.1/32:tcp:85","192.0.2.1/32:tcp:87","192.0.2.1/32:tcp:89","192.0.2.1/32:tcp:91","192.0.2.1/32:tcp:93","192.0.2.1/32:tcp:95","192.0.2.1/32:tcp:97","192.0.2.1/32:tcp:99","192.0.2.1/32:tcp:101","192.0.2.1/32:tcp:103","192.0.2.1/32:tcp:105","192.0.2.1/32:tcp:107","192.0.2.1/32:tcp:109","192.0.2.1/32:tcp:111","192.0.2.1/32:tcp:113","192.0.2.1/32:tcp:115","192.0.2.1/32:tcp:117","192.0.2.1/32:tcp:119","192.0.2.1/32:tcp:121","192.0.2.1/32:tcp:123","192.0.2.1/32:tcp:125","192.0.2.1/32:tcp:127","192.0.2.1/32:tcp:129","192.0.2.1/32:tcp:131","192.0.2.1/32:tcp:133","192.0.2.1/32:tcp:135","192.0.2.1/32:tcp:137","192.0.2.1/32:tcp:139","192.0.2.1/32:tcp:141","192.0.2.1/32:tcp:143","192.0.2.1/32:tcp:145","192.0.2.1/32:tcp:147","192.0.2.1/32:tcp:149","192.0.2.1/32:tcp:151","192.0.2.1/32:tcp:153","192.0.2.1/32:tcp:155","192.0.2.1/32:tcp:157","192.0.2.1/32:tcp:159","192.0.2.1/32:tcp:161","192.0.2.1/32:tcp:163","192.0.2.1/32:tcp:165","192.0.2.1/32:tcp:167","192.0.2.1/32:tcp:169","192.0.2.1/32:tcp:171","192.0.2.1/32:tcp:173","192.0.2.1/32:tcp:175","192.0.2.1/32:tcp:177","192.0.2.1/32:tcp:179","192.0.2.1/32:tcp:181","192.0.2.1/32:tcp:183","192.0.2.1/32:tcp:185","192.0.2.1/32:tcp:187","192.0.2.1/32:tcp:189","192.0.2.1/32:tcp:191","192.0.2.1/32:tcp:193","192.0.2.1/32:tcp:195","192.0.2.1/32:tcp:197","192.0.2.1/32:tcp:199","192.0.2.1/32:tcp:201","192.0.2.1/32:tcp:203","192.0.2.1/32:tcp:205","192.0.2.1/32:tcp:207","192.0.2.1/32:tcp:209","192.0.2.1/32:tcp:211","192.0.2.1/32:tcp:213","192.0.2.1/32:tcp:215","192.0.2.1/32:tcp:217","192.0.2.1/32:tcp:219","192.0.2.1/32:tcp:221","192.0.2.1/32:tcp:223","192.0.2.1/32:tcp:225","192.0.2.1/32:tcp:227","192.0.2.1/32:tcp:229","192.0.2.1/32:tcp:231","192.0.2.1/32:tcp:233","192.0.2.1/32:tcp:235","192.0.2.1/32:tcp:237","192.0.2.1/32:tcp:239","192.0.2.1/32:tcp:241","192.0.2.1/32:tcp:243","192.0.2.1/32:tcp:245","192.0.2.1/32:tcp:247","192.0.2.1/32:tcp:249","192.0.2.1/32:tcp:251","192.0.2.1/32:tcp:253","192.0.2.1/32:tcp:255","2001:db8::1/128:tcp:1","2001:db8::1/128:tcp:3","2001:db8::1/128:tcp:5","2001:db8::1/128:tcp:7","2001:db8::1/128:tcp:9","2001:db8::1/128:tcp:11","2001:db8::1/128:tcp:13","2001:db8::1/128:tcp:15","2001:db8::1/128:tcp:17","2001:db8::1/128:tcp:19","2001:db8::1/128:tcp:21","2001:db8::1/128:tcp:23","2001:db8::1/128:tcp:25","2001:db8::1/128:tcp:27","2001:db8::1/128:tcp:29","2001:db8::1/128:tcp:31","2001:db8::1/128:tcp:33","2001:db8::1/128:tcp:35","2001:db8::1/128:tcp:37","2001:db8::1/128:tcp:39","2001:db8::1/128:tcp:41","2001:db8::1/128:tcp:43","2001:db8::1/128:tcp:45","2001:db8::1/128:tcp:47","2001:db8::1/128:tcp:49","2001:db8::1/128:tcp:51","2001:db8::1/128:tcp:53","2001:db8::1/128:tcp:55","2001:db8::1/128:tcp:57","2001:db8::1/128:tcp:59","2001:db8::1/128:tcp:61","2001:db8::1/128:tcp:63","2001:db8::1/128:tcp:65","2001:db8::1/128:tcp:67","2001:db8::1/128:tcp:69","2001:db8::1/128:tcp:71","2001:db8::1/128:tcp:73","2001:db8::1/128:tcp:75","2001:db8::1/128:tcp:77","2001:db8::1/128:tcp:79","2001:db8::1/128:tcp:81","2001:db8::1/128:tcp:83","2001:db8::1/128:tcp:85","2001:db8::1/128:tcp:87","2001:db8::1/128:tcp:89","2001:db8::1/128:tcp:91","2001:db8::1/128:tcp:93","2001:db8::1/128:tcp:95","2001:db8::1/128:tcp:97","2001:db8::1/128:tcp:99","2001:db8::1/128:tcp:101","2001:db8::1/128:tcp:103","2001:db8::1/128:tcp:105","2001:db8::1/128:tcp:107","2001:db8::1/128:tcp:109","2001:db8::1/128:tcp:111","2001:db8::1/128:tcp:113","2001:db8::1/128:tcp:115","2001:db8::1/128:tcp:117","2001:db8::1/128:tcp:119","2001:db8::1/128:tcp:121","2001:db8::1/128:tcp:123","2001:db8::1/128:tcp:125","2001:db8::1/128:tcp:127","2001:db8::1/128:tcp:129","2001:db8::1/128:tcp:131","2001:db8::1/128:tcp:133","2001:db8::1/128:tcp:135","2001:db8::1/128:tcp:137","2001:db8::1/128:tcp:139","2001:db8::1/128:tcp:141","2001:db8::1/128:tcp:143","2001:db8::1/128:tcp:145","2001:db8::1/128:tcp:147","2001:db8::1/128:tcp:149","2001:db8::1/128:tcp:151","2001:db8::1/128:tcp:153","2001:db8::1/128:tcp:155","2001:db8::1/128:tcp:157","2001:db8::1/128:tcp:159","2001:db8::1/128:tcp:161","2001:db8::1/128:tcp:163","2001:db8::1/128:tcp:165","2001:db8::1/128:tcp:167","2001:db8::1/128:tcp:169","2001:db8::1/128:tcp:171","2001:db8::1/128:tcp:173","2001:db8::1/128:tcp:175","2001:db8::1/128:tcp:177","2001:db8::1/128:tcp:179","2001:db8::1/128:tcp:181","2001:db8::1/128:tcp:183","2001:db8::1/128:tcp:185","2001:db8::1/128:tcp:187","2001:db8::1/128:tcp:189","2001:db8::1/128:tcp:191","2001:db8::1/128:tcp:193","2001:db8::1/128:tcp:195","2001:db8::1/128:tcp:197","2001:db8::1/128:tcp:199","2001:db8::1/128:tcp:201","2001:db8::1/128:tcp:203","2001:db8::1/128:tcp:205","2001:db8::1/128:tcp:207","2001:db8::1/128:tcp:209","2001:db8::1/128:tcp:211","2001:db8::1/128:tcp:213","2001:db8::1/128:tcp:215","2001:db8::1/128:tcp:217","2001:db8::1/128:tcp:219","2001:db8::1/128:tcp:221","2001:db8::1/128:tcp:223","2001:db8::1/128:tcp:225","2001:db8::1/128:tcp:227","2001:db8::1/128:tcp:229","2001:db8::1/128:tcp:231","2001:db8::1/128:tcp:233","2001:db8::1/128:tcp:235","2001:db8::1/128:tcp:237","2001:db8::1/128:tcp:239","2001:db8::1/128:tcp:241","2001:db8::1/128:tcp:243","2001:db8::1/128:tcp:245","2001:db8::1/128:tcp:247","2001:db8::1/128:tcp:249","2001:db8::1/128:tcp:251","2001:db8::1/128:tcp:253","2001:db8::1/128:tcp:255"],"deny":[]}}, + {"name":"vector: test_both_families_share_the_native_rule_budget #2","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1}, {\"protocol\": \"tcp\", \"port\": 3}, {\"protocol\": \"tcp\", \"port\": 5}, {\"protocol\": \"tcp\", \"port\": 7}, {\"protocol\": \"tcp\", \"port\": 9}, {\"protocol\": \"tcp\", \"port\": 11}, {\"protocol\": \"tcp\", \"port\": 13}, {\"protocol\": \"tcp\", \"port\": 15}, {\"protocol\": \"tcp\", \"port\": 17}, {\"protocol\": \"tcp\", \"port\": 19}, {\"protocol\": \"tcp\", \"port\": 21}, {\"protocol\": \"tcp\", \"port\": 23}, {\"protocol\": \"tcp\", \"port\": 25}, {\"protocol\": \"tcp\", \"port\": 27}, {\"protocol\": \"tcp\", \"port\": 29}, {\"protocol\": \"tcp\", \"port\": 31}, {\"protocol\": \"tcp\", \"port\": 33}, {\"protocol\": \"tcp\", \"port\": 35}, {\"protocol\": \"tcp\", \"port\": 37}, {\"protocol\": \"tcp\", \"port\": 39}, {\"protocol\": \"tcp\", \"port\": 41}, {\"protocol\": \"tcp\", \"port\": 43}, {\"protocol\": \"tcp\", \"port\": 45}, {\"protocol\": \"tcp\", \"port\": 47}, {\"protocol\": \"tcp\", \"port\": 49}, {\"protocol\": \"tcp\", \"port\": 51}, {\"protocol\": \"tcp\", \"port\": 53}, {\"protocol\": \"tcp\", \"port\": 55}, {\"protocol\": \"tcp\", \"port\": 57}, {\"protocol\": \"tcp\", \"port\": 59}, {\"protocol\": \"tcp\", \"port\": 61}, {\"protocol\": \"tcp\", \"port\": 63}, {\"protocol\": \"tcp\", \"port\": 65}, {\"protocol\": \"tcp\", \"port\": 67}, {\"protocol\": \"tcp\", \"port\": 69}, {\"protocol\": \"tcp\", \"port\": 71}, {\"protocol\": \"tcp\", \"port\": 73}, {\"protocol\": \"tcp\", \"port\": 75}, {\"protocol\": \"tcp\", \"port\": 77}, {\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 83}, {\"protocol\": \"tcp\", \"port\": 85}, {\"protocol\": \"tcp\", \"port\": 87}, {\"protocol\": \"tcp\", \"port\": 89}, {\"protocol\": \"tcp\", \"port\": 91}, {\"protocol\": \"tcp\", \"port\": 93}, {\"protocol\": \"tcp\", \"port\": 95}, {\"protocol\": \"tcp\", \"port\": 97}, {\"protocol\": \"tcp\", \"port\": 99}, {\"protocol\": \"tcp\", \"port\": 101}, {\"protocol\": \"tcp\", \"port\": 103}, {\"protocol\": \"tcp\", \"port\": 105}, {\"protocol\": \"tcp\", \"port\": 107}, {\"protocol\": \"tcp\", \"port\": 109}, {\"protocol\": \"tcp\", \"port\": 111}, {\"protocol\": \"tcp\", \"port\": 113}, {\"protocol\": \"tcp\", \"port\": 115}, {\"protocol\": \"tcp\", \"port\": 117}, {\"protocol\": \"tcp\", \"port\": 119}, {\"protocol\": \"tcp\", \"port\": 121}, {\"protocol\": \"tcp\", \"port\": 123}, {\"protocol\": \"tcp\", \"port\": 125}, {\"protocol\": \"tcp\", \"port\": 127}, {\"protocol\": \"tcp\", \"port\": 129}, {\"protocol\": \"tcp\", \"port\": 131}, {\"protocol\": \"tcp\", \"port\": 133}, {\"protocol\": \"tcp\", \"port\": 135}, {\"protocol\": \"tcp\", \"port\": 137}, {\"protocol\": \"tcp\", \"port\": 139}, {\"protocol\": \"tcp\", \"port\": 141}, {\"protocol\": \"tcp\", \"port\": 143}, {\"protocol\": \"tcp\", \"port\": 145}, {\"protocol\": \"tcp\", \"port\": 147}, {\"protocol\": \"tcp\", \"port\": 149}, {\"protocol\": \"tcp\", \"port\": 151}, {\"protocol\": \"tcp\", \"port\": 153}, {\"protocol\": \"tcp\", \"port\": 155}, {\"protocol\": \"tcp\", \"port\": 157}, {\"protocol\": \"tcp\", \"port\": 159}, {\"protocol\": \"tcp\", \"port\": 161}, {\"protocol\": \"tcp\", \"port\": 163}, {\"protocol\": \"tcp\", \"port\": 165}, {\"protocol\": \"tcp\", \"port\": 167}, {\"protocol\": \"tcp\", \"port\": 169}, {\"protocol\": \"tcp\", \"port\": 171}, {\"protocol\": \"tcp\", \"port\": 173}, {\"protocol\": \"tcp\", \"port\": 175}, {\"protocol\": \"tcp\", \"port\": 177}, {\"protocol\": \"tcp\", \"port\": 179}, {\"protocol\": \"tcp\", \"port\": 181}, {\"protocol\": \"tcp\", \"port\": 183}, {\"protocol\": \"tcp\", \"port\": 185}, {\"protocol\": \"tcp\", \"port\": 187}, {\"protocol\": \"tcp\", \"port\": 189}, {\"protocol\": \"tcp\", \"port\": 191}, {\"protocol\": \"tcp\", \"port\": 193}, {\"protocol\": \"tcp\", \"port\": 195}, {\"protocol\": \"tcp\", \"port\": 197}, {\"protocol\": \"tcp\", \"port\": 199}, {\"protocol\": \"tcp\", \"port\": 201}, {\"protocol\": \"tcp\", \"port\": 203}, {\"protocol\": \"tcp\", \"port\": 205}, {\"protocol\": \"tcp\", \"port\": 207}, {\"protocol\": \"tcp\", \"port\": 209}, {\"protocol\": \"tcp\", \"port\": 211}, {\"protocol\": \"tcp\", \"port\": 213}, {\"protocol\": \"tcp\", \"port\": 215}, {\"protocol\": \"tcp\", \"port\": 217}, {\"protocol\": \"tcp\", \"port\": 219}, {\"protocol\": \"tcp\", \"port\": 221}, {\"protocol\": \"tcp\", \"port\": 223}, {\"protocol\": \"tcp\", \"port\": 225}, {\"protocol\": \"tcp\", \"port\": 227}, {\"protocol\": \"tcp\", \"port\": 229}, {\"protocol\": \"tcp\", \"port\": 231}, {\"protocol\": \"tcp\", \"port\": 233}, {\"protocol\": \"tcp\", \"port\": 235}, {\"protocol\": \"tcp\", \"port\": 237}, {\"protocol\": \"tcp\", \"port\": 239}, {\"protocol\": \"tcp\", \"port\": 241}, {\"protocol\": \"tcp\", \"port\": 243}, {\"protocol\": \"tcp\", \"port\": 245}, {\"protocol\": \"tcp\", \"port\": 247}, {\"protocol\": \"tcp\", \"port\": 249}, {\"protocol\": \"tcp\", \"port\": 251}, {\"protocol\": \"tcp\", \"port\": 253}, {\"protocol\": \"tcp\", \"port\": 255}, {\"protocol\": \"tcp\", \"port\": 257}]}, {\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1}, {\"protocol\": \"tcp\", \"port\": 3}, {\"protocol\": \"tcp\", \"port\": 5}, {\"protocol\": \"tcp\", \"port\": 7}, {\"protocol\": \"tcp\", \"port\": 9}, {\"protocol\": \"tcp\", \"port\": 11}, {\"protocol\": \"tcp\", \"port\": 13}, {\"protocol\": \"tcp\", \"port\": 15}, {\"protocol\": \"tcp\", \"port\": 17}, {\"protocol\": \"tcp\", \"port\": 19}, {\"protocol\": \"tcp\", \"port\": 21}, {\"protocol\": \"tcp\", \"port\": 23}, {\"protocol\": \"tcp\", \"port\": 25}, {\"protocol\": \"tcp\", \"port\": 27}, {\"protocol\": \"tcp\", \"port\": 29}, {\"protocol\": \"tcp\", \"port\": 31}, {\"protocol\": \"tcp\", \"port\": 33}, {\"protocol\": \"tcp\", \"port\": 35}, {\"protocol\": \"tcp\", \"port\": 37}, {\"protocol\": \"tcp\", \"port\": 39}, {\"protocol\": \"tcp\", \"port\": 41}, {\"protocol\": \"tcp\", \"port\": 43}, {\"protocol\": \"tcp\", \"port\": 45}, {\"protocol\": \"tcp\", \"port\": 47}, {\"protocol\": \"tcp\", \"port\": 49}, {\"protocol\": \"tcp\", \"port\": 51}, {\"protocol\": \"tcp\", \"port\": 53}, {\"protocol\": \"tcp\", \"port\": 55}, {\"protocol\": \"tcp\", \"port\": 57}, {\"protocol\": \"tcp\", \"port\": 59}, {\"protocol\": \"tcp\", \"port\": 61}, {\"protocol\": \"tcp\", \"port\": 63}, {\"protocol\": \"tcp\", \"port\": 65}, {\"protocol\": \"tcp\", \"port\": 67}, {\"protocol\": \"tcp\", \"port\": 69}, {\"protocol\": \"tcp\", \"port\": 71}, {\"protocol\": \"tcp\", \"port\": 73}, {\"protocol\": \"tcp\", \"port\": 75}, {\"protocol\": \"tcp\", \"port\": 77}, {\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 83}, {\"protocol\": \"tcp\", \"port\": 85}, {\"protocol\": \"tcp\", \"port\": 87}, {\"protocol\": \"tcp\", \"port\": 89}, {\"protocol\": \"tcp\", \"port\": 91}, {\"protocol\": \"tcp\", \"port\": 93}, {\"protocol\": \"tcp\", \"port\": 95}, {\"protocol\": \"tcp\", \"port\": 97}, {\"protocol\": \"tcp\", \"port\": 99}, {\"protocol\": \"tcp\", \"port\": 101}, {\"protocol\": \"tcp\", \"port\": 103}, {\"protocol\": \"tcp\", \"port\": 105}, {\"protocol\": \"tcp\", \"port\": 107}, {\"protocol\": \"tcp\", \"port\": 109}, {\"protocol\": \"tcp\", \"port\": 111}, {\"protocol\": \"tcp\", \"port\": 113}, {\"protocol\": \"tcp\", \"port\": 115}, {\"protocol\": \"tcp\", \"port\": 117}, {\"protocol\": \"tcp\", \"port\": 119}, {\"protocol\": \"tcp\", \"port\": 121}, {\"protocol\": \"tcp\", \"port\": 123}, {\"protocol\": \"tcp\", \"port\": 125}, {\"protocol\": \"tcp\", \"port\": 127}, {\"protocol\": \"tcp\", \"port\": 129}, {\"protocol\": \"tcp\", \"port\": 131}, {\"protocol\": \"tcp\", \"port\": 133}, {\"protocol\": \"tcp\", \"port\": 135}, {\"protocol\": \"tcp\", \"port\": 137}, {\"protocol\": \"tcp\", \"port\": 139}, {\"protocol\": \"tcp\", \"port\": 141}, {\"protocol\": \"tcp\", \"port\": 143}, {\"protocol\": \"tcp\", \"port\": 145}, {\"protocol\": \"tcp\", \"port\": 147}, {\"protocol\": \"tcp\", \"port\": 149}, {\"protocol\": \"tcp\", \"port\": 151}, {\"protocol\": \"tcp\", \"port\": 153}, {\"protocol\": \"tcp\", \"port\": 155}, {\"protocol\": \"tcp\", \"port\": 157}, {\"protocol\": \"tcp\", \"port\": 159}, {\"protocol\": \"tcp\", \"port\": 161}, {\"protocol\": \"tcp\", \"port\": 163}, {\"protocol\": \"tcp\", \"port\": 165}, {\"protocol\": \"tcp\", \"port\": 167}, {\"protocol\": \"tcp\", \"port\": 169}, {\"protocol\": \"tcp\", \"port\": 171}, {\"protocol\": \"tcp\", \"port\": 173}, {\"protocol\": \"tcp\", \"port\": 175}, {\"protocol\": \"tcp\", \"port\": 177}, {\"protocol\": \"tcp\", \"port\": 179}, {\"protocol\": \"tcp\", \"port\": 181}, {\"protocol\": \"tcp\", \"port\": 183}, {\"protocol\": \"tcp\", \"port\": 185}, {\"protocol\": \"tcp\", \"port\": 187}, {\"protocol\": \"tcp\", \"port\": 189}, {\"protocol\": \"tcp\", \"port\": 191}, {\"protocol\": \"tcp\", \"port\": 193}, {\"protocol\": \"tcp\", \"port\": 195}, {\"protocol\": \"tcp\", \"port\": 197}, {\"protocol\": \"tcp\", \"port\": 199}, {\"protocol\": \"tcp\", \"port\": 201}, {\"protocol\": \"tcp\", \"port\": 203}, {\"protocol\": \"tcp\", \"port\": 205}, {\"protocol\": \"tcp\", \"port\": 207}, {\"protocol\": \"tcp\", \"port\": 209}, {\"protocol\": \"tcp\", \"port\": 211}, {\"protocol\": \"tcp\", \"port\": 213}, {\"protocol\": \"tcp\", \"port\": 215}, {\"protocol\": \"tcp\", \"port\": 217}, {\"protocol\": \"tcp\", \"port\": 219}, {\"protocol\": \"tcp\", \"port\": 221}, {\"protocol\": \"tcp\", \"port\": 223}, {\"protocol\": \"tcp\", \"port\": 225}, {\"protocol\": \"tcp\", \"port\": 227}, {\"protocol\": \"tcp\", \"port\": 229}, {\"protocol\": \"tcp\", \"port\": 231}, {\"protocol\": \"tcp\", \"port\": 233}, {\"protocol\": \"tcp\", \"port\": 235}, {\"protocol\": \"tcp\", \"port\": 237}, {\"protocol\": \"tcp\", \"port\": 239}, {\"protocol\": \"tcp\", \"port\": 241}, {\"protocol\": \"tcp\", \"port\": 243}, {\"protocol\": \"tcp\", \"port\": 245}, {\"protocol\": \"tcp\", \"port\": 247}, {\"protocol\": \"tcp\", \"port\": 249}, {\"protocol\": \"tcp\", \"port\": 251}, {\"protocol\": \"tcp\", \"port\": 253}, {\"protocol\": \"tcp\", \"port\": 255}]}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"vector: test_both_families_share_the_native_rule_budget #3","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1}, {\"protocol\": \"tcp\", \"port\": 3}, {\"protocol\": \"tcp\", \"port\": 5}, {\"protocol\": \"tcp\", \"port\": 7}, {\"protocol\": \"tcp\", \"port\": 9}, {\"protocol\": \"tcp\", \"port\": 11}, {\"protocol\": \"tcp\", \"port\": 13}, {\"protocol\": \"tcp\", \"port\": 15}, {\"protocol\": \"tcp\", \"port\": 17}, {\"protocol\": \"tcp\", \"port\": 19}, {\"protocol\": \"tcp\", \"port\": 21}, {\"protocol\": \"tcp\", \"port\": 23}, {\"protocol\": \"tcp\", \"port\": 25}, {\"protocol\": \"tcp\", \"port\": 27}, {\"protocol\": \"tcp\", \"port\": 29}, {\"protocol\": \"tcp\", \"port\": 31}, {\"protocol\": \"tcp\", \"port\": 33}, {\"protocol\": \"tcp\", \"port\": 35}, {\"protocol\": \"tcp\", \"port\": 37}, {\"protocol\": \"tcp\", \"port\": 39}, {\"protocol\": \"tcp\", \"port\": 41}, {\"protocol\": \"tcp\", \"port\": 43}, {\"protocol\": \"tcp\", \"port\": 45}, {\"protocol\": \"tcp\", \"port\": 47}, {\"protocol\": \"tcp\", \"port\": 49}, {\"protocol\": \"tcp\", \"port\": 51}, {\"protocol\": \"tcp\", \"port\": 53}, {\"protocol\": \"tcp\", \"port\": 55}, {\"protocol\": \"tcp\", \"port\": 57}, {\"protocol\": \"tcp\", \"port\": 59}, {\"protocol\": \"tcp\", \"port\": 61}, {\"protocol\": \"tcp\", \"port\": 63}, {\"protocol\": \"tcp\", \"port\": 65}, {\"protocol\": \"tcp\", \"port\": 67}, {\"protocol\": \"tcp\", \"port\": 69}, {\"protocol\": \"tcp\", \"port\": 71}, {\"protocol\": \"tcp\", \"port\": 73}, {\"protocol\": \"tcp\", \"port\": 75}, {\"protocol\": \"tcp\", \"port\": 77}, {\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 83}, {\"protocol\": \"tcp\", \"port\": 85}, {\"protocol\": \"tcp\", \"port\": 87}, {\"protocol\": \"tcp\", \"port\": 89}, {\"protocol\": \"tcp\", \"port\": 91}, {\"protocol\": \"tcp\", \"port\": 93}, {\"protocol\": \"tcp\", \"port\": 95}, {\"protocol\": \"tcp\", \"port\": 97}, {\"protocol\": \"tcp\", \"port\": 99}, {\"protocol\": \"tcp\", \"port\": 101}, {\"protocol\": \"tcp\", \"port\": 103}, {\"protocol\": \"tcp\", \"port\": 105}, {\"protocol\": \"tcp\", \"port\": 107}, {\"protocol\": \"tcp\", \"port\": 109}, {\"protocol\": \"tcp\", \"port\": 111}, {\"protocol\": \"tcp\", \"port\": 113}, {\"protocol\": \"tcp\", \"port\": 115}, {\"protocol\": \"tcp\", \"port\": 117}, {\"protocol\": \"tcp\", \"port\": 119}, {\"protocol\": \"tcp\", \"port\": 121}, {\"protocol\": \"tcp\", \"port\": 123}, {\"protocol\": \"tcp\", \"port\": 125}, {\"protocol\": \"tcp\", \"port\": 127}, {\"protocol\": \"tcp\", \"port\": 129}, {\"protocol\": \"tcp\", \"port\": 131}, {\"protocol\": \"tcp\", \"port\": 133}, {\"protocol\": \"tcp\", \"port\": 135}, {\"protocol\": \"tcp\", \"port\": 137}, {\"protocol\": \"tcp\", \"port\": 139}, {\"protocol\": \"tcp\", \"port\": 141}, {\"protocol\": \"tcp\", \"port\": 143}, {\"protocol\": \"tcp\", \"port\": 145}, {\"protocol\": \"tcp\", \"port\": 147}, {\"protocol\": \"tcp\", \"port\": 149}, {\"protocol\": \"tcp\", \"port\": 151}, {\"protocol\": \"tcp\", \"port\": 153}, {\"protocol\": \"tcp\", \"port\": 155}, {\"protocol\": \"tcp\", \"port\": 157}, {\"protocol\": \"tcp\", \"port\": 159}, {\"protocol\": \"tcp\", \"port\": 161}, {\"protocol\": \"tcp\", \"port\": 163}, {\"protocol\": \"tcp\", \"port\": 165}, {\"protocol\": \"tcp\", \"port\": 167}, {\"protocol\": \"tcp\", \"port\": 169}, {\"protocol\": \"tcp\", \"port\": 171}, {\"protocol\": \"tcp\", \"port\": 173}, {\"protocol\": \"tcp\", \"port\": 175}, {\"protocol\": \"tcp\", \"port\": 177}, {\"protocol\": \"tcp\", \"port\": 179}, {\"protocol\": \"tcp\", \"port\": 181}, {\"protocol\": \"tcp\", \"port\": 183}, {\"protocol\": \"tcp\", \"port\": 185}, {\"protocol\": \"tcp\", \"port\": 187}, {\"protocol\": \"tcp\", \"port\": 189}, {\"protocol\": \"tcp\", \"port\": 191}, {\"protocol\": \"tcp\", \"port\": 193}, {\"protocol\": \"tcp\", \"port\": 195}, {\"protocol\": \"tcp\", \"port\": 197}, {\"protocol\": \"tcp\", \"port\": 199}, {\"protocol\": \"tcp\", \"port\": 201}, {\"protocol\": \"tcp\", \"port\": 203}, {\"protocol\": \"tcp\", \"port\": 205}, {\"protocol\": \"tcp\", \"port\": 207}, {\"protocol\": \"tcp\", \"port\": 209}, {\"protocol\": \"tcp\", \"port\": 211}, {\"protocol\": \"tcp\", \"port\": 213}, {\"protocol\": \"tcp\", \"port\": 215}, {\"protocol\": \"tcp\", \"port\": 217}, {\"protocol\": \"tcp\", \"port\": 219}, {\"protocol\": \"tcp\", \"port\": 221}, {\"protocol\": \"tcp\", \"port\": 223}, {\"protocol\": \"tcp\", \"port\": 225}, {\"protocol\": \"tcp\", \"port\": 227}, {\"protocol\": \"tcp\", \"port\": 229}, {\"protocol\": \"tcp\", \"port\": 231}, {\"protocol\": \"tcp\", \"port\": 233}, {\"protocol\": \"tcp\", \"port\": 235}, {\"protocol\": \"tcp\", \"port\": 237}, {\"protocol\": \"tcp\", \"port\": 239}, {\"protocol\": \"tcp\", \"port\": 241}, {\"protocol\": \"tcp\", \"port\": 243}, {\"protocol\": \"tcp\", \"port\": 245}, {\"protocol\": \"tcp\", \"port\": 247}, {\"protocol\": \"tcp\", \"port\": 249}, {\"protocol\": \"tcp\", \"port\": 251}, {\"protocol\": \"tcp\", \"port\": 253}, {\"protocol\": \"tcp\", \"port\": 255}]}, {\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1}, {\"protocol\": \"tcp\", \"port\": 3}, {\"protocol\": \"tcp\", \"port\": 5}, {\"protocol\": \"tcp\", \"port\": 7}, {\"protocol\": \"tcp\", \"port\": 9}, {\"protocol\": \"tcp\", \"port\": 11}, {\"protocol\": \"tcp\", \"port\": 13}, {\"protocol\": \"tcp\", \"port\": 15}, {\"protocol\": \"tcp\", \"port\": 17}, {\"protocol\": \"tcp\", \"port\": 19}, {\"protocol\": \"tcp\", \"port\": 21}, {\"protocol\": \"tcp\", \"port\": 23}, {\"protocol\": \"tcp\", \"port\": 25}, {\"protocol\": \"tcp\", \"port\": 27}, {\"protocol\": \"tcp\", \"port\": 29}, {\"protocol\": \"tcp\", \"port\": 31}, {\"protocol\": \"tcp\", \"port\": 33}, {\"protocol\": \"tcp\", \"port\": 35}, {\"protocol\": \"tcp\", \"port\": 37}, {\"protocol\": \"tcp\", \"port\": 39}, {\"protocol\": \"tcp\", \"port\": 41}, {\"protocol\": \"tcp\", \"port\": 43}, {\"protocol\": \"tcp\", \"port\": 45}, {\"protocol\": \"tcp\", \"port\": 47}, {\"protocol\": \"tcp\", \"port\": 49}, {\"protocol\": \"tcp\", \"port\": 51}, {\"protocol\": \"tcp\", \"port\": 53}, {\"protocol\": \"tcp\", \"port\": 55}, {\"protocol\": \"tcp\", \"port\": 57}, {\"protocol\": \"tcp\", \"port\": 59}, {\"protocol\": \"tcp\", \"port\": 61}, {\"protocol\": \"tcp\", \"port\": 63}, {\"protocol\": \"tcp\", \"port\": 65}, {\"protocol\": \"tcp\", \"port\": 67}, {\"protocol\": \"tcp\", \"port\": 69}, {\"protocol\": \"tcp\", \"port\": 71}, {\"protocol\": \"tcp\", \"port\": 73}, {\"protocol\": \"tcp\", \"port\": 75}, {\"protocol\": \"tcp\", \"port\": 77}, {\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 83}, {\"protocol\": \"tcp\", \"port\": 85}, {\"protocol\": \"tcp\", \"port\": 87}, {\"protocol\": \"tcp\", \"port\": 89}, {\"protocol\": \"tcp\", \"port\": 91}, {\"protocol\": \"tcp\", \"port\": 93}, {\"protocol\": \"tcp\", \"port\": 95}, {\"protocol\": \"tcp\", \"port\": 97}, {\"protocol\": \"tcp\", \"port\": 99}, {\"protocol\": \"tcp\", \"port\": 101}, {\"protocol\": \"tcp\", \"port\": 103}, {\"protocol\": \"tcp\", \"port\": 105}, {\"protocol\": \"tcp\", \"port\": 107}, {\"protocol\": \"tcp\", \"port\": 109}, {\"protocol\": \"tcp\", \"port\": 111}, {\"protocol\": \"tcp\", \"port\": 113}, {\"protocol\": \"tcp\", \"port\": 115}, {\"protocol\": \"tcp\", \"port\": 117}, {\"protocol\": \"tcp\", \"port\": 119}, {\"protocol\": \"tcp\", \"port\": 121}, {\"protocol\": \"tcp\", \"port\": 123}, {\"protocol\": \"tcp\", \"port\": 125}, {\"protocol\": \"tcp\", \"port\": 127}, {\"protocol\": \"tcp\", \"port\": 129}, {\"protocol\": \"tcp\", \"port\": 131}, {\"protocol\": \"tcp\", \"port\": 133}, {\"protocol\": \"tcp\", \"port\": 135}, {\"protocol\": \"tcp\", \"port\": 137}, {\"protocol\": \"tcp\", \"port\": 139}, {\"protocol\": \"tcp\", \"port\": 141}, {\"protocol\": \"tcp\", \"port\": 143}, {\"protocol\": \"tcp\", \"port\": 145}, {\"protocol\": \"tcp\", \"port\": 147}, {\"protocol\": \"tcp\", \"port\": 149}, {\"protocol\": \"tcp\", \"port\": 151}, {\"protocol\": \"tcp\", \"port\": 153}, {\"protocol\": \"tcp\", \"port\": 155}, {\"protocol\": \"tcp\", \"port\": 157}, {\"protocol\": \"tcp\", \"port\": 159}, {\"protocol\": \"tcp\", \"port\": 161}, {\"protocol\": \"tcp\", \"port\": 163}, {\"protocol\": \"tcp\", \"port\": 165}, {\"protocol\": \"tcp\", \"port\": 167}, {\"protocol\": \"tcp\", \"port\": 169}, {\"protocol\": \"tcp\", \"port\": 171}, {\"protocol\": \"tcp\", \"port\": 173}, {\"protocol\": \"tcp\", \"port\": 175}, {\"protocol\": \"tcp\", \"port\": 177}, {\"protocol\": \"tcp\", \"port\": 179}, {\"protocol\": \"tcp\", \"port\": 181}, {\"protocol\": \"tcp\", \"port\": 183}, {\"protocol\": \"tcp\", \"port\": 185}, {\"protocol\": \"tcp\", \"port\": 187}, {\"protocol\": \"tcp\", \"port\": 189}, {\"protocol\": \"tcp\", \"port\": 191}, {\"protocol\": \"tcp\", \"port\": 193}, {\"protocol\": \"tcp\", \"port\": 195}, {\"protocol\": \"tcp\", \"port\": 197}, {\"protocol\": \"tcp\", \"port\": 199}, {\"protocol\": \"tcp\", \"port\": 201}, {\"protocol\": \"tcp\", \"port\": 203}, {\"protocol\": \"tcp\", \"port\": 205}, {\"protocol\": \"tcp\", \"port\": 207}, {\"protocol\": \"tcp\", \"port\": 209}, {\"protocol\": \"tcp\", \"port\": 211}, {\"protocol\": \"tcp\", \"port\": 213}, {\"protocol\": \"tcp\", \"port\": 215}, {\"protocol\": \"tcp\", \"port\": 217}, {\"protocol\": \"tcp\", \"port\": 219}, {\"protocol\": \"tcp\", \"port\": 221}, {\"protocol\": \"tcp\", \"port\": 223}, {\"protocol\": \"tcp\", \"port\": 225}, {\"protocol\": \"tcp\", \"port\": 227}, {\"protocol\": \"tcp\", \"port\": 229}, {\"protocol\": \"tcp\", \"port\": 231}, {\"protocol\": \"tcp\", \"port\": 233}, {\"protocol\": \"tcp\", \"port\": 235}, {\"protocol\": \"tcp\", \"port\": 237}, {\"protocol\": \"tcp\", \"port\": 239}, {\"protocol\": \"tcp\", \"port\": 241}, {\"protocol\": \"tcp\", \"port\": 243}, {\"protocol\": \"tcp\", \"port\": 245}, {\"protocol\": \"tcp\", \"port\": 247}, {\"protocol\": \"tcp\", \"port\": 249}, {\"protocol\": \"tcp\", \"port\": 251}, {\"protocol\": \"tcp\", \"port\": 253}, {\"protocol\": \"tcp\", \"port\": 255}, {\"protocol\": \"tcp\", \"port\": 257}]}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"vector: test_bounds_exclusions_times_port_segments #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 1}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 3}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 5}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 7}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 9}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 11}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 13}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 15}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 17}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 21}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 23}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 25}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 27}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 29}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 31}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 33}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 35}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 37}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 39}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 41}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 43}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 45}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 47}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 49}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 51}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 53}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 55}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 57}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 59}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 61}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 63}]}"}],"expected":{"allow":["192.0.2.0/32:tcp:1","192.0.2.0/32:tcp:3","192.0.2.0/32:tcp:5","192.0.2.0/32:tcp:7","192.0.2.0/32:tcp:9","192.0.2.0/32:tcp:11","192.0.2.0/32:tcp:13","192.0.2.0/32:tcp:15","192.0.2.0/32:tcp:17","192.0.2.0/32:tcp:19","192.0.2.0/32:tcp:21","192.0.2.0/32:tcp:23","192.0.2.0/32:tcp:25","192.0.2.0/32:tcp:27","192.0.2.0/32:tcp:29","192.0.2.0/32:tcp:31","192.0.2.0/32:tcp:33","192.0.2.0/32:tcp:35","192.0.2.0/32:tcp:37","192.0.2.0/32:tcp:39","192.0.2.0/32:tcp:41","192.0.2.0/32:tcp:43","192.0.2.0/32:tcp:45","192.0.2.0/32:tcp:47","192.0.2.0/32:tcp:49","192.0.2.0/32:tcp:51","192.0.2.0/32:tcp:53","192.0.2.0/32:tcp:55","192.0.2.0/32:tcp:57","192.0.2.0/32:tcp:59","192.0.2.0/32:tcp:61","192.0.2.0/32:tcp:63","192.0.2.2/31:tcp:1","192.0.2.2/31:tcp:3","192.0.2.2/31:tcp:5","192.0.2.2/31:tcp:7","192.0.2.2/31:tcp:9","192.0.2.2/31:tcp:11","192.0.2.2/31:tcp:13","192.0.2.2/31:tcp:15","192.0.2.2/31:tcp:17","192.0.2.2/31:tcp:19","192.0.2.2/31:tcp:21","192.0.2.2/31:tcp:23","192.0.2.2/31:tcp:25","192.0.2.2/31:tcp:27","192.0.2.2/31:tcp:29","192.0.2.2/31:tcp:31","192.0.2.2/31:tcp:33","192.0.2.2/31:tcp:35","192.0.2.2/31:tcp:37","192.0.2.2/31:tcp:39","192.0.2.2/31:tcp:41","192.0.2.2/31:tcp:43","192.0.2.2/31:tcp:45","192.0.2.2/31:tcp:47","192.0.2.2/31:tcp:49","192.0.2.2/31:tcp:51","192.0.2.2/31:tcp:53","192.0.2.2/31:tcp:55","192.0.2.2/31:tcp:57","192.0.2.2/31:tcp:59","192.0.2.2/31:tcp:61","192.0.2.2/31:tcp:63","192.0.2.4/30:tcp:1","192.0.2.4/30:tcp:3","192.0.2.4/30:tcp:5","192.0.2.4/30:tcp:7","192.0.2.4/30:tcp:9","192.0.2.4/30:tcp:11","192.0.2.4/30:tcp:13","192.0.2.4/30:tcp:15","192.0.2.4/30:tcp:17","192.0.2.4/30:tcp:19","192.0.2.4/30:tcp:21","192.0.2.4/30:tcp:23","192.0.2.4/30:tcp:25","192.0.2.4/30:tcp:27","192.0.2.4/30:tcp:29","192.0.2.4/30:tcp:31","192.0.2.4/30:tcp:33","192.0.2.4/30:tcp:35","192.0.2.4/30:tcp:37","192.0.2.4/30:tcp:39","192.0.2.4/30:tcp:41","192.0.2.4/30:tcp:43","192.0.2.4/30:tcp:45","192.0.2.4/30:tcp:47","192.0.2.4/30:tcp:49","192.0.2.4/30:tcp:51","192.0.2.4/30:tcp:53","192.0.2.4/30:tcp:55","192.0.2.4/30:tcp:57","192.0.2.4/30:tcp:59","192.0.2.4/30:tcp:61","192.0.2.4/30:tcp:63","192.0.2.8/29:tcp:1","192.0.2.8/29:tcp:3","192.0.2.8/29:tcp:5","192.0.2.8/29:tcp:7","192.0.2.8/29:tcp:9","192.0.2.8/29:tcp:11","192.0.2.8/29:tcp:13","192.0.2.8/29:tcp:15","192.0.2.8/29:tcp:17","192.0.2.8/29:tcp:19","192.0.2.8/29:tcp:21","192.0.2.8/29:tcp:23","192.0.2.8/29:tcp:25","192.0.2.8/29:tcp:27","192.0.2.8/29:tcp:29","192.0.2.8/29:tcp:31","192.0.2.8/29:tcp:33","192.0.2.8/29:tcp:35","192.0.2.8/29:tcp:37","192.0.2.8/29:tcp:39","192.0.2.8/29:tcp:41","192.0.2.8/29:tcp:43","192.0.2.8/29:tcp:45","192.0.2.8/29:tcp:47","192.0.2.8/29:tcp:49","192.0.2.8/29:tcp:51","192.0.2.8/29:tcp:53","192.0.2.8/29:tcp:55","192.0.2.8/29:tcp:57","192.0.2.8/29:tcp:59","192.0.2.8/29:tcp:61","192.0.2.8/29:tcp:63","192.0.2.16/28:tcp:1","192.0.2.16/28:tcp:3","192.0.2.16/28:tcp:5","192.0.2.16/28:tcp:7","192.0.2.16/28:tcp:9","192.0.2.16/28:tcp:11","192.0.2.16/28:tcp:13","192.0.2.16/28:tcp:15","192.0.2.16/28:tcp:17","192.0.2.16/28:tcp:19","192.0.2.16/28:tcp:21","192.0.2.16/28:tcp:23","192.0.2.16/28:tcp:25","192.0.2.16/28:tcp:27","192.0.2.16/28:tcp:29","192.0.2.16/28:tcp:31","192.0.2.16/28:tcp:33","192.0.2.16/28:tcp:35","192.0.2.16/28:tcp:37","192.0.2.16/28:tcp:39","192.0.2.16/28:tcp:41","192.0.2.16/28:tcp:43","192.0.2.16/28:tcp:45","192.0.2.16/28:tcp:47","192.0.2.16/28:tcp:49","192.0.2.16/28:tcp:51","192.0.2.16/28:tcp:53","192.0.2.16/28:tcp:55","192.0.2.16/28:tcp:57","192.0.2.16/28:tcp:59","192.0.2.16/28:tcp:61","192.0.2.16/28:tcp:63","192.0.2.32/27:tcp:1","192.0.2.32/27:tcp:3","192.0.2.32/27:tcp:5","192.0.2.32/27:tcp:7","192.0.2.32/27:tcp:9","192.0.2.32/27:tcp:11","192.0.2.32/27:tcp:13","192.0.2.32/27:tcp:15","192.0.2.32/27:tcp:17","192.0.2.32/27:tcp:19","192.0.2.32/27:tcp:21","192.0.2.32/27:tcp:23","192.0.2.32/27:tcp:25","192.0.2.32/27:tcp:27","192.0.2.32/27:tcp:29","192.0.2.32/27:tcp:31","192.0.2.32/27:tcp:33","192.0.2.32/27:tcp:35","192.0.2.32/27:tcp:37","192.0.2.32/27:tcp:39","192.0.2.32/27:tcp:41","192.0.2.32/27:tcp:43","192.0.2.32/27:tcp:45","192.0.2.32/27:tcp:47","192.0.2.32/27:tcp:49","192.0.2.32/27:tcp:51","192.0.2.32/27:tcp:53","192.0.2.32/27:tcp:55","192.0.2.32/27:tcp:57","192.0.2.32/27:tcp:59","192.0.2.32/27:tcp:61","192.0.2.32/27:tcp:63","192.0.2.64/26:tcp:1","192.0.2.64/26:tcp:3","192.0.2.64/26:tcp:5","192.0.2.64/26:tcp:7","192.0.2.64/26:tcp:9","192.0.2.64/26:tcp:11","192.0.2.64/26:tcp:13","192.0.2.64/26:tcp:15","192.0.2.64/26:tcp:17","192.0.2.64/26:tcp:19","192.0.2.64/26:tcp:21","192.0.2.64/26:tcp:23","192.0.2.64/26:tcp:25","192.0.2.64/26:tcp:27","192.0.2.64/26:tcp:29","192.0.2.64/26:tcp:31","192.0.2.64/26:tcp:33","192.0.2.64/26:tcp:35","192.0.2.64/26:tcp:37","192.0.2.64/26:tcp:39","192.0.2.64/26:tcp:41","192.0.2.64/26:tcp:43","192.0.2.64/26:tcp:45","192.0.2.64/26:tcp:47","192.0.2.64/26:tcp:49","192.0.2.64/26:tcp:51","192.0.2.64/26:tcp:53","192.0.2.64/26:tcp:55","192.0.2.64/26:tcp:57","192.0.2.64/26:tcp:59","192.0.2.64/26:tcp:61","192.0.2.64/26:tcp:63","192.0.2.128/25:tcp:1","192.0.2.128/25:tcp:3","192.0.2.128/25:tcp:5","192.0.2.128/25:tcp:7","192.0.2.128/25:tcp:9","192.0.2.128/25:tcp:11","192.0.2.128/25:tcp:13","192.0.2.128/25:tcp:15","192.0.2.128/25:tcp:17","192.0.2.128/25:tcp:19","192.0.2.128/25:tcp:21","192.0.2.128/25:tcp:23","192.0.2.128/25:tcp:25","192.0.2.128/25:tcp:27","192.0.2.128/25:tcp:29","192.0.2.128/25:tcp:31","192.0.2.128/25:tcp:33","192.0.2.128/25:tcp:35","192.0.2.128/25:tcp:37","192.0.2.128/25:tcp:39","192.0.2.128/25:tcp:41","192.0.2.128/25:tcp:43","192.0.2.128/25:tcp:45","192.0.2.128/25:tcp:47","192.0.2.128/25:tcp:49","192.0.2.128/25:tcp:51","192.0.2.128/25:tcp:53","192.0.2.128/25:tcp:55","192.0.2.128/25:tcp:57","192.0.2.128/25:tcp:59","192.0.2.128/25:tcp:61","192.0.2.128/25:tcp:63"],"deny":[]}}, + {"name":"vector: test_bounds_exclusions_times_port_segments #2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 1}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 3}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 5}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 7}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 9}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 11}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 13}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 15}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 17}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 21}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 23}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 25}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 27}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 29}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 31}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 33}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 35}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 37}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 39}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 41}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 43}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 45}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 47}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 49}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 51}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 53}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 55}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 57}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 59}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 61}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 63}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 65}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"vector: test_bounds_exclusions_times_port_segments #3","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 1}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 2}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 3}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 4}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 5}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 6}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 7}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 8}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 9}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 10}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 11}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 12}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 13}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 14}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 15}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 16}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 17}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 18}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 20}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 21}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 22}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 23}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 24}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 25}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 26}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 27}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 28}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 29}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 30}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 31}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 32}, {\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.1/32\"], \"protocol\": \"tcp\", \"port\": 33}]}"}],"expected":{"allow":["192.0.2.0/32:tcp:1-33","192.0.2.2/31:tcp:1-33","192.0.2.4/30:tcp:1-33","192.0.2.8/29:tcp:1-33","192.0.2.16/28:tcp:1-33","192.0.2.32/27:tcp:1-33","192.0.2.64/26:tcp:1-33","192.0.2.128/25:tcp:1-33"],"deny":[]}}, + {"name":"vector: test_compiles_inclusive_tcp_and_udp_ranges #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8002}]}, {\"to\": [{\"cidr\": \"198.51.100.0/24\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 5000, \"endPort\": 5001}]}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000-8002","198.51.100.0/24:udp:5000-5001"],"deny":[]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"tcp\"}]}]}"}],"expected":{"allow":["2001:db8::7/128:tcp"],"deny":[]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #2","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"udp\"}]}]}"}],"expected":{"allow":["2001:db8::7/128:udp"],"deny":[]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #3","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"icmp\"}]}]}"}],"expected":{"allow":["2001:db8::7/128:icmp"],"deny":[]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #4","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}]}]}"}],"expected":{"allow":["2001:db8::7/128"],"deny":[]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #5","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 443}]}]}"}],"expected":{"allow":["2001:db8::7/128:tcp:443","2001:db8::7/128:udp:443"],"deny":[]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #6","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}]}]}"}],"expected":{"allow":["2001:db8::7/128:tcp:8000-8010"],"deny":[]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #7","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"tcp\"}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::7/128:tcp"]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #8","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"udp\"}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::7/128:udp"]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #9","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"icmp\"}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::7/128:icmp"]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #10","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::7/128"]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #11","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 443}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::7/128:tcp:443","2001:db8::7/128:udp:443"]}}, + {"name":"vector: test_compiles_ipv6_networks_with_every_selector #12","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::7/128:tcp:8000-8010"]}}, + {"name":"vector: test_compiles_mxc_destination_and_port_unions #1","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": 53}, {\"protocol\": \"icmp\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"198.51.100.0/25\"}, {\"cidr\": \"198.51.100.128/25\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 80}, {\"protocol\": \"udp\", \"port\": 53}]}]}"}],"expected":{"allow":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:53","0.0.0.0/0:udp:53","::/0:icmp","::/0:tcp:53","::/0:udp:53"],"deny":["198.51.100.0/24:tcp:80","198.51.100.0/24:udp:53"]}}, + {"name":"vector: test_compiles_one_allowed_ipv6_network_with_a_denied_address #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8:1::/64\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 443}]}, {\"to\": [{\"cidr\": \"::/0\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 53}]}], \"deny\": [{\"to\": [{\"cidr\": \"2001:db8:1::123\"}]}]}"}],"expected":{"allow":["::/0:udp:53","2001:db8:1::/64:tcp:443"],"deny":["2001:db8:1::123/128"]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\"}]}"}],"expected":{"allow":["192.0.2.7/32:tcp"],"deny":[]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"udp\"}]}"}],"expected":{"allow":["192.0.2.7/32:udp"],"deny":[]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #3","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"icmp\"}]}"}],"expected":{"allow":["192.0.2.7/32:icmp"],"deny":[]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #4","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\"}]}"}],"expected":{"allow":["192.0.2.7/32"],"deny":[]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #5","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\"}]}"}],"expected":{"allow":["192.0.2.7/32"],"deny":[]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #6","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\", \"port\": 443}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:443","192.0.2.7/32:udp:443"],"deny":[]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #7","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\", \"port\": 443, \"endPort\": 444}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:443-444","192.0.2.7/32:udp:443-444"],"deny":[]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #8","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\"}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp"]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #9","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"udp\"}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:udp"]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #10","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"icmp\"}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:icmp"]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #11","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\"}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32"]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #12","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\"}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32"]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #13","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\", \"port\": 443}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:443","192.0.2.7/32:udp:443"]}}, + {"name":"vector: test_compiles_protocol_selectors_with_and_without_ports #14","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\", \"port\": 443, \"endPort\": 444}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:443-444","192.0.2.7/32:udp:443-444"]}}, + {"name":"vector: test_deduplicates_overlapping_exclusions_and_collapses_safe_prefixes #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\", \"192.0.2.128/25\", \"192.0.2.192/26\"], \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"198.51.100.0/25\", \"protocol\": \"udp\", \"port\": 53}, {\"cidr\": \"198.51.100.128/25\", \"protocol\": \"udp\", \"port\": 53}]}"}],"expected":{"allow":["192.0.2.0/25:tcp:443","198.51.100.0/24:udp:53"],"deny":[]}}, + {"name":"vector: test_empty_destination_does_not_expand_port_range #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.0/24\"], \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}]}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"vector: test_flat_rules_accept_ipv6_networks #1","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::/32\", \"except\": [\"2001:db8::/33\"], \"protocol\": \"tcp\", \"port\": 443}]}"}],"expected":{"allow":["2001:db8:8000::/33:tcp:443"],"deny":[]}}, + {"name":"vector: test_keeps_a_denied_port_inside_an_allowed_range #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}]}], \"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8005}]}]}"}],"expected":{"allow":["192.0.2.0/24:tcp:8000-8010"],"deny":["192.0.2.0/24:tcp:8005"]}}, + {"name":"vector: test_keeps_each_address_family_separate #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/96\"}, {\"cidr\": \"0.0.0.0/0\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 80}]}, {\"to\": [{\"cidr\": \"192.0.2.0/24\"}]}, {\"to\": [{\"cidr\": \"::c000:200/120\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 53}]}], \"deny\": [{\"to\": [{\"cidr\": \"0.0.0.0/0\"}]}, {\"to\": [{\"cidr\": \"::/96\"}], \"ports\": [{\"protocol\": \"icmp\"}]}]}"}],"expected":{"allow":["0.0.0.0/0:tcp:80","192.0.2.0/24","::/96:tcp:80","::c000:200/120:udp:53"],"deny":["0.0.0.0/0","::/96:icmp"]}}, + {"name":"vector: test_keeps_network_range_whole_when_adjacent_rule_changes_union #1","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.0/25\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 10}, {\"cidr\": \"10.0.0.128/25\", \"protocol\": \"tcp\", \"port\": 5, \"endPort\": 6}]}"}],"expected":{"allow":["10.0.0.0/24:tcp:5-6","10.0.0.0/25:tcp:1-10"],"deny":[]}}, + {"name":"vector: test_limits_the_actual_policy_read #1","input":[{"text":" ","repeat":1048577}],"expected":{"error":"egress policy file exceeds 1048576-byte limit: {path}"}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}]}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000-8010"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000-8010"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #3","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 5000, \"endPort\": 5010}]}]}"}],"expected":{"allow":["192.0.2.7/32:udp:5000-5010"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #4","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"udp\", \"port\": 5000, \"endPort\": 5010}]}"}],"expected":{"allow":["192.0.2.7/32:udp:5000-5010"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #5","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}]}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:1-65535"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #6","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:1-65535"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #7","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 1, \"endPort\": 65535}]}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:1-65535","192.0.2.7/32:udp:1-65535"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #8","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 65535}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:1-65535","192.0.2.7/32:udp:1-65535"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #9","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8000}]}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #10","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8000}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000"],"deny":[]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #11","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}]}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:8000-8010"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #12","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:8000-8010"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #13","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 5000, \"endPort\": 5010}]}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:udp:5000-5010"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #14","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"udp\", \"port\": 5000, \"endPort\": 5010}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:udp:5000-5010"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #15","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}]}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:1-65535"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #16","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:1-65535"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #17","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 1, \"endPort\": 65535}]}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:1-65535","192.0.2.7/32:udp:1-65535"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #18","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 65535}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:1-65535","192.0.2.7/32:udp:1-65535"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #19","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.7\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8000}]}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:8000"]}}, + {"name":"vector: test_lowers_each_range_to_one_native_range_rule #20","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8000}]}"}],"expected":{"allow":[],"deny":["192.0.2.7/32:tcp:8000"]}}, + {"name":"vector: test_lowers_the_destinations_of_each_family_separately #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/25\"}, {\"cidr\": \"2001:db8::/32\", \"except\": [\"2001:db8::/33\"]}, {\"cidr\": \"192.0.2.128/25\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 443}]}]}"}],"expected":{"allow":["192.0.2.0/24:tcp:443","2001:db8:8000::/33:tcp:443"],"deny":[]}}, + {"name":"vector: test_many_source_networks_can_collapse_within_native_budget #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.2\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.3\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.4\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.5\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.6\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.8\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.9\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.10\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.11\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.12\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.13\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.14\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.15\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.16\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.17\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.18\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.19\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.20\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.21\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.22\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.23\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.24\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.25\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.26\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.27\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.28\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.29\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.30\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.31\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.32\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.33\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.34\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.35\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.36\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.37\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.38\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.39\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.40\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.41\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.42\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.43\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.44\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.45\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.46\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.47\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.48\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.49\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.50\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.51\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.52\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.53\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.54\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.55\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.56\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.57\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.58\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.59\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.60\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.61\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.62\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.63\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.64\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.65\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.66\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.67\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.68\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.69\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.70\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.71\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.72\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.73\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.74\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.75\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.76\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.77\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.78\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.79\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.80\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.81\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.82\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.83\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.84\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.85\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.86\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.87\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.88\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.89\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.90\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.91\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.92\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.93\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.94\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.95\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.96\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.97\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.98\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.99\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.100\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.101\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.102\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.103\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.104\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.105\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.106\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.107\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.108\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.109\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.110\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.111\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.112\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.113\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.114\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.115\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.116\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.117\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.118\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.119\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.120\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.121\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.122\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.123\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.124\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.125\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.126\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.127\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.128\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.129\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.130\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.131\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.132\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.133\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.134\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.135\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.136\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.137\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.138\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.139\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.140\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.141\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.142\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.143\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.144\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.145\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.146\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.147\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.148\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.149\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.150\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.151\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.152\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.153\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.154\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.155\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.156\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.157\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.158\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.159\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.160\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.161\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.162\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.163\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.164\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.165\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.166\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.167\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.168\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.169\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.170\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.171\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.172\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.173\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.174\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.175\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.176\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.177\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.178\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.179\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.180\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.181\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.182\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.183\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.184\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.185\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.186\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.187\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.188\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.189\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.190\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.191\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.192\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.193\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.194\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.195\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.196\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.197\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.198\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.199\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.200\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.201\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.202\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.203\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.204\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.205\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.206\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.207\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.208\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.209\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.210\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.211\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.212\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.213\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.214\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.215\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.216\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.217\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.218\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.219\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.220\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.221\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.222\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.223\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.224\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.225\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.226\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.227\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.228\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.229\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.230\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.231\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.232\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.233\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.234\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.235\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.236\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.237\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.238\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.239\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.240\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.241\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.242\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.243\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.244\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.245\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.246\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.247\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.248\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.249\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.250\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.251\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.252\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.253\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.254\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.2.255\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.0\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.1\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.2\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.3\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.4\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.5\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.6\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.7\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.8\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.9\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.10\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.11\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.12\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.13\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.14\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.15\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.16\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.17\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.18\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.19\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.20\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.21\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.22\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.23\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.24\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.25\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.26\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.27\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.28\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.29\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.30\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.31\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.32\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.33\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.34\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.35\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.36\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.37\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.38\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.39\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.40\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.41\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.42\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.43\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.44\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.45\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.46\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.47\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.48\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.49\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.50\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.51\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.52\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.53\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.54\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.55\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.56\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.57\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.58\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.59\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.60\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.61\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.62\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.63\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.64\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.65\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.66\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.67\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.68\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.69\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.70\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.71\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.72\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.73\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.74\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.75\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.76\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.77\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.78\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.79\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.80\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.81\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.82\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.83\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.84\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.85\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.86\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.87\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.88\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.89\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.90\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.91\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.92\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.93\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.94\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.95\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.96\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.97\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.98\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.99\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.100\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.101\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.102\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.103\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.104\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.105\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.106\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.107\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.108\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.109\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.110\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.111\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.112\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.113\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.114\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.115\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.116\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.117\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.118\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.119\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.120\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.121\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.122\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.123\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.124\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.125\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.126\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.127\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.128\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.129\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.130\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.131\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.132\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.133\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.134\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.135\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.136\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.137\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.138\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.139\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.140\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.141\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.142\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.143\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.144\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.145\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.146\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.147\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.148\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.149\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.150\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.151\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.152\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.153\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.154\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.155\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.156\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.157\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.158\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.159\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.160\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.161\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.162\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.163\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.164\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.165\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.166\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.167\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.168\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.169\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.170\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.171\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.172\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.173\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.174\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.175\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.176\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.177\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.178\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.179\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.180\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.181\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.182\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.183\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.184\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.185\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.186\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.187\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.188\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.189\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.190\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.191\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.192\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.193\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.194\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.195\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.196\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.197\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.198\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.199\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.200\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.201\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.202\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.203\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.204\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.205\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.206\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.207\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.208\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.209\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.210\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.211\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.212\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.213\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.214\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.215\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.216\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.217\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.218\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.219\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.220\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.221\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.222\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.223\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.224\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.225\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.226\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.227\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.228\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.229\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.230\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.231\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.232\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.233\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.234\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.235\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.236\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.237\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.238\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.239\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.240\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.241\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.242\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.243\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.244\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.245\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.246\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.247\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.248\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.249\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.250\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.251\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.252\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.253\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.254\", \"protocol\": \"tcp\", \"port\": 80}, {\"cidr\": \"192.0.3.255\", \"protocol\": \"tcp\", \"port\": 80}]}"}],"expected":{"allow":["192.0.2.0/23:tcp:80"],"deny":[]}}, + {"name":"vector: test_merges_adjacent_and_overlapping_ranges_of_one_network #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8004}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8005, \"endPort\": 8010}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000-8010"],"deny":[]}}, + {"name":"vector: test_merges_adjacent_and_overlapping_ranges_of_one_network #2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8008}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8003, \"endPort\": 8010}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000-8010"],"deny":[]}}, + {"name":"vector: test_merges_adjacent_and_overlapping_ranges_of_one_network #3","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8010}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8000}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8001, \"endPort\": 8001}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8002, \"endPort\": 8002}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8003, \"endPort\": 8003}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8004, \"endPort\": 8004}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8005, \"endPort\": 8005}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8006, \"endPort\": 8006}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8007, \"endPort\": 8007}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8008, \"endPort\": 8008}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8009, \"endPort\": 8009}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8010, \"endPort\": 8010}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000-8010"],"deny":[]}}, + {"name":"vector: test_merges_adjacent_and_overlapping_ranges_of_one_network #4","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 8004}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 8006, \"endPort\": 8010}]}"}],"expected":{"allow":["192.0.2.7/32:tcp:8000-8004","192.0.2.7/32:tcp:8006-8010"],"deny":[]}}, + {"name":"vector: test_merges_adjacent_and_overlapping_ranges_of_one_network #5","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"udp\", \"port\": 1, \"endPort\": 10}, {\"cidr\": \"198.51.100.9\", \"protocol\": \"udp\", \"port\": 5, \"endPort\": 20}]}"}],"expected":{"allow":["192.0.2.7/32:udp:1-10","198.51.100.9/32:udp:5-20"],"deny":[]}}, + {"name":"vector: test_normalizes_host_bits_like_the_native_cidr_parser #1","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/24\"}, {\"cidr\": \"fe80::1/64\"}]}"}],"expected":{"allow":["10.0.0.0/24","fe80::/64"],"deny":[]}}, + {"name":"vector: test_overlapping_segments_do_not_inflate_native_rule_budget #1","input":[{"text":"{\"allow\": [{\"cidr\": \"0.0.0.0/0\", \"except\": [\"10.0.0.0/8\"], \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 65535}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1000}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1002}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1004}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1006}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1008}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1010}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1012}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1014}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1016}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1018}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1020}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1022}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1024}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1026}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1028}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1030}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1032}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1034}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1036}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1038}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1040}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1042}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1044}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1046}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1048}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1050}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1052}, {\"cidr\": \"10.0.0.0/8\", \"protocol\": \"tcp\", \"port\": 1054}]}"}],"expected":{"allow":["0.0.0.0/0:tcp:1000","0.0.0.0/0:tcp:1002","0.0.0.0/0:tcp:1004","0.0.0.0/0:tcp:1006","0.0.0.0/0:tcp:1008","0.0.0.0/0:tcp:1010","0.0.0.0/0:tcp:1012","0.0.0.0/0:tcp:1014","0.0.0.0/0:tcp:1016","0.0.0.0/0:tcp:1018","0.0.0.0/0:tcp:1020","0.0.0.0/0:tcp:1022","0.0.0.0/0:tcp:1024","0.0.0.0/0:tcp:1026","0.0.0.0/0:tcp:1028","0.0.0.0/0:tcp:1030","0.0.0.0/0:tcp:1032","0.0.0.0/0:tcp:1034","0.0.0.0/0:tcp:1036","0.0.0.0/0:tcp:1038","0.0.0.0/0:tcp:1040","0.0.0.0/0:tcp:1042","0.0.0.0/0:tcp:1044","0.0.0.0/0:tcp:1046","0.0.0.0/0:tcp:1048","0.0.0.0/0:tcp:1050","0.0.0.0/0:tcp:1052","0.0.0.0/0:tcp:1054","0.0.0.0/5:tcp:1-65535","8.0.0.0/7:tcp:1-65535","11.0.0.0/8:tcp:1-65535","12.0.0.0/6:tcp:1-65535","16.0.0.0/4:tcp:1-65535","32.0.0.0/3:tcp:1-65535","64.0.0.0/2:tcp:1-65535","128.0.0.0/1:tcp:1-65535"],"deny":[]}}, + {"name":"vector: test_partial_address_only_canonicalization_does_not_bridge_gaps #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.1/32\"}, {\"cidr\": \"192.0.2.0/32\", \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"192.0.2.3/32\", \"protocol\": \"tcp\", \"port\": 443}]}"}],"expected":{"allow":["192.0.2.0/31:tcp:443","192.0.2.1/32","192.0.2.3/32:tcp:443"],"deny":[]}}, + {"name":"vector: test_partial_address_only_coverage_keeps_protocol_union_compact #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.0.1/32\"}, {\"cidr\": \"192.0.0.3/32\"}, {\"cidr\": \"192.0.0.5/32\"}, {\"cidr\": \"192.0.0.7/32\"}, {\"cidr\": \"192.0.0.9/32\"}, {\"cidr\": \"192.0.0.11/32\"}, {\"cidr\": \"192.0.0.13/32\"}, {\"cidr\": \"192.0.0.15/32\"}, {\"cidr\": \"192.0.0.17/32\"}, {\"cidr\": \"192.0.0.19/32\"}, {\"cidr\": \"192.0.0.21/32\"}, {\"cidr\": \"192.0.0.23/32\"}, {\"cidr\": \"192.0.0.25/32\"}, {\"cidr\": \"192.0.0.27/32\"}, {\"cidr\": \"192.0.0.29/32\"}, {\"cidr\": \"192.0.0.31/32\"}, {\"cidr\": \"192.0.0.33/32\"}, {\"cidr\": \"192.0.0.35/32\"}, {\"cidr\": \"192.0.0.37/32\"}, {\"cidr\": \"192.0.0.39/32\"}, {\"cidr\": \"192.0.0.41/32\"}, {\"cidr\": \"192.0.0.43/32\"}, {\"cidr\": \"192.0.0.45/32\"}, {\"cidr\": \"192.0.0.47/32\"}, {\"cidr\": \"192.0.0.49/32\"}, {\"cidr\": \"192.0.0.51/32\"}, {\"cidr\": \"192.0.0.53/32\"}, {\"cidr\": \"192.0.0.55/32\"}, {\"cidr\": \"192.0.0.57/32\"}, {\"cidr\": \"192.0.0.59/32\"}, {\"cidr\": \"192.0.0.61/32\"}, {\"cidr\": \"192.0.0.63/32\"}, {\"cidr\": \"192.0.0.65/32\"}, {\"cidr\": \"192.0.0.67/32\"}, {\"cidr\": \"192.0.0.69/32\"}, {\"cidr\": \"192.0.0.71/32\"}, {\"cidr\": \"192.0.0.73/32\"}, {\"cidr\": \"192.0.0.75/32\"}, {\"cidr\": \"192.0.0.77/32\"}, {\"cidr\": \"192.0.0.79/32\"}, {\"cidr\": \"192.0.0.81/32\"}, {\"cidr\": \"192.0.0.83/32\"}, {\"cidr\": \"192.0.0.85/32\"}, {\"cidr\": \"192.0.0.87/32\"}, {\"cidr\": \"192.0.0.89/32\"}, {\"cidr\": \"192.0.0.91/32\"}, {\"cidr\": \"192.0.0.93/32\"}, {\"cidr\": \"192.0.0.95/32\"}, {\"cidr\": \"192.0.0.97/32\"}, {\"cidr\": \"192.0.0.99/32\"}, {\"cidr\": \"192.0.0.101/32\"}, {\"cidr\": \"192.0.0.103/32\"}, {\"cidr\": \"192.0.0.105/32\"}, {\"cidr\": \"192.0.0.107/32\"}, {\"cidr\": \"192.0.0.109/32\"}, {\"cidr\": \"192.0.0.111/32\"}, {\"cidr\": \"192.0.0.113/32\"}, {\"cidr\": \"192.0.0.115/32\"}, {\"cidr\": \"192.0.0.117/32\"}, {\"cidr\": \"192.0.0.119/32\"}, {\"cidr\": \"192.0.0.121/32\"}, {\"cidr\": \"192.0.0.123/32\"}, {\"cidr\": \"192.0.0.125/32\"}, {\"cidr\": \"192.0.0.127/32\"}, {\"cidr\": \"192.0.0.129/32\"}, {\"cidr\": \"192.0.0.131/32\"}, {\"cidr\": \"192.0.0.133/32\"}, {\"cidr\": \"192.0.0.135/32\"}, {\"cidr\": \"192.0.0.137/32\"}, {\"cidr\": \"192.0.0.139/32\"}, {\"cidr\": \"192.0.0.141/32\"}, {\"cidr\": \"192.0.0.143/32\"}, {\"cidr\": \"192.0.0.145/32\"}, {\"cidr\": \"192.0.0.147/32\"}, {\"cidr\": \"192.0.0.149/32\"}, {\"cidr\": \"192.0.0.151/32\"}, {\"cidr\": \"192.0.0.153/32\"}, {\"cidr\": \"192.0.0.155/32\"}, {\"cidr\": \"192.0.0.157/32\"}, {\"cidr\": \"192.0.0.159/32\"}, {\"cidr\": \"192.0.0.161/32\"}, {\"cidr\": \"192.0.0.163/32\"}, {\"cidr\": \"192.0.0.165/32\"}, {\"cidr\": \"192.0.0.167/32\"}, {\"cidr\": \"192.0.0.169/32\"}, {\"cidr\": \"192.0.0.171/32\"}, {\"cidr\": \"192.0.0.173/32\"}, {\"cidr\": \"192.0.0.175/32\"}, {\"cidr\": \"192.0.0.177/32\"}, {\"cidr\": \"192.0.0.179/32\"}, {\"cidr\": \"192.0.0.181/32\"}, {\"cidr\": \"192.0.0.183/32\"}, {\"cidr\": \"192.0.0.185/32\"}, {\"cidr\": \"192.0.0.187/32\"}, {\"cidr\": \"192.0.0.189/32\"}, {\"cidr\": \"192.0.0.191/32\"}, {\"cidr\": \"192.0.0.193/32\"}, {\"cidr\": \"192.0.0.195/32\"}, {\"cidr\": \"192.0.0.197/32\"}, {\"cidr\": \"192.0.0.199/32\"}, {\"cidr\": \"192.0.0.201/32\"}, {\"cidr\": \"192.0.0.203/32\"}, {\"cidr\": \"192.0.0.205/32\"}, {\"cidr\": \"192.0.0.207/32\"}, {\"cidr\": \"192.0.0.209/32\"}, {\"cidr\": \"192.0.0.211/32\"}, {\"cidr\": \"192.0.0.213/32\"}, {\"cidr\": \"192.0.0.215/32\"}, {\"cidr\": \"192.0.0.217/32\"}, {\"cidr\": \"192.0.0.219/32\"}, {\"cidr\": \"192.0.0.221/32\"}, {\"cidr\": \"192.0.0.223/32\"}, {\"cidr\": \"192.0.0.225/32\"}, {\"cidr\": \"192.0.0.227/32\"}, {\"cidr\": \"192.0.0.229/32\"}, {\"cidr\": \"192.0.0.231/32\"}, {\"cidr\": \"192.0.0.233/32\"}, {\"cidr\": \"192.0.0.235/32\"}, {\"cidr\": \"192.0.0.237/32\"}, {\"cidr\": \"192.0.0.239/32\"}, {\"cidr\": \"192.0.0.241/32\"}, {\"cidr\": \"192.0.0.243/32\"}, {\"cidr\": \"192.0.0.245/32\"}, {\"cidr\": \"192.0.0.247/32\"}, {\"cidr\": \"192.0.0.249/32\"}, {\"cidr\": \"192.0.0.251/32\"}, {\"cidr\": \"192.0.0.253/32\"}, {\"cidr\": \"192.0.0.255/32\"}, {\"cidr\": \"192.0.0.0/16\", \"protocol\": \"tcp\", \"port\": 443}]}"}],"expected":{"allow":["192.0.0.0/16:tcp:443","192.0.0.1/32","192.0.0.3/32","192.0.0.5/32","192.0.0.7/32","192.0.0.9/32","192.0.0.11/32","192.0.0.13/32","192.0.0.15/32","192.0.0.17/32","192.0.0.19/32","192.0.0.21/32","192.0.0.23/32","192.0.0.25/32","192.0.0.27/32","192.0.0.29/32","192.0.0.31/32","192.0.0.33/32","192.0.0.35/32","192.0.0.37/32","192.0.0.39/32","192.0.0.41/32","192.0.0.43/32","192.0.0.45/32","192.0.0.47/32","192.0.0.49/32","192.0.0.51/32","192.0.0.53/32","192.0.0.55/32","192.0.0.57/32","192.0.0.59/32","192.0.0.61/32","192.0.0.63/32","192.0.0.65/32","192.0.0.67/32","192.0.0.69/32","192.0.0.71/32","192.0.0.73/32","192.0.0.75/32","192.0.0.77/32","192.0.0.79/32","192.0.0.81/32","192.0.0.83/32","192.0.0.85/32","192.0.0.87/32","192.0.0.89/32","192.0.0.91/32","192.0.0.93/32","192.0.0.95/32","192.0.0.97/32","192.0.0.99/32","192.0.0.101/32","192.0.0.103/32","192.0.0.105/32","192.0.0.107/32","192.0.0.109/32","192.0.0.111/32","192.0.0.113/32","192.0.0.115/32","192.0.0.117/32","192.0.0.119/32","192.0.0.121/32","192.0.0.123/32","192.0.0.125/32","192.0.0.127/32","192.0.0.129/32","192.0.0.131/32","192.0.0.133/32","192.0.0.135/32","192.0.0.137/32","192.0.0.139/32","192.0.0.141/32","192.0.0.143/32","192.0.0.145/32","192.0.0.147/32","192.0.0.149/32","192.0.0.151/32","192.0.0.153/32","192.0.0.155/32","192.0.0.157/32","192.0.0.159/32","192.0.0.161/32","192.0.0.163/32","192.0.0.165/32","192.0.0.167/32","192.0.0.169/32","192.0.0.171/32","192.0.0.173/32","192.0.0.175/32","192.0.0.177/32","192.0.0.179/32","192.0.0.181/32","192.0.0.183/32","192.0.0.185/32","192.0.0.187/32","192.0.0.189/32","192.0.0.191/32","192.0.0.193/32","192.0.0.195/32","192.0.0.197/32","192.0.0.199/32","192.0.0.201/32","192.0.0.203/32","192.0.0.205/32","192.0.0.207/32","192.0.0.209/32","192.0.0.211/32","192.0.0.213/32","192.0.0.215/32","192.0.0.217/32","192.0.0.219/32","192.0.0.221/32","192.0.0.223/32","192.0.0.225/32","192.0.0.227/32","192.0.0.229/32","192.0.0.231/32","192.0.0.233/32","192.0.0.235/32","192.0.0.237/32","192.0.0.239/32","192.0.0.241/32","192.0.0.243/32","192.0.0.245/32","192.0.0.247/32","192.0.0.249/32","192.0.0.251/32","192.0.0.253/32","192.0.0.255/32"],"deny":[]}}, + {"name":"vector: test_partial_address_only_coverage_keeps_protocol_union_compact #2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.0.0/16\", \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"192.0.0.1/32\"}, {\"cidr\": \"192.0.0.3/32\"}, {\"cidr\": \"192.0.0.5/32\"}, {\"cidr\": \"192.0.0.7/32\"}, {\"cidr\": \"192.0.0.9/32\"}, {\"cidr\": \"192.0.0.11/32\"}, {\"cidr\": \"192.0.0.13/32\"}, {\"cidr\": \"192.0.0.15/32\"}, {\"cidr\": \"192.0.0.17/32\"}, {\"cidr\": \"192.0.0.19/32\"}, {\"cidr\": \"192.0.0.21/32\"}, {\"cidr\": \"192.0.0.23/32\"}, {\"cidr\": \"192.0.0.25/32\"}, {\"cidr\": \"192.0.0.27/32\"}, {\"cidr\": \"192.0.0.29/32\"}, {\"cidr\": \"192.0.0.31/32\"}, {\"cidr\": \"192.0.0.33/32\"}, {\"cidr\": \"192.0.0.35/32\"}, {\"cidr\": \"192.0.0.37/32\"}, {\"cidr\": \"192.0.0.39/32\"}, {\"cidr\": \"192.0.0.41/32\"}, {\"cidr\": \"192.0.0.43/32\"}, {\"cidr\": \"192.0.0.45/32\"}, {\"cidr\": \"192.0.0.47/32\"}, {\"cidr\": \"192.0.0.49/32\"}, {\"cidr\": \"192.0.0.51/32\"}, {\"cidr\": \"192.0.0.53/32\"}, {\"cidr\": \"192.0.0.55/32\"}, {\"cidr\": \"192.0.0.57/32\"}, {\"cidr\": \"192.0.0.59/32\"}, {\"cidr\": \"192.0.0.61/32\"}, {\"cidr\": \"192.0.0.63/32\"}, {\"cidr\": \"192.0.0.65/32\"}, {\"cidr\": \"192.0.0.67/32\"}, {\"cidr\": \"192.0.0.69/32\"}, {\"cidr\": \"192.0.0.71/32\"}, {\"cidr\": \"192.0.0.73/32\"}, {\"cidr\": \"192.0.0.75/32\"}, {\"cidr\": \"192.0.0.77/32\"}, {\"cidr\": \"192.0.0.79/32\"}, {\"cidr\": \"192.0.0.81/32\"}, {\"cidr\": \"192.0.0.83/32\"}, {\"cidr\": \"192.0.0.85/32\"}, {\"cidr\": \"192.0.0.87/32\"}, {\"cidr\": \"192.0.0.89/32\"}, {\"cidr\": \"192.0.0.91/32\"}, {\"cidr\": \"192.0.0.93/32\"}, {\"cidr\": \"192.0.0.95/32\"}, {\"cidr\": \"192.0.0.97/32\"}, {\"cidr\": \"192.0.0.99/32\"}, {\"cidr\": \"192.0.0.101/32\"}, {\"cidr\": \"192.0.0.103/32\"}, {\"cidr\": \"192.0.0.105/32\"}, {\"cidr\": \"192.0.0.107/32\"}, {\"cidr\": \"192.0.0.109/32\"}, {\"cidr\": \"192.0.0.111/32\"}, {\"cidr\": \"192.0.0.113/32\"}, {\"cidr\": \"192.0.0.115/32\"}, {\"cidr\": \"192.0.0.117/32\"}, {\"cidr\": \"192.0.0.119/32\"}, {\"cidr\": \"192.0.0.121/32\"}, {\"cidr\": \"192.0.0.123/32\"}, {\"cidr\": \"192.0.0.125/32\"}, {\"cidr\": \"192.0.0.127/32\"}, {\"cidr\": \"192.0.0.129/32\"}, {\"cidr\": \"192.0.0.131/32\"}, {\"cidr\": \"192.0.0.133/32\"}, {\"cidr\": \"192.0.0.135/32\"}, {\"cidr\": \"192.0.0.137/32\"}, {\"cidr\": \"192.0.0.139/32\"}, {\"cidr\": \"192.0.0.141/32\"}, {\"cidr\": \"192.0.0.143/32\"}, {\"cidr\": \"192.0.0.145/32\"}, {\"cidr\": \"192.0.0.147/32\"}, {\"cidr\": \"192.0.0.149/32\"}, {\"cidr\": \"192.0.0.151/32\"}, {\"cidr\": \"192.0.0.153/32\"}, {\"cidr\": \"192.0.0.155/32\"}, {\"cidr\": \"192.0.0.157/32\"}, {\"cidr\": \"192.0.0.159/32\"}, {\"cidr\": \"192.0.0.161/32\"}, {\"cidr\": \"192.0.0.163/32\"}, {\"cidr\": \"192.0.0.165/32\"}, {\"cidr\": \"192.0.0.167/32\"}, {\"cidr\": \"192.0.0.169/32\"}, {\"cidr\": \"192.0.0.171/32\"}, {\"cidr\": \"192.0.0.173/32\"}, {\"cidr\": \"192.0.0.175/32\"}, {\"cidr\": \"192.0.0.177/32\"}, {\"cidr\": \"192.0.0.179/32\"}, {\"cidr\": \"192.0.0.181/32\"}, {\"cidr\": \"192.0.0.183/32\"}, {\"cidr\": \"192.0.0.185/32\"}, {\"cidr\": \"192.0.0.187/32\"}, {\"cidr\": \"192.0.0.189/32\"}, {\"cidr\": \"192.0.0.191/32\"}, {\"cidr\": \"192.0.0.193/32\"}, {\"cidr\": \"192.0.0.195/32\"}, {\"cidr\": \"192.0.0.197/32\"}, {\"cidr\": \"192.0.0.199/32\"}, {\"cidr\": \"192.0.0.201/32\"}, {\"cidr\": \"192.0.0.203/32\"}, {\"cidr\": \"192.0.0.205/32\"}, {\"cidr\": \"192.0.0.207/32\"}, {\"cidr\": \"192.0.0.209/32\"}, {\"cidr\": \"192.0.0.211/32\"}, {\"cidr\": \"192.0.0.213/32\"}, {\"cidr\": \"192.0.0.215/32\"}, {\"cidr\": \"192.0.0.217/32\"}, {\"cidr\": \"192.0.0.219/32\"}, {\"cidr\": \"192.0.0.221/32\"}, {\"cidr\": \"192.0.0.223/32\"}, {\"cidr\": \"192.0.0.225/32\"}, {\"cidr\": \"192.0.0.227/32\"}, {\"cidr\": \"192.0.0.229/32\"}, {\"cidr\": \"192.0.0.231/32\"}, {\"cidr\": \"192.0.0.233/32\"}, {\"cidr\": \"192.0.0.235/32\"}, {\"cidr\": \"192.0.0.237/32\"}, {\"cidr\": \"192.0.0.239/32\"}, {\"cidr\": \"192.0.0.241/32\"}, {\"cidr\": \"192.0.0.243/32\"}, {\"cidr\": \"192.0.0.245/32\"}, {\"cidr\": \"192.0.0.247/32\"}, {\"cidr\": \"192.0.0.249/32\"}, {\"cidr\": \"192.0.0.251/32\"}, {\"cidr\": \"192.0.0.253/32\"}, {\"cidr\": \"192.0.0.255/32\"}]}"}],"expected":{"allow":["192.0.0.0/16:tcp:443","192.0.0.1/32","192.0.0.3/32","192.0.0.5/32","192.0.0.7/32","192.0.0.9/32","192.0.0.11/32","192.0.0.13/32","192.0.0.15/32","192.0.0.17/32","192.0.0.19/32","192.0.0.21/32","192.0.0.23/32","192.0.0.25/32","192.0.0.27/32","192.0.0.29/32","192.0.0.31/32","192.0.0.33/32","192.0.0.35/32","192.0.0.37/32","192.0.0.39/32","192.0.0.41/32","192.0.0.43/32","192.0.0.45/32","192.0.0.47/32","192.0.0.49/32","192.0.0.51/32","192.0.0.53/32","192.0.0.55/32","192.0.0.57/32","192.0.0.59/32","192.0.0.61/32","192.0.0.63/32","192.0.0.65/32","192.0.0.67/32","192.0.0.69/32","192.0.0.71/32","192.0.0.73/32","192.0.0.75/32","192.0.0.77/32","192.0.0.79/32","192.0.0.81/32","192.0.0.83/32","192.0.0.85/32","192.0.0.87/32","192.0.0.89/32","192.0.0.91/32","192.0.0.93/32","192.0.0.95/32","192.0.0.97/32","192.0.0.99/32","192.0.0.101/32","192.0.0.103/32","192.0.0.105/32","192.0.0.107/32","192.0.0.109/32","192.0.0.111/32","192.0.0.113/32","192.0.0.115/32","192.0.0.117/32","192.0.0.119/32","192.0.0.121/32","192.0.0.123/32","192.0.0.125/32","192.0.0.127/32","192.0.0.129/32","192.0.0.131/32","192.0.0.133/32","192.0.0.135/32","192.0.0.137/32","192.0.0.139/32","192.0.0.141/32","192.0.0.143/32","192.0.0.145/32","192.0.0.147/32","192.0.0.149/32","192.0.0.151/32","192.0.0.153/32","192.0.0.155/32","192.0.0.157/32","192.0.0.159/32","192.0.0.161/32","192.0.0.163/32","192.0.0.165/32","192.0.0.167/32","192.0.0.169/32","192.0.0.171/32","192.0.0.173/32","192.0.0.175/32","192.0.0.177/32","192.0.0.179/32","192.0.0.181/32","192.0.0.183/32","192.0.0.185/32","192.0.0.187/32","192.0.0.189/32","192.0.0.191/32","192.0.0.193/32","192.0.0.195/32","192.0.0.197/32","192.0.0.199/32","192.0.0.201/32","192.0.0.203/32","192.0.0.205/32","192.0.0.207/32","192.0.0.209/32","192.0.0.211/32","192.0.0.213/32","192.0.0.215/32","192.0.0.217/32","192.0.0.219/32","192.0.0.221/32","192.0.0.223/32","192.0.0.225/32","192.0.0.227/32","192.0.0.229/32","192.0.0.231/32","192.0.0.233/32","192.0.0.235/32","192.0.0.237/32","192.0.0.239/32","192.0.0.241/32","192.0.0.243/32","192.0.0.245/32","192.0.0.247/32","192.0.0.249/32","192.0.0.251/32","192.0.0.253/32","192.0.0.255/32"],"deny":[]}}, + {"name":"vector: test_partial_address_only_coverage_keeps_protocol_union_compact #3","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.0.1/32\"}, {\"cidr\": \"192.0.0.3/32\"}, {\"cidr\": \"192.0.0.5/32\"}, {\"cidr\": \"192.0.0.7/32\"}, {\"cidr\": \"192.0.0.9/32\"}, {\"cidr\": \"192.0.0.11/32\"}, {\"cidr\": \"192.0.0.13/32\"}, {\"cidr\": \"192.0.0.15/32\"}, {\"cidr\": \"192.0.0.17/32\"}, {\"cidr\": \"192.0.0.19/32\"}, {\"cidr\": \"192.0.0.21/32\"}, {\"cidr\": \"192.0.0.23/32\"}, {\"cidr\": \"192.0.0.25/32\"}, {\"cidr\": \"192.0.0.27/32\"}, {\"cidr\": \"192.0.0.29/32\"}, {\"cidr\": \"192.0.0.31/32\"}, {\"cidr\": \"192.0.0.33/32\"}, {\"cidr\": \"192.0.0.35/32\"}, {\"cidr\": \"192.0.0.37/32\"}, {\"cidr\": \"192.0.0.39/32\"}, {\"cidr\": \"192.0.0.41/32\"}, {\"cidr\": \"192.0.0.43/32\"}, {\"cidr\": \"192.0.0.45/32\"}, {\"cidr\": \"192.0.0.47/32\"}, {\"cidr\": \"192.0.0.49/32\"}, {\"cidr\": \"192.0.0.51/32\"}, {\"cidr\": \"192.0.0.53/32\"}, {\"cidr\": \"192.0.0.55/32\"}, {\"cidr\": \"192.0.0.57/32\"}, {\"cidr\": \"192.0.0.59/32\"}, {\"cidr\": \"192.0.0.61/32\"}, {\"cidr\": \"192.0.0.63/32\"}, {\"cidr\": \"192.0.0.65/32\"}, {\"cidr\": \"192.0.0.67/32\"}, {\"cidr\": \"192.0.0.69/32\"}, {\"cidr\": \"192.0.0.71/32\"}, {\"cidr\": \"192.0.0.73/32\"}, {\"cidr\": \"192.0.0.75/32\"}, {\"cidr\": \"192.0.0.77/32\"}, {\"cidr\": \"192.0.0.79/32\"}, {\"cidr\": \"192.0.0.81/32\"}, {\"cidr\": \"192.0.0.83/32\"}, {\"cidr\": \"192.0.0.85/32\"}, {\"cidr\": \"192.0.0.87/32\"}, {\"cidr\": \"192.0.0.89/32\"}, {\"cidr\": \"192.0.0.91/32\"}, {\"cidr\": \"192.0.0.93/32\"}, {\"cidr\": \"192.0.0.95/32\"}, {\"cidr\": \"192.0.0.97/32\"}, {\"cidr\": \"192.0.0.99/32\"}, {\"cidr\": \"192.0.0.101/32\"}, {\"cidr\": \"192.0.0.103/32\"}, {\"cidr\": \"192.0.0.105/32\"}, {\"cidr\": \"192.0.0.107/32\"}, {\"cidr\": \"192.0.0.109/32\"}, {\"cidr\": \"192.0.0.111/32\"}, {\"cidr\": \"192.0.0.113/32\"}, {\"cidr\": \"192.0.0.115/32\"}, {\"cidr\": \"192.0.0.117/32\"}, {\"cidr\": \"192.0.0.119/32\"}, {\"cidr\": \"192.0.0.121/32\"}, {\"cidr\": \"192.0.0.123/32\"}, {\"cidr\": \"192.0.0.125/32\"}, {\"cidr\": \"192.0.0.127/32\"}, {\"cidr\": \"192.0.0.129/32\"}, {\"cidr\": \"192.0.0.131/32\"}, {\"cidr\": \"192.0.0.133/32\"}, {\"cidr\": \"192.0.0.135/32\"}, {\"cidr\": \"192.0.0.137/32\"}, {\"cidr\": \"192.0.0.139/32\"}, {\"cidr\": \"192.0.0.141/32\"}, {\"cidr\": \"192.0.0.143/32\"}, {\"cidr\": \"192.0.0.145/32\"}, {\"cidr\": \"192.0.0.147/32\"}, {\"cidr\": \"192.0.0.149/32\"}, {\"cidr\": \"192.0.0.151/32\"}, {\"cidr\": \"192.0.0.153/32\"}, {\"cidr\": \"192.0.0.155/32\"}, {\"cidr\": \"192.0.0.157/32\"}, {\"cidr\": \"192.0.0.159/32\"}, {\"cidr\": \"192.0.0.161/32\"}, {\"cidr\": \"192.0.0.163/32\"}, {\"cidr\": \"192.0.0.165/32\"}, {\"cidr\": \"192.0.0.167/32\"}, {\"cidr\": \"192.0.0.169/32\"}, {\"cidr\": \"192.0.0.171/32\"}, {\"cidr\": \"192.0.0.173/32\"}, {\"cidr\": \"192.0.0.175/32\"}, {\"cidr\": \"192.0.0.177/32\"}, {\"cidr\": \"192.0.0.179/32\"}, {\"cidr\": \"192.0.0.181/32\"}, {\"cidr\": \"192.0.0.183/32\"}, {\"cidr\": \"192.0.0.185/32\"}, {\"cidr\": \"192.0.0.187/32\"}, {\"cidr\": \"192.0.0.189/32\"}, {\"cidr\": \"192.0.0.191/32\"}, {\"cidr\": \"192.0.0.193/32\"}, {\"cidr\": \"192.0.0.195/32\"}, {\"cidr\": \"192.0.0.197/32\"}, {\"cidr\": \"192.0.0.199/32\"}, {\"cidr\": \"192.0.0.201/32\"}, {\"cidr\": \"192.0.0.203/32\"}, {\"cidr\": \"192.0.0.205/32\"}, {\"cidr\": \"192.0.0.207/32\"}, {\"cidr\": \"192.0.0.209/32\"}, {\"cidr\": \"192.0.0.211/32\"}, {\"cidr\": \"192.0.0.213/32\"}, {\"cidr\": \"192.0.0.215/32\"}, {\"cidr\": \"192.0.0.217/32\"}, {\"cidr\": \"192.0.0.219/32\"}, {\"cidr\": \"192.0.0.221/32\"}, {\"cidr\": \"192.0.0.223/32\"}, {\"cidr\": \"192.0.0.225/32\"}, {\"cidr\": \"192.0.0.227/32\"}, {\"cidr\": \"192.0.0.229/32\"}, {\"cidr\": \"192.0.0.231/32\"}, {\"cidr\": \"192.0.0.233/32\"}, {\"cidr\": \"192.0.0.235/32\"}, {\"cidr\": \"192.0.0.237/32\"}, {\"cidr\": \"192.0.0.239/32\"}, {\"cidr\": \"192.0.0.241/32\"}, {\"cidr\": \"192.0.0.243/32\"}, {\"cidr\": \"192.0.0.245/32\"}, {\"cidr\": \"192.0.0.247/32\"}, {\"cidr\": \"192.0.0.249/32\"}, {\"cidr\": \"192.0.0.251/32\"}, {\"cidr\": \"192.0.0.253/32\"}, {\"cidr\": \"192.0.0.255/32\"}, {\"cidr\": \"192.0.0.0/16\", \"protocol\": \"tcp\", \"port\": 443}]}"}],"expected":{"allow":[],"deny":["192.0.0.0/16:tcp:443","192.0.0.1/32","192.0.0.3/32","192.0.0.5/32","192.0.0.7/32","192.0.0.9/32","192.0.0.11/32","192.0.0.13/32","192.0.0.15/32","192.0.0.17/32","192.0.0.19/32","192.0.0.21/32","192.0.0.23/32","192.0.0.25/32","192.0.0.27/32","192.0.0.29/32","192.0.0.31/32","192.0.0.33/32","192.0.0.35/32","192.0.0.37/32","192.0.0.39/32","192.0.0.41/32","192.0.0.43/32","192.0.0.45/32","192.0.0.47/32","192.0.0.49/32","192.0.0.51/32","192.0.0.53/32","192.0.0.55/32","192.0.0.57/32","192.0.0.59/32","192.0.0.61/32","192.0.0.63/32","192.0.0.65/32","192.0.0.67/32","192.0.0.69/32","192.0.0.71/32","192.0.0.73/32","192.0.0.75/32","192.0.0.77/32","192.0.0.79/32","192.0.0.81/32","192.0.0.83/32","192.0.0.85/32","192.0.0.87/32","192.0.0.89/32","192.0.0.91/32","192.0.0.93/32","192.0.0.95/32","192.0.0.97/32","192.0.0.99/32","192.0.0.101/32","192.0.0.103/32","192.0.0.105/32","192.0.0.107/32","192.0.0.109/32","192.0.0.111/32","192.0.0.113/32","192.0.0.115/32","192.0.0.117/32","192.0.0.119/32","192.0.0.121/32","192.0.0.123/32","192.0.0.125/32","192.0.0.127/32","192.0.0.129/32","192.0.0.131/32","192.0.0.133/32","192.0.0.135/32","192.0.0.137/32","192.0.0.139/32","192.0.0.141/32","192.0.0.143/32","192.0.0.145/32","192.0.0.147/32","192.0.0.149/32","192.0.0.151/32","192.0.0.153/32","192.0.0.155/32","192.0.0.157/32","192.0.0.159/32","192.0.0.161/32","192.0.0.163/32","192.0.0.165/32","192.0.0.167/32","192.0.0.169/32","192.0.0.171/32","192.0.0.173/32","192.0.0.175/32","192.0.0.177/32","192.0.0.179/32","192.0.0.181/32","192.0.0.183/32","192.0.0.185/32","192.0.0.187/32","192.0.0.189/32","192.0.0.191/32","192.0.0.193/32","192.0.0.195/32","192.0.0.197/32","192.0.0.199/32","192.0.0.201/32","192.0.0.203/32","192.0.0.205/32","192.0.0.207/32","192.0.0.209/32","192.0.0.211/32","192.0.0.213/32","192.0.0.215/32","192.0.0.217/32","192.0.0.219/32","192.0.0.221/32","192.0.0.223/32","192.0.0.225/32","192.0.0.227/32","192.0.0.229/32","192.0.0.231/32","192.0.0.233/32","192.0.0.235/32","192.0.0.237/32","192.0.0.239/32","192.0.0.241/32","192.0.0.243/32","192.0.0.245/32","192.0.0.247/32","192.0.0.249/32","192.0.0.251/32","192.0.0.253/32","192.0.0.255/32"]}}, + {"name":"vector: test_partial_address_only_coverage_keeps_protocol_union_compact #4","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.0.0/16\", \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"192.0.0.1/32\"}, {\"cidr\": \"192.0.0.3/32\"}, {\"cidr\": \"192.0.0.5/32\"}, {\"cidr\": \"192.0.0.7/32\"}, {\"cidr\": \"192.0.0.9/32\"}, {\"cidr\": \"192.0.0.11/32\"}, {\"cidr\": \"192.0.0.13/32\"}, {\"cidr\": \"192.0.0.15/32\"}, {\"cidr\": \"192.0.0.17/32\"}, {\"cidr\": \"192.0.0.19/32\"}, {\"cidr\": \"192.0.0.21/32\"}, {\"cidr\": \"192.0.0.23/32\"}, {\"cidr\": \"192.0.0.25/32\"}, {\"cidr\": \"192.0.0.27/32\"}, {\"cidr\": \"192.0.0.29/32\"}, {\"cidr\": \"192.0.0.31/32\"}, {\"cidr\": \"192.0.0.33/32\"}, {\"cidr\": \"192.0.0.35/32\"}, {\"cidr\": \"192.0.0.37/32\"}, {\"cidr\": \"192.0.0.39/32\"}, {\"cidr\": \"192.0.0.41/32\"}, {\"cidr\": \"192.0.0.43/32\"}, {\"cidr\": \"192.0.0.45/32\"}, {\"cidr\": \"192.0.0.47/32\"}, {\"cidr\": \"192.0.0.49/32\"}, {\"cidr\": \"192.0.0.51/32\"}, {\"cidr\": \"192.0.0.53/32\"}, {\"cidr\": \"192.0.0.55/32\"}, {\"cidr\": \"192.0.0.57/32\"}, {\"cidr\": \"192.0.0.59/32\"}, {\"cidr\": \"192.0.0.61/32\"}, {\"cidr\": \"192.0.0.63/32\"}, {\"cidr\": \"192.0.0.65/32\"}, {\"cidr\": \"192.0.0.67/32\"}, {\"cidr\": \"192.0.0.69/32\"}, {\"cidr\": \"192.0.0.71/32\"}, {\"cidr\": \"192.0.0.73/32\"}, {\"cidr\": \"192.0.0.75/32\"}, {\"cidr\": \"192.0.0.77/32\"}, {\"cidr\": \"192.0.0.79/32\"}, {\"cidr\": \"192.0.0.81/32\"}, {\"cidr\": \"192.0.0.83/32\"}, {\"cidr\": \"192.0.0.85/32\"}, {\"cidr\": \"192.0.0.87/32\"}, {\"cidr\": \"192.0.0.89/32\"}, {\"cidr\": \"192.0.0.91/32\"}, {\"cidr\": \"192.0.0.93/32\"}, {\"cidr\": \"192.0.0.95/32\"}, {\"cidr\": \"192.0.0.97/32\"}, {\"cidr\": \"192.0.0.99/32\"}, {\"cidr\": \"192.0.0.101/32\"}, {\"cidr\": \"192.0.0.103/32\"}, {\"cidr\": \"192.0.0.105/32\"}, {\"cidr\": \"192.0.0.107/32\"}, {\"cidr\": \"192.0.0.109/32\"}, {\"cidr\": \"192.0.0.111/32\"}, {\"cidr\": \"192.0.0.113/32\"}, {\"cidr\": \"192.0.0.115/32\"}, {\"cidr\": \"192.0.0.117/32\"}, {\"cidr\": \"192.0.0.119/32\"}, {\"cidr\": \"192.0.0.121/32\"}, {\"cidr\": \"192.0.0.123/32\"}, {\"cidr\": \"192.0.0.125/32\"}, {\"cidr\": \"192.0.0.127/32\"}, {\"cidr\": \"192.0.0.129/32\"}, {\"cidr\": \"192.0.0.131/32\"}, {\"cidr\": \"192.0.0.133/32\"}, {\"cidr\": \"192.0.0.135/32\"}, {\"cidr\": \"192.0.0.137/32\"}, {\"cidr\": \"192.0.0.139/32\"}, {\"cidr\": \"192.0.0.141/32\"}, {\"cidr\": \"192.0.0.143/32\"}, {\"cidr\": \"192.0.0.145/32\"}, {\"cidr\": \"192.0.0.147/32\"}, {\"cidr\": \"192.0.0.149/32\"}, {\"cidr\": \"192.0.0.151/32\"}, {\"cidr\": \"192.0.0.153/32\"}, {\"cidr\": \"192.0.0.155/32\"}, {\"cidr\": \"192.0.0.157/32\"}, {\"cidr\": \"192.0.0.159/32\"}, {\"cidr\": \"192.0.0.161/32\"}, {\"cidr\": \"192.0.0.163/32\"}, {\"cidr\": \"192.0.0.165/32\"}, {\"cidr\": \"192.0.0.167/32\"}, {\"cidr\": \"192.0.0.169/32\"}, {\"cidr\": \"192.0.0.171/32\"}, {\"cidr\": \"192.0.0.173/32\"}, {\"cidr\": \"192.0.0.175/32\"}, {\"cidr\": \"192.0.0.177/32\"}, {\"cidr\": \"192.0.0.179/32\"}, {\"cidr\": \"192.0.0.181/32\"}, {\"cidr\": \"192.0.0.183/32\"}, {\"cidr\": \"192.0.0.185/32\"}, {\"cidr\": \"192.0.0.187/32\"}, {\"cidr\": \"192.0.0.189/32\"}, {\"cidr\": \"192.0.0.191/32\"}, {\"cidr\": \"192.0.0.193/32\"}, {\"cidr\": \"192.0.0.195/32\"}, {\"cidr\": \"192.0.0.197/32\"}, {\"cidr\": \"192.0.0.199/32\"}, {\"cidr\": \"192.0.0.201/32\"}, {\"cidr\": \"192.0.0.203/32\"}, {\"cidr\": \"192.0.0.205/32\"}, {\"cidr\": \"192.0.0.207/32\"}, {\"cidr\": \"192.0.0.209/32\"}, {\"cidr\": \"192.0.0.211/32\"}, {\"cidr\": \"192.0.0.213/32\"}, {\"cidr\": \"192.0.0.215/32\"}, {\"cidr\": \"192.0.0.217/32\"}, {\"cidr\": \"192.0.0.219/32\"}, {\"cidr\": \"192.0.0.221/32\"}, {\"cidr\": \"192.0.0.223/32\"}, {\"cidr\": \"192.0.0.225/32\"}, {\"cidr\": \"192.0.0.227/32\"}, {\"cidr\": \"192.0.0.229/32\"}, {\"cidr\": \"192.0.0.231/32\"}, {\"cidr\": \"192.0.0.233/32\"}, {\"cidr\": \"192.0.0.235/32\"}, {\"cidr\": \"192.0.0.237/32\"}, {\"cidr\": \"192.0.0.239/32\"}, {\"cidr\": \"192.0.0.241/32\"}, {\"cidr\": \"192.0.0.243/32\"}, {\"cidr\": \"192.0.0.245/32\"}, {\"cidr\": \"192.0.0.247/32\"}, {\"cidr\": \"192.0.0.249/32\"}, {\"cidr\": \"192.0.0.251/32\"}, {\"cidr\": \"192.0.0.253/32\"}, {\"cidr\": \"192.0.0.255/32\"}]}"}],"expected":{"allow":[],"deny":["192.0.0.0/16:tcp:443","192.0.0.1/32","192.0.0.3/32","192.0.0.5/32","192.0.0.7/32","192.0.0.9/32","192.0.0.11/32","192.0.0.13/32","192.0.0.15/32","192.0.0.17/32","192.0.0.19/32","192.0.0.21/32","192.0.0.23/32","192.0.0.25/32","192.0.0.27/32","192.0.0.29/32","192.0.0.31/32","192.0.0.33/32","192.0.0.35/32","192.0.0.37/32","192.0.0.39/32","192.0.0.41/32","192.0.0.43/32","192.0.0.45/32","192.0.0.47/32","192.0.0.49/32","192.0.0.51/32","192.0.0.53/32","192.0.0.55/32","192.0.0.57/32","192.0.0.59/32","192.0.0.61/32","192.0.0.63/32","192.0.0.65/32","192.0.0.67/32","192.0.0.69/32","192.0.0.71/32","192.0.0.73/32","192.0.0.75/32","192.0.0.77/32","192.0.0.79/32","192.0.0.81/32","192.0.0.83/32","192.0.0.85/32","192.0.0.87/32","192.0.0.89/32","192.0.0.91/32","192.0.0.93/32","192.0.0.95/32","192.0.0.97/32","192.0.0.99/32","192.0.0.101/32","192.0.0.103/32","192.0.0.105/32","192.0.0.107/32","192.0.0.109/32","192.0.0.111/32","192.0.0.113/32","192.0.0.115/32","192.0.0.117/32","192.0.0.119/32","192.0.0.121/32","192.0.0.123/32","192.0.0.125/32","192.0.0.127/32","192.0.0.129/32","192.0.0.131/32","192.0.0.133/32","192.0.0.135/32","192.0.0.137/32","192.0.0.139/32","192.0.0.141/32","192.0.0.143/32","192.0.0.145/32","192.0.0.147/32","192.0.0.149/32","192.0.0.151/32","192.0.0.153/32","192.0.0.155/32","192.0.0.157/32","192.0.0.159/32","192.0.0.161/32","192.0.0.163/32","192.0.0.165/32","192.0.0.167/32","192.0.0.169/32","192.0.0.171/32","192.0.0.173/32","192.0.0.175/32","192.0.0.177/32","192.0.0.179/32","192.0.0.181/32","192.0.0.183/32","192.0.0.185/32","192.0.0.187/32","192.0.0.189/32","192.0.0.191/32","192.0.0.193/32","192.0.0.195/32","192.0.0.197/32","192.0.0.199/32","192.0.0.201/32","192.0.0.203/32","192.0.0.205/32","192.0.0.207/32","192.0.0.209/32","192.0.0.211/32","192.0.0.213/32","192.0.0.215/32","192.0.0.217/32","192.0.0.219/32","192.0.0.221/32","192.0.0.223/32","192.0.0.225/32","192.0.0.227/32","192.0.0.229/32","192.0.0.231/32","192.0.0.233/32","192.0.0.235/32","192.0.0.237/32","192.0.0.239/32","192.0.0.241/32","192.0.0.243/32","192.0.0.245/32","192.0.0.247/32","192.0.0.249/32","192.0.0.251/32","192.0.0.253/32","192.0.0.255/32"]}}, + {"name":"vector: test_protocol_wide_rules_cover_their_port_rules_only #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\", \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\"}, {\"cidr\": \"192.0.2.7\", \"protocol\": \"udp\", \"port\": 443}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\"}], \"deny\": [{\"cidr\": \"198.51.100.0/24\", \"protocol\": \"any\", \"port\": 53}, {\"cidr\": \"198.51.100.0/24\", \"protocol\": \"udp\"}]}"}],"expected":{"allow":["192.0.2.0/24:icmp","192.0.2.0/24:tcp","192.0.2.7/32:udp:443"],"deny":["198.51.100.0/24:tcp:53","198.51.100.0/24:udp"]}}, + {"name":"vector: test_protocol_wide_rules_share_the_native_rule_budget #1","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1/32\"}, {\"cidr\": \"10.0.1.1/32\"}, {\"cidr\": \"10.0.2.1/32\"}, {\"cidr\": \"10.0.3.1/32\"}, {\"cidr\": \"10.0.4.1/32\"}, {\"cidr\": \"10.0.5.1/32\"}, {\"cidr\": \"10.0.6.1/32\"}, {\"cidr\": \"10.0.7.1/32\"}, {\"cidr\": \"10.0.8.1/32\"}, {\"cidr\": \"10.0.9.1/32\"}, {\"cidr\": \"10.0.10.1/32\"}, {\"cidr\": \"10.0.11.1/32\"}, {\"cidr\": \"10.0.12.1/32\"}, {\"cidr\": \"10.0.13.1/32\"}, {\"cidr\": \"10.0.14.1/32\"}, {\"cidr\": \"10.0.15.1/32\"}, {\"cidr\": \"10.0.16.1/32\"}, {\"cidr\": \"10.0.17.1/32\"}, {\"cidr\": \"10.0.18.1/32\"}, {\"cidr\": \"10.0.19.1/32\"}, {\"cidr\": \"10.0.20.1/32\"}, {\"cidr\": \"10.0.21.1/32\"}, {\"cidr\": \"10.0.22.1/32\"}, {\"cidr\": \"10.0.23.1/32\"}, {\"cidr\": \"10.0.24.1/32\"}, {\"cidr\": \"10.0.25.1/32\"}, {\"cidr\": \"10.0.26.1/32\"}, {\"cidr\": \"10.0.27.1/32\"}, {\"cidr\": \"10.0.28.1/32\"}, {\"cidr\": \"10.0.29.1/32\"}, {\"cidr\": \"10.0.30.1/32\"}, {\"cidr\": \"10.0.31.1/32\"}, {\"cidr\": \"10.0.32.1/32\"}, {\"cidr\": \"10.0.33.1/32\"}, {\"cidr\": \"10.0.34.1/32\"}, {\"cidr\": \"10.0.35.1/32\"}, {\"cidr\": \"10.0.36.1/32\"}, {\"cidr\": \"10.0.37.1/32\"}, {\"cidr\": \"10.0.38.1/32\"}, {\"cidr\": \"10.0.39.1/32\"}, {\"cidr\": \"10.0.40.1/32\"}, {\"cidr\": \"10.0.41.1/32\"}, {\"cidr\": \"10.0.42.1/32\"}, {\"cidr\": \"10.0.43.1/32\"}, {\"cidr\": \"10.0.44.1/32\"}, {\"cidr\": \"10.0.45.1/32\"}, {\"cidr\": \"10.0.46.1/32\"}, {\"cidr\": \"10.0.47.1/32\"}, {\"cidr\": \"10.0.48.1/32\"}, {\"cidr\": \"10.0.49.1/32\"}, {\"cidr\": \"10.0.50.1/32\"}, {\"cidr\": \"10.0.51.1/32\"}, {\"cidr\": \"10.0.52.1/32\"}, {\"cidr\": \"10.0.53.1/32\"}, {\"cidr\": \"10.0.54.1/32\"}, {\"cidr\": \"10.0.55.1/32\"}, {\"cidr\": \"10.0.56.1/32\"}, {\"cidr\": \"10.0.57.1/32\"}, {\"cidr\": \"10.0.58.1/32\"}, {\"cidr\": \"10.0.59.1/32\"}, {\"cidr\": \"10.0.60.1/32\"}, {\"cidr\": \"10.0.61.1/32\"}, {\"cidr\": \"10.0.62.1/32\"}, {\"cidr\": \"10.0.63.1/32\"}, {\"cidr\": \"10.0.64.1/32\"}, {\"cidr\": \"10.0.65.1/32\"}, {\"cidr\": \"10.0.66.1/32\"}, {\"cidr\": \"10.0.67.1/32\"}, {\"cidr\": \"10.0.68.1/32\"}, {\"cidr\": \"10.0.69.1/32\"}, {\"cidr\": \"10.0.70.1/32\"}, {\"cidr\": \"10.0.71.1/32\"}, {\"cidr\": \"10.0.72.1/32\"}, {\"cidr\": \"10.0.73.1/32\"}, {\"cidr\": \"10.0.74.1/32\"}, {\"cidr\": \"10.0.75.1/32\"}, {\"cidr\": \"10.0.76.1/32\"}, {\"cidr\": \"10.0.77.1/32\"}, {\"cidr\": \"10.0.78.1/32\"}, {\"cidr\": \"10.0.79.1/32\"}, {\"cidr\": \"10.0.80.1/32\"}, {\"cidr\": \"10.0.81.1/32\"}, {\"cidr\": \"10.0.82.1/32\"}, {\"cidr\": \"10.0.83.1/32\"}, {\"cidr\": \"10.0.84.1/32\"}, {\"cidr\": \"10.0.85.1/32\"}, {\"cidr\": \"10.0.86.1/32\"}, {\"cidr\": \"10.0.87.1/32\"}, {\"cidr\": \"10.0.88.1/32\"}, {\"cidr\": \"10.0.89.1/32\"}, {\"cidr\": \"10.0.90.1/32\"}, {\"cidr\": \"10.0.91.1/32\"}, {\"cidr\": \"10.0.92.1/32\"}, {\"cidr\": \"10.0.93.1/32\"}, {\"cidr\": \"10.0.94.1/32\"}, {\"cidr\": \"10.0.95.1/32\"}, {\"cidr\": \"10.0.96.1/32\"}, {\"cidr\": \"10.0.97.1/32\"}, {\"cidr\": \"10.0.98.1/32\"}, {\"cidr\": \"10.0.99.1/32\"}, {\"cidr\": \"10.0.100.1/32\"}, {\"cidr\": \"10.0.101.1/32\"}, {\"cidr\": \"10.0.102.1/32\"}, {\"cidr\": \"10.0.103.1/32\"}, {\"cidr\": \"10.0.104.1/32\"}, {\"cidr\": \"10.0.105.1/32\"}, {\"cidr\": \"10.0.106.1/32\"}, {\"cidr\": \"10.0.107.1/32\"}, {\"cidr\": \"10.0.108.1/32\"}, {\"cidr\": \"10.0.109.1/32\"}, {\"cidr\": \"10.0.110.1/32\"}, {\"cidr\": \"10.0.111.1/32\"}, {\"cidr\": \"10.0.112.1/32\"}, {\"cidr\": \"10.0.113.1/32\"}, {\"cidr\": \"10.0.114.1/32\"}, {\"cidr\": \"10.0.115.1/32\"}, {\"cidr\": \"10.0.116.1/32\"}, {\"cidr\": \"10.0.117.1/32\"}, {\"cidr\": \"10.0.118.1/32\"}, {\"cidr\": \"10.0.119.1/32\"}, {\"cidr\": \"10.0.120.1/32\"}, {\"cidr\": \"10.0.121.1/32\"}, {\"cidr\": \"10.0.122.1/32\"}, {\"cidr\": \"10.0.123.1/32\"}, {\"cidr\": \"10.0.124.1/32\"}, {\"cidr\": \"10.0.125.1/32\"}, {\"cidr\": \"10.0.126.1/32\"}, {\"cidr\": \"10.0.127.1/32\"}, {\"cidr\": \"10.0.128.1/32\"}, {\"cidr\": \"10.0.129.1/32\"}, {\"cidr\": \"10.0.130.1/32\"}, {\"cidr\": \"10.0.131.1/32\"}, {\"cidr\": \"10.0.132.1/32\"}, {\"cidr\": \"10.0.133.1/32\"}, {\"cidr\": \"10.0.134.1/32\"}, {\"cidr\": \"10.0.135.1/32\"}, {\"cidr\": \"10.0.136.1/32\"}, {\"cidr\": \"10.0.137.1/32\"}, {\"cidr\": \"10.0.138.1/32\"}, {\"cidr\": \"10.0.139.1/32\"}, {\"cidr\": \"10.0.140.1/32\"}, {\"cidr\": \"10.0.141.1/32\"}, {\"cidr\": \"10.0.142.1/32\"}, {\"cidr\": \"10.0.143.1/32\"}, {\"cidr\": \"10.0.144.1/32\"}, {\"cidr\": \"10.0.145.1/32\"}, {\"cidr\": \"10.0.146.1/32\"}, {\"cidr\": \"10.0.147.1/32\"}, {\"cidr\": \"10.0.148.1/32\"}, {\"cidr\": \"10.0.149.1/32\"}, {\"cidr\": \"10.0.150.1/32\"}, {\"cidr\": \"10.0.151.1/32\"}, {\"cidr\": \"10.0.152.1/32\"}, {\"cidr\": \"10.0.153.1/32\"}, {\"cidr\": \"10.0.154.1/32\"}, {\"cidr\": \"10.0.155.1/32\"}, {\"cidr\": \"10.0.156.1/32\"}, {\"cidr\": \"10.0.157.1/32\"}, {\"cidr\": \"10.0.158.1/32\"}, {\"cidr\": \"10.0.159.1/32\"}, {\"cidr\": \"10.0.160.1/32\"}, {\"cidr\": \"10.0.161.1/32\"}, {\"cidr\": \"10.0.162.1/32\"}, {\"cidr\": \"10.0.163.1/32\"}, {\"cidr\": \"10.0.164.1/32\"}, {\"cidr\": \"10.0.165.1/32\"}, {\"cidr\": \"10.0.166.1/32\"}, {\"cidr\": \"10.0.167.1/32\"}, {\"cidr\": \"10.0.168.1/32\"}, {\"cidr\": \"10.0.169.1/32\"}, {\"cidr\": \"10.0.170.1/32\"}, {\"cidr\": \"10.0.171.1/32\"}, {\"cidr\": \"10.0.172.1/32\"}, {\"cidr\": \"10.0.173.1/32\"}, {\"cidr\": \"10.0.174.1/32\"}, {\"cidr\": \"10.0.175.1/32\"}, {\"cidr\": \"10.0.176.1/32\"}, {\"cidr\": \"10.0.177.1/32\"}, {\"cidr\": \"10.0.178.1/32\"}, {\"cidr\": \"10.0.179.1/32\"}, {\"cidr\": \"10.0.180.1/32\"}, {\"cidr\": \"10.0.181.1/32\"}, {\"cidr\": \"10.0.182.1/32\"}, {\"cidr\": \"10.0.183.1/32\"}, {\"cidr\": \"10.0.184.1/32\"}, {\"cidr\": \"10.0.185.1/32\"}, {\"cidr\": \"10.0.186.1/32\"}, {\"cidr\": \"10.0.187.1/32\"}, {\"cidr\": \"10.0.188.1/32\"}, {\"cidr\": \"10.0.189.1/32\"}, {\"cidr\": \"10.0.190.1/32\"}, {\"cidr\": \"10.0.191.1/32\"}, {\"cidr\": \"10.0.192.1/32\"}, {\"cidr\": \"10.0.193.1/32\"}, {\"cidr\": \"10.0.194.1/32\"}, {\"cidr\": \"10.0.195.1/32\"}, {\"cidr\": \"10.0.196.1/32\"}, {\"cidr\": \"10.0.197.1/32\"}, {\"cidr\": \"10.0.198.1/32\"}, {\"cidr\": \"10.0.199.1/32\"}, {\"cidr\": \"10.0.200.1/32\"}, {\"cidr\": \"10.0.201.1/32\"}, {\"cidr\": \"10.0.202.1/32\"}, {\"cidr\": \"10.0.203.1/32\"}, {\"cidr\": \"10.0.204.1/32\"}, {\"cidr\": \"10.0.205.1/32\"}, {\"cidr\": \"10.0.206.1/32\"}, {\"cidr\": \"10.0.207.1/32\"}, {\"cidr\": \"10.0.208.1/32\"}, {\"cidr\": \"10.0.209.1/32\"}, {\"cidr\": \"10.0.210.1/32\"}, {\"cidr\": \"10.0.211.1/32\"}, {\"cidr\": \"10.0.212.1/32\"}, {\"cidr\": \"10.0.213.1/32\"}, {\"cidr\": \"10.0.214.1/32\"}, {\"cidr\": \"10.0.215.1/32\"}, {\"cidr\": \"10.0.216.1/32\"}, {\"cidr\": \"10.0.217.1/32\"}, {\"cidr\": \"10.0.218.1/32\"}, {\"cidr\": \"10.0.219.1/32\"}, {\"cidr\": \"10.0.220.1/32\"}, {\"cidr\": \"10.0.221.1/32\"}, {\"cidr\": \"10.0.222.1/32\"}, {\"cidr\": \"10.0.223.1/32\"}, {\"cidr\": \"10.0.224.1/32\"}, {\"cidr\": \"10.0.225.1/32\"}, {\"cidr\": \"10.0.226.1/32\"}, {\"cidr\": \"10.0.227.1/32\"}, {\"cidr\": \"10.0.228.1/32\"}, {\"cidr\": \"10.0.229.1/32\"}, {\"cidr\": \"10.0.230.1/32\"}, {\"cidr\": \"10.0.231.1/32\"}, {\"cidr\": \"10.0.232.1/32\"}, {\"cidr\": \"10.0.233.1/32\"}, {\"cidr\": \"10.0.234.1/32\"}, {\"cidr\": \"10.0.235.1/32\"}, {\"cidr\": \"10.0.236.1/32\"}, {\"cidr\": \"10.0.237.1/32\"}, {\"cidr\": \"10.0.238.1/32\"}, {\"cidr\": \"10.0.239.1/32\"}, {\"cidr\": \"10.0.240.1/32\"}, {\"cidr\": \"10.0.241.1/32\"}, {\"cidr\": \"10.0.242.1/32\"}, {\"cidr\": \"10.0.243.1/32\"}, {\"cidr\": \"10.0.244.1/32\"}, {\"cidr\": \"10.0.245.1/32\"}, {\"cidr\": \"10.0.246.1/32\"}, {\"cidr\": \"10.0.247.1/32\"}, {\"cidr\": \"10.0.248.1/32\"}, {\"cidr\": \"10.0.249.1/32\"}, {\"cidr\": \"10.0.250.1/32\"}, {\"cidr\": \"10.0.251.1/32\"}, {\"cidr\": \"10.0.252.1/32\"}, {\"cidr\": \"10.0.253.1/32\"}, {\"cidr\": \"10.0.254.1/32\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\"}]}"}],"expected":{"allow":["10.0.0.1/32","10.0.1.1/32","10.0.2.1/32","10.0.3.1/32","10.0.4.1/32","10.0.5.1/32","10.0.6.1/32","10.0.7.1/32","10.0.8.1/32","10.0.9.1/32","10.0.10.1/32","10.0.11.1/32","10.0.12.1/32","10.0.13.1/32","10.0.14.1/32","10.0.15.1/32","10.0.16.1/32","10.0.17.1/32","10.0.18.1/32","10.0.19.1/32","10.0.20.1/32","10.0.21.1/32","10.0.22.1/32","10.0.23.1/32","10.0.24.1/32","10.0.25.1/32","10.0.26.1/32","10.0.27.1/32","10.0.28.1/32","10.0.29.1/32","10.0.30.1/32","10.0.31.1/32","10.0.32.1/32","10.0.33.1/32","10.0.34.1/32","10.0.35.1/32","10.0.36.1/32","10.0.37.1/32","10.0.38.1/32","10.0.39.1/32","10.0.40.1/32","10.0.41.1/32","10.0.42.1/32","10.0.43.1/32","10.0.44.1/32","10.0.45.1/32","10.0.46.1/32","10.0.47.1/32","10.0.48.1/32","10.0.49.1/32","10.0.50.1/32","10.0.51.1/32","10.0.52.1/32","10.0.53.1/32","10.0.54.1/32","10.0.55.1/32","10.0.56.1/32","10.0.57.1/32","10.0.58.1/32","10.0.59.1/32","10.0.60.1/32","10.0.61.1/32","10.0.62.1/32","10.0.63.1/32","10.0.64.1/32","10.0.65.1/32","10.0.66.1/32","10.0.67.1/32","10.0.68.1/32","10.0.69.1/32","10.0.70.1/32","10.0.71.1/32","10.0.72.1/32","10.0.73.1/32","10.0.74.1/32","10.0.75.1/32","10.0.76.1/32","10.0.77.1/32","10.0.78.1/32","10.0.79.1/32","10.0.80.1/32","10.0.81.1/32","10.0.82.1/32","10.0.83.1/32","10.0.84.1/32","10.0.85.1/32","10.0.86.1/32","10.0.87.1/32","10.0.88.1/32","10.0.89.1/32","10.0.90.1/32","10.0.91.1/32","10.0.92.1/32","10.0.93.1/32","10.0.94.1/32","10.0.95.1/32","10.0.96.1/32","10.0.97.1/32","10.0.98.1/32","10.0.99.1/32","10.0.100.1/32","10.0.101.1/32","10.0.102.1/32","10.0.103.1/32","10.0.104.1/32","10.0.105.1/32","10.0.106.1/32","10.0.107.1/32","10.0.108.1/32","10.0.109.1/32","10.0.110.1/32","10.0.111.1/32","10.0.112.1/32","10.0.113.1/32","10.0.114.1/32","10.0.115.1/32","10.0.116.1/32","10.0.117.1/32","10.0.118.1/32","10.0.119.1/32","10.0.120.1/32","10.0.121.1/32","10.0.122.1/32","10.0.123.1/32","10.0.124.1/32","10.0.125.1/32","10.0.126.1/32","10.0.127.1/32","10.0.128.1/32","10.0.129.1/32","10.0.130.1/32","10.0.131.1/32","10.0.132.1/32","10.0.133.1/32","10.0.134.1/32","10.0.135.1/32","10.0.136.1/32","10.0.137.1/32","10.0.138.1/32","10.0.139.1/32","10.0.140.1/32","10.0.141.1/32","10.0.142.1/32","10.0.143.1/32","10.0.144.1/32","10.0.145.1/32","10.0.146.1/32","10.0.147.1/32","10.0.148.1/32","10.0.149.1/32","10.0.150.1/32","10.0.151.1/32","10.0.152.1/32","10.0.153.1/32","10.0.154.1/32","10.0.155.1/32","10.0.156.1/32","10.0.157.1/32","10.0.158.1/32","10.0.159.1/32","10.0.160.1/32","10.0.161.1/32","10.0.162.1/32","10.0.163.1/32","10.0.164.1/32","10.0.165.1/32","10.0.166.1/32","10.0.167.1/32","10.0.168.1/32","10.0.169.1/32","10.0.170.1/32","10.0.171.1/32","10.0.172.1/32","10.0.173.1/32","10.0.174.1/32","10.0.175.1/32","10.0.176.1/32","10.0.177.1/32","10.0.178.1/32","10.0.179.1/32","10.0.180.1/32","10.0.181.1/32","10.0.182.1/32","10.0.183.1/32","10.0.184.1/32","10.0.185.1/32","10.0.186.1/32","10.0.187.1/32","10.0.188.1/32","10.0.189.1/32","10.0.190.1/32","10.0.191.1/32","10.0.192.1/32","10.0.193.1/32","10.0.194.1/32","10.0.195.1/32","10.0.196.1/32","10.0.197.1/32","10.0.198.1/32","10.0.199.1/32","10.0.200.1/32","10.0.201.1/32","10.0.202.1/32","10.0.203.1/32","10.0.204.1/32","10.0.205.1/32","10.0.206.1/32","10.0.207.1/32","10.0.208.1/32","10.0.209.1/32","10.0.210.1/32","10.0.211.1/32","10.0.212.1/32","10.0.213.1/32","10.0.214.1/32","10.0.215.1/32","10.0.216.1/32","10.0.217.1/32","10.0.218.1/32","10.0.219.1/32","10.0.220.1/32","10.0.221.1/32","10.0.222.1/32","10.0.223.1/32","10.0.224.1/32","10.0.225.1/32","10.0.226.1/32","10.0.227.1/32","10.0.228.1/32","10.0.229.1/32","10.0.230.1/32","10.0.231.1/32","10.0.232.1/32","10.0.233.1/32","10.0.234.1/32","10.0.235.1/32","10.0.236.1/32","10.0.237.1/32","10.0.238.1/32","10.0.239.1/32","10.0.240.1/32","10.0.241.1/32","10.0.242.1/32","10.0.243.1/32","10.0.244.1/32","10.0.245.1/32","10.0.246.1/32","10.0.247.1/32","10.0.248.1/32","10.0.249.1/32","10.0.250.1/32","10.0.251.1/32","10.0.252.1/32","10.0.253.1/32","10.0.254.1/32","192.0.2.0/24:tcp"],"deny":[]}}, + {"name":"vector: test_protocol_wide_rules_share_the_native_rule_budget #2","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1/32\"}, {\"cidr\": \"10.0.1.1/32\"}, {\"cidr\": \"10.0.2.1/32\"}, {\"cidr\": \"10.0.3.1/32\"}, {\"cidr\": \"10.0.4.1/32\"}, {\"cidr\": \"10.0.5.1/32\"}, {\"cidr\": \"10.0.6.1/32\"}, {\"cidr\": \"10.0.7.1/32\"}, {\"cidr\": \"10.0.8.1/32\"}, {\"cidr\": \"10.0.9.1/32\"}, {\"cidr\": \"10.0.10.1/32\"}, {\"cidr\": \"10.0.11.1/32\"}, {\"cidr\": \"10.0.12.1/32\"}, {\"cidr\": \"10.0.13.1/32\"}, {\"cidr\": \"10.0.14.1/32\"}, {\"cidr\": \"10.0.15.1/32\"}, {\"cidr\": \"10.0.16.1/32\"}, {\"cidr\": \"10.0.17.1/32\"}, {\"cidr\": \"10.0.18.1/32\"}, {\"cidr\": \"10.0.19.1/32\"}, {\"cidr\": \"10.0.20.1/32\"}, {\"cidr\": \"10.0.21.1/32\"}, {\"cidr\": \"10.0.22.1/32\"}, {\"cidr\": \"10.0.23.1/32\"}, {\"cidr\": \"10.0.24.1/32\"}, {\"cidr\": \"10.0.25.1/32\"}, {\"cidr\": \"10.0.26.1/32\"}, {\"cidr\": \"10.0.27.1/32\"}, {\"cidr\": \"10.0.28.1/32\"}, {\"cidr\": \"10.0.29.1/32\"}, {\"cidr\": \"10.0.30.1/32\"}, {\"cidr\": \"10.0.31.1/32\"}, {\"cidr\": \"10.0.32.1/32\"}, {\"cidr\": \"10.0.33.1/32\"}, {\"cidr\": \"10.0.34.1/32\"}, {\"cidr\": \"10.0.35.1/32\"}, {\"cidr\": \"10.0.36.1/32\"}, {\"cidr\": \"10.0.37.1/32\"}, {\"cidr\": \"10.0.38.1/32\"}, {\"cidr\": \"10.0.39.1/32\"}, {\"cidr\": \"10.0.40.1/32\"}, {\"cidr\": \"10.0.41.1/32\"}, {\"cidr\": \"10.0.42.1/32\"}, {\"cidr\": \"10.0.43.1/32\"}, {\"cidr\": \"10.0.44.1/32\"}, {\"cidr\": \"10.0.45.1/32\"}, {\"cidr\": \"10.0.46.1/32\"}, {\"cidr\": \"10.0.47.1/32\"}, {\"cidr\": \"10.0.48.1/32\"}, {\"cidr\": \"10.0.49.1/32\"}, {\"cidr\": \"10.0.50.1/32\"}, {\"cidr\": \"10.0.51.1/32\"}, {\"cidr\": \"10.0.52.1/32\"}, {\"cidr\": \"10.0.53.1/32\"}, {\"cidr\": \"10.0.54.1/32\"}, {\"cidr\": \"10.0.55.1/32\"}, {\"cidr\": \"10.0.56.1/32\"}, {\"cidr\": \"10.0.57.1/32\"}, {\"cidr\": \"10.0.58.1/32\"}, {\"cidr\": \"10.0.59.1/32\"}, {\"cidr\": \"10.0.60.1/32\"}, {\"cidr\": \"10.0.61.1/32\"}, {\"cidr\": \"10.0.62.1/32\"}, {\"cidr\": \"10.0.63.1/32\"}, {\"cidr\": \"10.0.64.1/32\"}, {\"cidr\": \"10.0.65.1/32\"}, {\"cidr\": \"10.0.66.1/32\"}, {\"cidr\": \"10.0.67.1/32\"}, {\"cidr\": \"10.0.68.1/32\"}, {\"cidr\": \"10.0.69.1/32\"}, {\"cidr\": \"10.0.70.1/32\"}, {\"cidr\": \"10.0.71.1/32\"}, {\"cidr\": \"10.0.72.1/32\"}, {\"cidr\": \"10.0.73.1/32\"}, {\"cidr\": \"10.0.74.1/32\"}, {\"cidr\": \"10.0.75.1/32\"}, {\"cidr\": \"10.0.76.1/32\"}, {\"cidr\": \"10.0.77.1/32\"}, {\"cidr\": \"10.0.78.1/32\"}, {\"cidr\": \"10.0.79.1/32\"}, {\"cidr\": \"10.0.80.1/32\"}, {\"cidr\": \"10.0.81.1/32\"}, {\"cidr\": \"10.0.82.1/32\"}, {\"cidr\": \"10.0.83.1/32\"}, {\"cidr\": \"10.0.84.1/32\"}, {\"cidr\": \"10.0.85.1/32\"}, {\"cidr\": \"10.0.86.1/32\"}, {\"cidr\": \"10.0.87.1/32\"}, {\"cidr\": \"10.0.88.1/32\"}, {\"cidr\": \"10.0.89.1/32\"}, {\"cidr\": \"10.0.90.1/32\"}, {\"cidr\": \"10.0.91.1/32\"}, {\"cidr\": \"10.0.92.1/32\"}, {\"cidr\": \"10.0.93.1/32\"}, {\"cidr\": \"10.0.94.1/32\"}, {\"cidr\": \"10.0.95.1/32\"}, {\"cidr\": \"10.0.96.1/32\"}, {\"cidr\": \"10.0.97.1/32\"}, {\"cidr\": \"10.0.98.1/32\"}, {\"cidr\": \"10.0.99.1/32\"}, {\"cidr\": \"10.0.100.1/32\"}, {\"cidr\": \"10.0.101.1/32\"}, {\"cidr\": \"10.0.102.1/32\"}, {\"cidr\": \"10.0.103.1/32\"}, {\"cidr\": \"10.0.104.1/32\"}, {\"cidr\": \"10.0.105.1/32\"}, {\"cidr\": \"10.0.106.1/32\"}, {\"cidr\": \"10.0.107.1/32\"}, {\"cidr\": \"10.0.108.1/32\"}, {\"cidr\": \"10.0.109.1/32\"}, {\"cidr\": \"10.0.110.1/32\"}, {\"cidr\": \"10.0.111.1/32\"}, {\"cidr\": \"10.0.112.1/32\"}, {\"cidr\": \"10.0.113.1/32\"}, {\"cidr\": \"10.0.114.1/32\"}, {\"cidr\": \"10.0.115.1/32\"}, {\"cidr\": \"10.0.116.1/32\"}, {\"cidr\": \"10.0.117.1/32\"}, {\"cidr\": \"10.0.118.1/32\"}, {\"cidr\": \"10.0.119.1/32\"}, {\"cidr\": \"10.0.120.1/32\"}, {\"cidr\": \"10.0.121.1/32\"}, {\"cidr\": \"10.0.122.1/32\"}, {\"cidr\": \"10.0.123.1/32\"}, {\"cidr\": \"10.0.124.1/32\"}, {\"cidr\": \"10.0.125.1/32\"}, {\"cidr\": \"10.0.126.1/32\"}, {\"cidr\": \"10.0.127.1/32\"}, {\"cidr\": \"10.0.128.1/32\"}, {\"cidr\": \"10.0.129.1/32\"}, {\"cidr\": \"10.0.130.1/32\"}, {\"cidr\": \"10.0.131.1/32\"}, {\"cidr\": \"10.0.132.1/32\"}, {\"cidr\": \"10.0.133.1/32\"}, {\"cidr\": \"10.0.134.1/32\"}, {\"cidr\": \"10.0.135.1/32\"}, {\"cidr\": \"10.0.136.1/32\"}, {\"cidr\": \"10.0.137.1/32\"}, {\"cidr\": \"10.0.138.1/32\"}, {\"cidr\": \"10.0.139.1/32\"}, {\"cidr\": \"10.0.140.1/32\"}, {\"cidr\": \"10.0.141.1/32\"}, {\"cidr\": \"10.0.142.1/32\"}, {\"cidr\": \"10.0.143.1/32\"}, {\"cidr\": \"10.0.144.1/32\"}, {\"cidr\": \"10.0.145.1/32\"}, {\"cidr\": \"10.0.146.1/32\"}, {\"cidr\": \"10.0.147.1/32\"}, {\"cidr\": \"10.0.148.1/32\"}, {\"cidr\": \"10.0.149.1/32\"}, {\"cidr\": \"10.0.150.1/32\"}, {\"cidr\": \"10.0.151.1/32\"}, {\"cidr\": \"10.0.152.1/32\"}, {\"cidr\": \"10.0.153.1/32\"}, {\"cidr\": \"10.0.154.1/32\"}, {\"cidr\": \"10.0.155.1/32\"}, {\"cidr\": \"10.0.156.1/32\"}, {\"cidr\": \"10.0.157.1/32\"}, {\"cidr\": \"10.0.158.1/32\"}, {\"cidr\": \"10.0.159.1/32\"}, {\"cidr\": \"10.0.160.1/32\"}, {\"cidr\": \"10.0.161.1/32\"}, {\"cidr\": \"10.0.162.1/32\"}, {\"cidr\": \"10.0.163.1/32\"}, {\"cidr\": \"10.0.164.1/32\"}, {\"cidr\": \"10.0.165.1/32\"}, {\"cidr\": \"10.0.166.1/32\"}, {\"cidr\": \"10.0.167.1/32\"}, {\"cidr\": \"10.0.168.1/32\"}, {\"cidr\": \"10.0.169.1/32\"}, {\"cidr\": \"10.0.170.1/32\"}, {\"cidr\": \"10.0.171.1/32\"}, {\"cidr\": \"10.0.172.1/32\"}, {\"cidr\": \"10.0.173.1/32\"}, {\"cidr\": \"10.0.174.1/32\"}, {\"cidr\": \"10.0.175.1/32\"}, {\"cidr\": \"10.0.176.1/32\"}, {\"cidr\": \"10.0.177.1/32\"}, {\"cidr\": \"10.0.178.1/32\"}, {\"cidr\": \"10.0.179.1/32\"}, {\"cidr\": \"10.0.180.1/32\"}, {\"cidr\": \"10.0.181.1/32\"}, {\"cidr\": \"10.0.182.1/32\"}, {\"cidr\": \"10.0.183.1/32\"}, {\"cidr\": \"10.0.184.1/32\"}, {\"cidr\": \"10.0.185.1/32\"}, {\"cidr\": \"10.0.186.1/32\"}, {\"cidr\": \"10.0.187.1/32\"}, {\"cidr\": \"10.0.188.1/32\"}, {\"cidr\": \"10.0.189.1/32\"}, {\"cidr\": \"10.0.190.1/32\"}, {\"cidr\": \"10.0.191.1/32\"}, {\"cidr\": \"10.0.192.1/32\"}, {\"cidr\": \"10.0.193.1/32\"}, {\"cidr\": \"10.0.194.1/32\"}, {\"cidr\": \"10.0.195.1/32\"}, {\"cidr\": \"10.0.196.1/32\"}, {\"cidr\": \"10.0.197.1/32\"}, {\"cidr\": \"10.0.198.1/32\"}, {\"cidr\": \"10.0.199.1/32\"}, {\"cidr\": \"10.0.200.1/32\"}, {\"cidr\": \"10.0.201.1/32\"}, {\"cidr\": \"10.0.202.1/32\"}, {\"cidr\": \"10.0.203.1/32\"}, {\"cidr\": \"10.0.204.1/32\"}, {\"cidr\": \"10.0.205.1/32\"}, {\"cidr\": \"10.0.206.1/32\"}, {\"cidr\": \"10.0.207.1/32\"}, {\"cidr\": \"10.0.208.1/32\"}, {\"cidr\": \"10.0.209.1/32\"}, {\"cidr\": \"10.0.210.1/32\"}, {\"cidr\": \"10.0.211.1/32\"}, {\"cidr\": \"10.0.212.1/32\"}, {\"cidr\": \"10.0.213.1/32\"}, {\"cidr\": \"10.0.214.1/32\"}, {\"cidr\": \"10.0.215.1/32\"}, {\"cidr\": \"10.0.216.1/32\"}, {\"cidr\": \"10.0.217.1/32\"}, {\"cidr\": \"10.0.218.1/32\"}, {\"cidr\": \"10.0.219.1/32\"}, {\"cidr\": \"10.0.220.1/32\"}, {\"cidr\": \"10.0.221.1/32\"}, {\"cidr\": \"10.0.222.1/32\"}, {\"cidr\": \"10.0.223.1/32\"}, {\"cidr\": \"10.0.224.1/32\"}, {\"cidr\": \"10.0.225.1/32\"}, {\"cidr\": \"10.0.226.1/32\"}, {\"cidr\": \"10.0.227.1/32\"}, {\"cidr\": \"10.0.228.1/32\"}, {\"cidr\": \"10.0.229.1/32\"}, {\"cidr\": \"10.0.230.1/32\"}, {\"cidr\": \"10.0.231.1/32\"}, {\"cidr\": \"10.0.232.1/32\"}, {\"cidr\": \"10.0.233.1/32\"}, {\"cidr\": \"10.0.234.1/32\"}, {\"cidr\": \"10.0.235.1/32\"}, {\"cidr\": \"10.0.236.1/32\"}, {\"cidr\": \"10.0.237.1/32\"}, {\"cidr\": \"10.0.238.1/32\"}, {\"cidr\": \"10.0.239.1/32\"}, {\"cidr\": \"10.0.240.1/32\"}, {\"cidr\": \"10.0.241.1/32\"}, {\"cidr\": \"10.0.242.1/32\"}, {\"cidr\": \"10.0.243.1/32\"}, {\"cidr\": \"10.0.244.1/32\"}, {\"cidr\": \"10.0.245.1/32\"}, {\"cidr\": \"10.0.246.1/32\"}, {\"cidr\": \"10.0.247.1/32\"}, {\"cidr\": \"10.0.248.1/32\"}, {\"cidr\": \"10.0.249.1/32\"}, {\"cidr\": \"10.0.250.1/32\"}, {\"cidr\": \"10.0.251.1/32\"}, {\"cidr\": \"10.0.252.1/32\"}, {\"cidr\": \"10.0.253.1/32\"}, {\"cidr\": \"10.0.254.1/32\"}, {\"cidr\": \"10.0.255.1/32\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\"}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"vector: test_protocol_wide_rules_share_the_native_rule_budget #3","input":[{"text":"{\"deny\": [{\"cidr\": \"10.0.0.1/32\"}, {\"cidr\": \"10.0.1.1/32\"}, {\"cidr\": \"10.0.2.1/32\"}, {\"cidr\": \"10.0.3.1/32\"}, {\"cidr\": \"10.0.4.1/32\"}, {\"cidr\": \"10.0.5.1/32\"}, {\"cidr\": \"10.0.6.1/32\"}, {\"cidr\": \"10.0.7.1/32\"}, {\"cidr\": \"10.0.8.1/32\"}, {\"cidr\": \"10.0.9.1/32\"}, {\"cidr\": \"10.0.10.1/32\"}, {\"cidr\": \"10.0.11.1/32\"}, {\"cidr\": \"10.0.12.1/32\"}, {\"cidr\": \"10.0.13.1/32\"}, {\"cidr\": \"10.0.14.1/32\"}, {\"cidr\": \"10.0.15.1/32\"}, {\"cidr\": \"10.0.16.1/32\"}, {\"cidr\": \"10.0.17.1/32\"}, {\"cidr\": \"10.0.18.1/32\"}, {\"cidr\": \"10.0.19.1/32\"}, {\"cidr\": \"10.0.20.1/32\"}, {\"cidr\": \"10.0.21.1/32\"}, {\"cidr\": \"10.0.22.1/32\"}, {\"cidr\": \"10.0.23.1/32\"}, {\"cidr\": \"10.0.24.1/32\"}, {\"cidr\": \"10.0.25.1/32\"}, {\"cidr\": \"10.0.26.1/32\"}, {\"cidr\": \"10.0.27.1/32\"}, {\"cidr\": \"10.0.28.1/32\"}, {\"cidr\": \"10.0.29.1/32\"}, {\"cidr\": \"10.0.30.1/32\"}, {\"cidr\": \"10.0.31.1/32\"}, {\"cidr\": \"10.0.32.1/32\"}, {\"cidr\": \"10.0.33.1/32\"}, {\"cidr\": \"10.0.34.1/32\"}, {\"cidr\": \"10.0.35.1/32\"}, {\"cidr\": \"10.0.36.1/32\"}, {\"cidr\": \"10.0.37.1/32\"}, {\"cidr\": \"10.0.38.1/32\"}, {\"cidr\": \"10.0.39.1/32\"}, {\"cidr\": \"10.0.40.1/32\"}, {\"cidr\": \"10.0.41.1/32\"}, {\"cidr\": \"10.0.42.1/32\"}, {\"cidr\": \"10.0.43.1/32\"}, {\"cidr\": \"10.0.44.1/32\"}, {\"cidr\": \"10.0.45.1/32\"}, {\"cidr\": \"10.0.46.1/32\"}, {\"cidr\": \"10.0.47.1/32\"}, {\"cidr\": \"10.0.48.1/32\"}, {\"cidr\": \"10.0.49.1/32\"}, {\"cidr\": \"10.0.50.1/32\"}, {\"cidr\": \"10.0.51.1/32\"}, {\"cidr\": \"10.0.52.1/32\"}, {\"cidr\": \"10.0.53.1/32\"}, {\"cidr\": \"10.0.54.1/32\"}, {\"cidr\": \"10.0.55.1/32\"}, {\"cidr\": \"10.0.56.1/32\"}, {\"cidr\": \"10.0.57.1/32\"}, {\"cidr\": \"10.0.58.1/32\"}, {\"cidr\": \"10.0.59.1/32\"}, {\"cidr\": \"10.0.60.1/32\"}, {\"cidr\": \"10.0.61.1/32\"}, {\"cidr\": \"10.0.62.1/32\"}, {\"cidr\": \"10.0.63.1/32\"}, {\"cidr\": \"10.0.64.1/32\"}, {\"cidr\": \"10.0.65.1/32\"}, {\"cidr\": \"10.0.66.1/32\"}, {\"cidr\": \"10.0.67.1/32\"}, {\"cidr\": \"10.0.68.1/32\"}, {\"cidr\": \"10.0.69.1/32\"}, {\"cidr\": \"10.0.70.1/32\"}, {\"cidr\": \"10.0.71.1/32\"}, {\"cidr\": \"10.0.72.1/32\"}, {\"cidr\": \"10.0.73.1/32\"}, {\"cidr\": \"10.0.74.1/32\"}, {\"cidr\": \"10.0.75.1/32\"}, {\"cidr\": \"10.0.76.1/32\"}, {\"cidr\": \"10.0.77.1/32\"}, {\"cidr\": \"10.0.78.1/32\"}, {\"cidr\": \"10.0.79.1/32\"}, {\"cidr\": \"10.0.80.1/32\"}, {\"cidr\": \"10.0.81.1/32\"}, {\"cidr\": \"10.0.82.1/32\"}, {\"cidr\": \"10.0.83.1/32\"}, {\"cidr\": \"10.0.84.1/32\"}, {\"cidr\": \"10.0.85.1/32\"}, {\"cidr\": \"10.0.86.1/32\"}, {\"cidr\": \"10.0.87.1/32\"}, {\"cidr\": \"10.0.88.1/32\"}, {\"cidr\": \"10.0.89.1/32\"}, {\"cidr\": \"10.0.90.1/32\"}, {\"cidr\": \"10.0.91.1/32\"}, {\"cidr\": \"10.0.92.1/32\"}, {\"cidr\": \"10.0.93.1/32\"}, {\"cidr\": \"10.0.94.1/32\"}, {\"cidr\": \"10.0.95.1/32\"}, {\"cidr\": \"10.0.96.1/32\"}, {\"cidr\": \"10.0.97.1/32\"}, {\"cidr\": \"10.0.98.1/32\"}, {\"cidr\": \"10.0.99.1/32\"}, {\"cidr\": \"10.0.100.1/32\"}, {\"cidr\": \"10.0.101.1/32\"}, {\"cidr\": \"10.0.102.1/32\"}, {\"cidr\": \"10.0.103.1/32\"}, {\"cidr\": \"10.0.104.1/32\"}, {\"cidr\": \"10.0.105.1/32\"}, {\"cidr\": \"10.0.106.1/32\"}, {\"cidr\": \"10.0.107.1/32\"}, {\"cidr\": \"10.0.108.1/32\"}, {\"cidr\": \"10.0.109.1/32\"}, {\"cidr\": \"10.0.110.1/32\"}, {\"cidr\": \"10.0.111.1/32\"}, {\"cidr\": \"10.0.112.1/32\"}, {\"cidr\": \"10.0.113.1/32\"}, {\"cidr\": \"10.0.114.1/32\"}, {\"cidr\": \"10.0.115.1/32\"}, {\"cidr\": \"10.0.116.1/32\"}, {\"cidr\": \"10.0.117.1/32\"}, {\"cidr\": \"10.0.118.1/32\"}, {\"cidr\": \"10.0.119.1/32\"}, {\"cidr\": \"10.0.120.1/32\"}, {\"cidr\": \"10.0.121.1/32\"}, {\"cidr\": \"10.0.122.1/32\"}, {\"cidr\": \"10.0.123.1/32\"}, {\"cidr\": \"10.0.124.1/32\"}, {\"cidr\": \"10.0.125.1/32\"}, {\"cidr\": \"10.0.126.1/32\"}, {\"cidr\": \"10.0.127.1/32\"}, {\"cidr\": \"10.0.128.1/32\"}, {\"cidr\": \"10.0.129.1/32\"}, {\"cidr\": \"10.0.130.1/32\"}, {\"cidr\": \"10.0.131.1/32\"}, {\"cidr\": \"10.0.132.1/32\"}, {\"cidr\": \"10.0.133.1/32\"}, {\"cidr\": \"10.0.134.1/32\"}, {\"cidr\": \"10.0.135.1/32\"}, {\"cidr\": \"10.0.136.1/32\"}, {\"cidr\": \"10.0.137.1/32\"}, {\"cidr\": \"10.0.138.1/32\"}, {\"cidr\": \"10.0.139.1/32\"}, {\"cidr\": \"10.0.140.1/32\"}, {\"cidr\": \"10.0.141.1/32\"}, {\"cidr\": \"10.0.142.1/32\"}, {\"cidr\": \"10.0.143.1/32\"}, {\"cidr\": \"10.0.144.1/32\"}, {\"cidr\": \"10.0.145.1/32\"}, {\"cidr\": \"10.0.146.1/32\"}, {\"cidr\": \"10.0.147.1/32\"}, {\"cidr\": \"10.0.148.1/32\"}, {\"cidr\": \"10.0.149.1/32\"}, {\"cidr\": \"10.0.150.1/32\"}, {\"cidr\": \"10.0.151.1/32\"}, {\"cidr\": \"10.0.152.1/32\"}, {\"cidr\": \"10.0.153.1/32\"}, {\"cidr\": \"10.0.154.1/32\"}, {\"cidr\": \"10.0.155.1/32\"}, {\"cidr\": \"10.0.156.1/32\"}, {\"cidr\": \"10.0.157.1/32\"}, {\"cidr\": \"10.0.158.1/32\"}, {\"cidr\": \"10.0.159.1/32\"}, {\"cidr\": \"10.0.160.1/32\"}, {\"cidr\": \"10.0.161.1/32\"}, {\"cidr\": \"10.0.162.1/32\"}, {\"cidr\": \"10.0.163.1/32\"}, {\"cidr\": \"10.0.164.1/32\"}, {\"cidr\": \"10.0.165.1/32\"}, {\"cidr\": \"10.0.166.1/32\"}, {\"cidr\": \"10.0.167.1/32\"}, {\"cidr\": \"10.0.168.1/32\"}, {\"cidr\": \"10.0.169.1/32\"}, {\"cidr\": \"10.0.170.1/32\"}, {\"cidr\": \"10.0.171.1/32\"}, {\"cidr\": \"10.0.172.1/32\"}, {\"cidr\": \"10.0.173.1/32\"}, {\"cidr\": \"10.0.174.1/32\"}, {\"cidr\": \"10.0.175.1/32\"}, {\"cidr\": \"10.0.176.1/32\"}, {\"cidr\": \"10.0.177.1/32\"}, {\"cidr\": \"10.0.178.1/32\"}, {\"cidr\": \"10.0.179.1/32\"}, {\"cidr\": \"10.0.180.1/32\"}, {\"cidr\": \"10.0.181.1/32\"}, {\"cidr\": \"10.0.182.1/32\"}, {\"cidr\": \"10.0.183.1/32\"}, {\"cidr\": \"10.0.184.1/32\"}, {\"cidr\": \"10.0.185.1/32\"}, {\"cidr\": \"10.0.186.1/32\"}, {\"cidr\": \"10.0.187.1/32\"}, {\"cidr\": \"10.0.188.1/32\"}, {\"cidr\": \"10.0.189.1/32\"}, {\"cidr\": \"10.0.190.1/32\"}, {\"cidr\": \"10.0.191.1/32\"}, {\"cidr\": \"10.0.192.1/32\"}, {\"cidr\": \"10.0.193.1/32\"}, {\"cidr\": \"10.0.194.1/32\"}, {\"cidr\": \"10.0.195.1/32\"}, {\"cidr\": \"10.0.196.1/32\"}, {\"cidr\": \"10.0.197.1/32\"}, {\"cidr\": \"10.0.198.1/32\"}, {\"cidr\": \"10.0.199.1/32\"}, {\"cidr\": \"10.0.200.1/32\"}, {\"cidr\": \"10.0.201.1/32\"}, {\"cidr\": \"10.0.202.1/32\"}, {\"cidr\": \"10.0.203.1/32\"}, {\"cidr\": \"10.0.204.1/32\"}, {\"cidr\": \"10.0.205.1/32\"}, {\"cidr\": \"10.0.206.1/32\"}, {\"cidr\": \"10.0.207.1/32\"}, {\"cidr\": \"10.0.208.1/32\"}, {\"cidr\": \"10.0.209.1/32\"}, {\"cidr\": \"10.0.210.1/32\"}, {\"cidr\": \"10.0.211.1/32\"}, {\"cidr\": \"10.0.212.1/32\"}, {\"cidr\": \"10.0.213.1/32\"}, {\"cidr\": \"10.0.214.1/32\"}, {\"cidr\": \"10.0.215.1/32\"}, {\"cidr\": \"10.0.216.1/32\"}, {\"cidr\": \"10.0.217.1/32\"}, {\"cidr\": \"10.0.218.1/32\"}, {\"cidr\": \"10.0.219.1/32\"}, {\"cidr\": \"10.0.220.1/32\"}, {\"cidr\": \"10.0.221.1/32\"}, {\"cidr\": \"10.0.222.1/32\"}, {\"cidr\": \"10.0.223.1/32\"}, {\"cidr\": \"10.0.224.1/32\"}, {\"cidr\": \"10.0.225.1/32\"}, {\"cidr\": \"10.0.226.1/32\"}, {\"cidr\": \"10.0.227.1/32\"}, {\"cidr\": \"10.0.228.1/32\"}, {\"cidr\": \"10.0.229.1/32\"}, {\"cidr\": \"10.0.230.1/32\"}, {\"cidr\": \"10.0.231.1/32\"}, {\"cidr\": \"10.0.232.1/32\"}, {\"cidr\": \"10.0.233.1/32\"}, {\"cidr\": \"10.0.234.1/32\"}, {\"cidr\": \"10.0.235.1/32\"}, {\"cidr\": \"10.0.236.1/32\"}, {\"cidr\": \"10.0.237.1/32\"}, {\"cidr\": \"10.0.238.1/32\"}, {\"cidr\": \"10.0.239.1/32\"}, {\"cidr\": \"10.0.240.1/32\"}, {\"cidr\": \"10.0.241.1/32\"}, {\"cidr\": \"10.0.242.1/32\"}, {\"cidr\": \"10.0.243.1/32\"}, {\"cidr\": \"10.0.244.1/32\"}, {\"cidr\": \"10.0.245.1/32\"}, {\"cidr\": \"10.0.246.1/32\"}, {\"cidr\": \"10.0.247.1/32\"}, {\"cidr\": \"10.0.248.1/32\"}, {\"cidr\": \"10.0.249.1/32\"}, {\"cidr\": \"10.0.250.1/32\"}, {\"cidr\": \"10.0.251.1/32\"}, {\"cidr\": \"10.0.252.1/32\"}, {\"cidr\": \"10.0.253.1/32\"}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 129}]}"}],"expected":{"allow":[],"deny":["10.0.0.1/32","10.0.1.1/32","10.0.2.1/32","10.0.3.1/32","10.0.4.1/32","10.0.5.1/32","10.0.6.1/32","10.0.7.1/32","10.0.8.1/32","10.0.9.1/32","10.0.10.1/32","10.0.11.1/32","10.0.12.1/32","10.0.13.1/32","10.0.14.1/32","10.0.15.1/32","10.0.16.1/32","10.0.17.1/32","10.0.18.1/32","10.0.19.1/32","10.0.20.1/32","10.0.21.1/32","10.0.22.1/32","10.0.23.1/32","10.0.24.1/32","10.0.25.1/32","10.0.26.1/32","10.0.27.1/32","10.0.28.1/32","10.0.29.1/32","10.0.30.1/32","10.0.31.1/32","10.0.32.1/32","10.0.33.1/32","10.0.34.1/32","10.0.35.1/32","10.0.36.1/32","10.0.37.1/32","10.0.38.1/32","10.0.39.1/32","10.0.40.1/32","10.0.41.1/32","10.0.42.1/32","10.0.43.1/32","10.0.44.1/32","10.0.45.1/32","10.0.46.1/32","10.0.47.1/32","10.0.48.1/32","10.0.49.1/32","10.0.50.1/32","10.0.51.1/32","10.0.52.1/32","10.0.53.1/32","10.0.54.1/32","10.0.55.1/32","10.0.56.1/32","10.0.57.1/32","10.0.58.1/32","10.0.59.1/32","10.0.60.1/32","10.0.61.1/32","10.0.62.1/32","10.0.63.1/32","10.0.64.1/32","10.0.65.1/32","10.0.66.1/32","10.0.67.1/32","10.0.68.1/32","10.0.69.1/32","10.0.70.1/32","10.0.71.1/32","10.0.72.1/32","10.0.73.1/32","10.0.74.1/32","10.0.75.1/32","10.0.76.1/32","10.0.77.1/32","10.0.78.1/32","10.0.79.1/32","10.0.80.1/32","10.0.81.1/32","10.0.82.1/32","10.0.83.1/32","10.0.84.1/32","10.0.85.1/32","10.0.86.1/32","10.0.87.1/32","10.0.88.1/32","10.0.89.1/32","10.0.90.1/32","10.0.91.1/32","10.0.92.1/32","10.0.93.1/32","10.0.94.1/32","10.0.95.1/32","10.0.96.1/32","10.0.97.1/32","10.0.98.1/32","10.0.99.1/32","10.0.100.1/32","10.0.101.1/32","10.0.102.1/32","10.0.103.1/32","10.0.104.1/32","10.0.105.1/32","10.0.106.1/32","10.0.107.1/32","10.0.108.1/32","10.0.109.1/32","10.0.110.1/32","10.0.111.1/32","10.0.112.1/32","10.0.113.1/32","10.0.114.1/32","10.0.115.1/32","10.0.116.1/32","10.0.117.1/32","10.0.118.1/32","10.0.119.1/32","10.0.120.1/32","10.0.121.1/32","10.0.122.1/32","10.0.123.1/32","10.0.124.1/32","10.0.125.1/32","10.0.126.1/32","10.0.127.1/32","10.0.128.1/32","10.0.129.1/32","10.0.130.1/32","10.0.131.1/32","10.0.132.1/32","10.0.133.1/32","10.0.134.1/32","10.0.135.1/32","10.0.136.1/32","10.0.137.1/32","10.0.138.1/32","10.0.139.1/32","10.0.140.1/32","10.0.141.1/32","10.0.142.1/32","10.0.143.1/32","10.0.144.1/32","10.0.145.1/32","10.0.146.1/32","10.0.147.1/32","10.0.148.1/32","10.0.149.1/32","10.0.150.1/32","10.0.151.1/32","10.0.152.1/32","10.0.153.1/32","10.0.154.1/32","10.0.155.1/32","10.0.156.1/32","10.0.157.1/32","10.0.158.1/32","10.0.159.1/32","10.0.160.1/32","10.0.161.1/32","10.0.162.1/32","10.0.163.1/32","10.0.164.1/32","10.0.165.1/32","10.0.166.1/32","10.0.167.1/32","10.0.168.1/32","10.0.169.1/32","10.0.170.1/32","10.0.171.1/32","10.0.172.1/32","10.0.173.1/32","10.0.174.1/32","10.0.175.1/32","10.0.176.1/32","10.0.177.1/32","10.0.178.1/32","10.0.179.1/32","10.0.180.1/32","10.0.181.1/32","10.0.182.1/32","10.0.183.1/32","10.0.184.1/32","10.0.185.1/32","10.0.186.1/32","10.0.187.1/32","10.0.188.1/32","10.0.189.1/32","10.0.190.1/32","10.0.191.1/32","10.0.192.1/32","10.0.193.1/32","10.0.194.1/32","10.0.195.1/32","10.0.196.1/32","10.0.197.1/32","10.0.198.1/32","10.0.199.1/32","10.0.200.1/32","10.0.201.1/32","10.0.202.1/32","10.0.203.1/32","10.0.204.1/32","10.0.205.1/32","10.0.206.1/32","10.0.207.1/32","10.0.208.1/32","10.0.209.1/32","10.0.210.1/32","10.0.211.1/32","10.0.212.1/32","10.0.213.1/32","10.0.214.1/32","10.0.215.1/32","10.0.216.1/32","10.0.217.1/32","10.0.218.1/32","10.0.219.1/32","10.0.220.1/32","10.0.221.1/32","10.0.222.1/32","10.0.223.1/32","10.0.224.1/32","10.0.225.1/32","10.0.226.1/32","10.0.227.1/32","10.0.228.1/32","10.0.229.1/32","10.0.230.1/32","10.0.231.1/32","10.0.232.1/32","10.0.233.1/32","10.0.234.1/32","10.0.235.1/32","10.0.236.1/32","10.0.237.1/32","10.0.238.1/32","10.0.239.1/32","10.0.240.1/32","10.0.241.1/32","10.0.242.1/32","10.0.243.1/32","10.0.244.1/32","10.0.245.1/32","10.0.246.1/32","10.0.247.1/32","10.0.248.1/32","10.0.249.1/32","10.0.250.1/32","10.0.251.1/32","10.0.252.1/32","10.0.253.1/32","192.0.2.1/32:tcp:1-129","192.0.2.1/32:udp:1-129"]}}, + {"name":"vector: test_protocol_wide_rules_share_the_native_rule_budget #4","input":[{"text":"{\"deny\": [{\"cidr\": \"10.0.0.1/32\"}, {\"cidr\": \"10.0.1.1/32\"}, {\"cidr\": \"10.0.2.1/32\"}, {\"cidr\": \"10.0.3.1/32\"}, {\"cidr\": \"10.0.4.1/32\"}, {\"cidr\": \"10.0.5.1/32\"}, {\"cidr\": \"10.0.6.1/32\"}, {\"cidr\": \"10.0.7.1/32\"}, {\"cidr\": \"10.0.8.1/32\"}, {\"cidr\": \"10.0.9.1/32\"}, {\"cidr\": \"10.0.10.1/32\"}, {\"cidr\": \"10.0.11.1/32\"}, {\"cidr\": \"10.0.12.1/32\"}, {\"cidr\": \"10.0.13.1/32\"}, {\"cidr\": \"10.0.14.1/32\"}, {\"cidr\": \"10.0.15.1/32\"}, {\"cidr\": \"10.0.16.1/32\"}, {\"cidr\": \"10.0.17.1/32\"}, {\"cidr\": \"10.0.18.1/32\"}, {\"cidr\": \"10.0.19.1/32\"}, {\"cidr\": \"10.0.20.1/32\"}, {\"cidr\": \"10.0.21.1/32\"}, {\"cidr\": \"10.0.22.1/32\"}, {\"cidr\": \"10.0.23.1/32\"}, {\"cidr\": \"10.0.24.1/32\"}, {\"cidr\": \"10.0.25.1/32\"}, {\"cidr\": \"10.0.26.1/32\"}, {\"cidr\": \"10.0.27.1/32\"}, {\"cidr\": \"10.0.28.1/32\"}, {\"cidr\": \"10.0.29.1/32\"}, {\"cidr\": \"10.0.30.1/32\"}, {\"cidr\": \"10.0.31.1/32\"}, {\"cidr\": \"10.0.32.1/32\"}, {\"cidr\": \"10.0.33.1/32\"}, {\"cidr\": \"10.0.34.1/32\"}, {\"cidr\": \"10.0.35.1/32\"}, {\"cidr\": \"10.0.36.1/32\"}, {\"cidr\": \"10.0.37.1/32\"}, {\"cidr\": \"10.0.38.1/32\"}, {\"cidr\": \"10.0.39.1/32\"}, {\"cidr\": \"10.0.40.1/32\"}, {\"cidr\": \"10.0.41.1/32\"}, {\"cidr\": \"10.0.42.1/32\"}, {\"cidr\": \"10.0.43.1/32\"}, {\"cidr\": \"10.0.44.1/32\"}, {\"cidr\": \"10.0.45.1/32\"}, {\"cidr\": \"10.0.46.1/32\"}, {\"cidr\": \"10.0.47.1/32\"}, {\"cidr\": \"10.0.48.1/32\"}, {\"cidr\": \"10.0.49.1/32\"}, {\"cidr\": \"10.0.50.1/32\"}, {\"cidr\": \"10.0.51.1/32\"}, {\"cidr\": \"10.0.52.1/32\"}, {\"cidr\": \"10.0.53.1/32\"}, {\"cidr\": \"10.0.54.1/32\"}, {\"cidr\": \"10.0.55.1/32\"}, {\"cidr\": \"10.0.56.1/32\"}, {\"cidr\": \"10.0.57.1/32\"}, {\"cidr\": \"10.0.58.1/32\"}, {\"cidr\": \"10.0.59.1/32\"}, {\"cidr\": \"10.0.60.1/32\"}, {\"cidr\": \"10.0.61.1/32\"}, {\"cidr\": \"10.0.62.1/32\"}, {\"cidr\": \"10.0.63.1/32\"}, {\"cidr\": \"10.0.64.1/32\"}, {\"cidr\": \"10.0.65.1/32\"}, {\"cidr\": \"10.0.66.1/32\"}, {\"cidr\": \"10.0.67.1/32\"}, {\"cidr\": \"10.0.68.1/32\"}, {\"cidr\": \"10.0.69.1/32\"}, {\"cidr\": \"10.0.70.1/32\"}, {\"cidr\": \"10.0.71.1/32\"}, {\"cidr\": \"10.0.72.1/32\"}, {\"cidr\": \"10.0.73.1/32\"}, {\"cidr\": \"10.0.74.1/32\"}, {\"cidr\": \"10.0.75.1/32\"}, {\"cidr\": \"10.0.76.1/32\"}, {\"cidr\": \"10.0.77.1/32\"}, {\"cidr\": \"10.0.78.1/32\"}, {\"cidr\": \"10.0.79.1/32\"}, {\"cidr\": \"10.0.80.1/32\"}, {\"cidr\": \"10.0.81.1/32\"}, {\"cidr\": \"10.0.82.1/32\"}, {\"cidr\": \"10.0.83.1/32\"}, {\"cidr\": \"10.0.84.1/32\"}, {\"cidr\": \"10.0.85.1/32\"}, {\"cidr\": \"10.0.86.1/32\"}, {\"cidr\": \"10.0.87.1/32\"}, {\"cidr\": \"10.0.88.1/32\"}, {\"cidr\": \"10.0.89.1/32\"}, {\"cidr\": \"10.0.90.1/32\"}, {\"cidr\": \"10.0.91.1/32\"}, {\"cidr\": \"10.0.92.1/32\"}, {\"cidr\": \"10.0.93.1/32\"}, {\"cidr\": \"10.0.94.1/32\"}, {\"cidr\": \"10.0.95.1/32\"}, {\"cidr\": \"10.0.96.1/32\"}, {\"cidr\": \"10.0.97.1/32\"}, {\"cidr\": \"10.0.98.1/32\"}, {\"cidr\": \"10.0.99.1/32\"}, {\"cidr\": \"10.0.100.1/32\"}, {\"cidr\": \"10.0.101.1/32\"}, {\"cidr\": \"10.0.102.1/32\"}, {\"cidr\": \"10.0.103.1/32\"}, {\"cidr\": \"10.0.104.1/32\"}, {\"cidr\": \"10.0.105.1/32\"}, {\"cidr\": \"10.0.106.1/32\"}, {\"cidr\": \"10.0.107.1/32\"}, {\"cidr\": \"10.0.108.1/32\"}, {\"cidr\": \"10.0.109.1/32\"}, {\"cidr\": \"10.0.110.1/32\"}, {\"cidr\": \"10.0.111.1/32\"}, {\"cidr\": \"10.0.112.1/32\"}, {\"cidr\": \"10.0.113.1/32\"}, {\"cidr\": \"10.0.114.1/32\"}, {\"cidr\": \"10.0.115.1/32\"}, {\"cidr\": \"10.0.116.1/32\"}, {\"cidr\": \"10.0.117.1/32\"}, {\"cidr\": \"10.0.118.1/32\"}, {\"cidr\": \"10.0.119.1/32\"}, {\"cidr\": \"10.0.120.1/32\"}, {\"cidr\": \"10.0.121.1/32\"}, {\"cidr\": \"10.0.122.1/32\"}, {\"cidr\": \"10.0.123.1/32\"}, {\"cidr\": \"10.0.124.1/32\"}, {\"cidr\": \"10.0.125.1/32\"}, {\"cidr\": \"10.0.126.1/32\"}, {\"cidr\": \"10.0.127.1/32\"}, {\"cidr\": \"10.0.128.1/32\"}, {\"cidr\": \"10.0.129.1/32\"}, {\"cidr\": \"10.0.130.1/32\"}, {\"cidr\": \"10.0.131.1/32\"}, {\"cidr\": \"10.0.132.1/32\"}, {\"cidr\": \"10.0.133.1/32\"}, {\"cidr\": \"10.0.134.1/32\"}, {\"cidr\": \"10.0.135.1/32\"}, {\"cidr\": \"10.0.136.1/32\"}, {\"cidr\": \"10.0.137.1/32\"}, {\"cidr\": \"10.0.138.1/32\"}, {\"cidr\": \"10.0.139.1/32\"}, {\"cidr\": \"10.0.140.1/32\"}, {\"cidr\": \"10.0.141.1/32\"}, {\"cidr\": \"10.0.142.1/32\"}, {\"cidr\": \"10.0.143.1/32\"}, {\"cidr\": \"10.0.144.1/32\"}, {\"cidr\": \"10.0.145.1/32\"}, {\"cidr\": \"10.0.146.1/32\"}, {\"cidr\": \"10.0.147.1/32\"}, {\"cidr\": \"10.0.148.1/32\"}, {\"cidr\": \"10.0.149.1/32\"}, {\"cidr\": \"10.0.150.1/32\"}, {\"cidr\": \"10.0.151.1/32\"}, {\"cidr\": \"10.0.152.1/32\"}, {\"cidr\": \"10.0.153.1/32\"}, {\"cidr\": \"10.0.154.1/32\"}, {\"cidr\": \"10.0.155.1/32\"}, {\"cidr\": \"10.0.156.1/32\"}, {\"cidr\": \"10.0.157.1/32\"}, {\"cidr\": \"10.0.158.1/32\"}, {\"cidr\": \"10.0.159.1/32\"}, {\"cidr\": \"10.0.160.1/32\"}, {\"cidr\": \"10.0.161.1/32\"}, {\"cidr\": \"10.0.162.1/32\"}, {\"cidr\": \"10.0.163.1/32\"}, {\"cidr\": \"10.0.164.1/32\"}, {\"cidr\": \"10.0.165.1/32\"}, {\"cidr\": \"10.0.166.1/32\"}, {\"cidr\": \"10.0.167.1/32\"}, {\"cidr\": \"10.0.168.1/32\"}, {\"cidr\": \"10.0.169.1/32\"}, {\"cidr\": \"10.0.170.1/32\"}, {\"cidr\": \"10.0.171.1/32\"}, {\"cidr\": \"10.0.172.1/32\"}, {\"cidr\": \"10.0.173.1/32\"}, {\"cidr\": \"10.0.174.1/32\"}, {\"cidr\": \"10.0.175.1/32\"}, {\"cidr\": \"10.0.176.1/32\"}, {\"cidr\": \"10.0.177.1/32\"}, {\"cidr\": \"10.0.178.1/32\"}, {\"cidr\": \"10.0.179.1/32\"}, {\"cidr\": \"10.0.180.1/32\"}, {\"cidr\": \"10.0.181.1/32\"}, {\"cidr\": \"10.0.182.1/32\"}, {\"cidr\": \"10.0.183.1/32\"}, {\"cidr\": \"10.0.184.1/32\"}, {\"cidr\": \"10.0.185.1/32\"}, {\"cidr\": \"10.0.186.1/32\"}, {\"cidr\": \"10.0.187.1/32\"}, {\"cidr\": \"10.0.188.1/32\"}, {\"cidr\": \"10.0.189.1/32\"}, {\"cidr\": \"10.0.190.1/32\"}, {\"cidr\": \"10.0.191.1/32\"}, {\"cidr\": \"10.0.192.1/32\"}, {\"cidr\": \"10.0.193.1/32\"}, {\"cidr\": \"10.0.194.1/32\"}, {\"cidr\": \"10.0.195.1/32\"}, {\"cidr\": \"10.0.196.1/32\"}, {\"cidr\": \"10.0.197.1/32\"}, {\"cidr\": \"10.0.198.1/32\"}, {\"cidr\": \"10.0.199.1/32\"}, {\"cidr\": \"10.0.200.1/32\"}, {\"cidr\": \"10.0.201.1/32\"}, {\"cidr\": \"10.0.202.1/32\"}, {\"cidr\": \"10.0.203.1/32\"}, {\"cidr\": \"10.0.204.1/32\"}, {\"cidr\": \"10.0.205.1/32\"}, {\"cidr\": \"10.0.206.1/32\"}, {\"cidr\": \"10.0.207.1/32\"}, {\"cidr\": \"10.0.208.1/32\"}, {\"cidr\": \"10.0.209.1/32\"}, {\"cidr\": \"10.0.210.1/32\"}, {\"cidr\": \"10.0.211.1/32\"}, {\"cidr\": \"10.0.212.1/32\"}, {\"cidr\": \"10.0.213.1/32\"}, {\"cidr\": \"10.0.214.1/32\"}, {\"cidr\": \"10.0.215.1/32\"}, {\"cidr\": \"10.0.216.1/32\"}, {\"cidr\": \"10.0.217.1/32\"}, {\"cidr\": \"10.0.218.1/32\"}, {\"cidr\": \"10.0.219.1/32\"}, {\"cidr\": \"10.0.220.1/32\"}, {\"cidr\": \"10.0.221.1/32\"}, {\"cidr\": \"10.0.222.1/32\"}, {\"cidr\": \"10.0.223.1/32\"}, {\"cidr\": \"10.0.224.1/32\"}, {\"cidr\": \"10.0.225.1/32\"}, {\"cidr\": \"10.0.226.1/32\"}, {\"cidr\": \"10.0.227.1/32\"}, {\"cidr\": \"10.0.228.1/32\"}, {\"cidr\": \"10.0.229.1/32\"}, {\"cidr\": \"10.0.230.1/32\"}, {\"cidr\": \"10.0.231.1/32\"}, {\"cidr\": \"10.0.232.1/32\"}, {\"cidr\": \"10.0.233.1/32\"}, {\"cidr\": \"10.0.234.1/32\"}, {\"cidr\": \"10.0.235.1/32\"}, {\"cidr\": \"10.0.236.1/32\"}, {\"cidr\": \"10.0.237.1/32\"}, {\"cidr\": \"10.0.238.1/32\"}, {\"cidr\": \"10.0.239.1/32\"}, {\"cidr\": \"10.0.240.1/32\"}, {\"cidr\": \"10.0.241.1/32\"}, {\"cidr\": \"10.0.242.1/32\"}, {\"cidr\": \"10.0.243.1/32\"}, {\"cidr\": \"10.0.244.1/32\"}, {\"cidr\": \"10.0.245.1/32\"}, {\"cidr\": \"10.0.246.1/32\"}, {\"cidr\": \"10.0.247.1/32\"}, {\"cidr\": \"10.0.248.1/32\"}, {\"cidr\": \"10.0.249.1/32\"}, {\"cidr\": \"10.0.250.1/32\"}, {\"cidr\": \"10.0.251.1/32\"}, {\"cidr\": \"10.0.252.1/32\"}, {\"cidr\": \"10.0.253.1/32\"}, {\"cidr\": \"10.0.254.1/32\"}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 129}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"vector: test_rejects_257_rules_before_materializing_them #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 1}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 3}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 5}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 7}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 9}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 11}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 13}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 15}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 17}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 21}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 23}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 25}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 27}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 29}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 31}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 33}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 35}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 37}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 39}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 41}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 43}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 45}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 47}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 49}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 51}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 53}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 55}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 57}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 59}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 61}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 63}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 65}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 67}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 69}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 71}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 73}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 75}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 77}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 79}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 81}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 83}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 85}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 87}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 89}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 91}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 93}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 95}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 97}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 99}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 101}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 103}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 105}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 107}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 109}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 111}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 113}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 115}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 117}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 119}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 121}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 123}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 125}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 127}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 129}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 131}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 133}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 135}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 137}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 139}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 141}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 143}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 145}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 147}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 149}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 151}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 153}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 155}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 157}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 159}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 161}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 163}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 165}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 167}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 169}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 171}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 173}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 175}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 177}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 179}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 181}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 183}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 185}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 187}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 189}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 191}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 193}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 195}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 197}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 199}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 201}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 203}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 205}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 207}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 209}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 211}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 213}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 215}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 217}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 219}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 221}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 223}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 225}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 227}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 229}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 231}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 233}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 235}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 237}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 239}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 241}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 243}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 245}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 247}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 249}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 251}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 253}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 255}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 257}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 259}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 261}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 263}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 265}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 267}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 269}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 271}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 273}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 275}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 277}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 279}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 281}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 283}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 285}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 287}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 289}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 291}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 293}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 295}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 297}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 299}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 301}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 303}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 305}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 307}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 309}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 311}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 313}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 315}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 317}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 319}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 321}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 323}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 325}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 327}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 329}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 331}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 333}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 335}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 337}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 339}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 341}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 343}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 345}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 347}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 349}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 351}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 353}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 355}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 357}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 359}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 361}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 363}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 365}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 367}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 369}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 371}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 373}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 375}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 377}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 379}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 381}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 383}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 385}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 387}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 389}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 391}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 393}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 395}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 397}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 399}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 401}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 403}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 405}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 407}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 409}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 411}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 413}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 415}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 417}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 419}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 421}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 423}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 425}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 427}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 429}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 431}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 433}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 435}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 437}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 439}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 441}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 445}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 447}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 449}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 451}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 453}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 455}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 457}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 459}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 461}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 463}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 465}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 467}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 469}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 471}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 473}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 475}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 477}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 479}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 481}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 483}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 485}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 487}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 489}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 491}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 493}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 495}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 497}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 499}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 501}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 503}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 505}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 507}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 509}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 511}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 513}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"vector: test_rejects_257_rules_before_materializing_them #2","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 1}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 3}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 5}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 7}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 9}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 11}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 13}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 15}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 17}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 21}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 23}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 25}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 27}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 29}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 31}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 33}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 35}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 37}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 39}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 41}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 43}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 45}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 47}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 49}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 51}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 53}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 55}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 57}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 59}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 61}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 63}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 65}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 67}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 69}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 71}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 73}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 75}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 77}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 79}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 81}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 83}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 85}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 87}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 89}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 91}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 93}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 95}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 97}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 99}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 101}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 103}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 105}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 107}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 109}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 111}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 113}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 115}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 117}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 119}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 121}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 123}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 125}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 127}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 129}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 131}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 133}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 135}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 137}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 139}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 141}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 143}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 145}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 147}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 149}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 151}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 153}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 155}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 157}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 159}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 161}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 163}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 165}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 167}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 169}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 171}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 173}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 175}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 177}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 179}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 181}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 183}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 185}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 187}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 189}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 191}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 193}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 195}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 197}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 199}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 201}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 203}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 205}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 207}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 209}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 211}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 213}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 215}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 217}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 219}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 221}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 223}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 225}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 227}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 229}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 231}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 233}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 235}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 237}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 239}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 241}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 243}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 245}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 247}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 249}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 251}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 253}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 255}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 257}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 259}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 261}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 263}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 265}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 267}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 269}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 271}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 273}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 275}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 277}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 279}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 281}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 283}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 285}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 287}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 289}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 291}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 293}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 295}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 297}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 299}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 301}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 303}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 305}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 307}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 309}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 311}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 313}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 315}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 317}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 319}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 321}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 323}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 325}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 327}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 329}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 331}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 333}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 335}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 337}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 339}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 341}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 343}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 345}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 347}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 349}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 351}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 353}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 355}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 357}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 359}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 361}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 363}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 365}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 367}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 369}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 371}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 373}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 375}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 377}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 379}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 381}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 383}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 385}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 387}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 389}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 391}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 393}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 395}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 397}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 399}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 401}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 403}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 405}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 407}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 409}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 411}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 413}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 415}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 417}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 419}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 421}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 423}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 425}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 427}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 429}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 431}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 433}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 435}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 437}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 439}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 441}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 445}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 447}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 449}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 451}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 453}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 455}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 457}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 459}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 461}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 463}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 465}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 467}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 469}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 471}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 473}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 475}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 477}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 479}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 481}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 483}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 485}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 487}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 489}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 491}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 493}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 495}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 497}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 499}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 501}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 503}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 505}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 507}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 509}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 511}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"tcp\", \"port\": 513}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"vector: test_rejects_duplicate_json_fields #1","input":[{"text":"{\"deny\":[{\"cidr\":\"0.0.0.0/0\"}],\"deny\":[]}"}],"expected":{"error":"duplicate JSON property: deny"}}, + {"name":"vector: test_rejects_duplicate_json_fields #2","input":[{"text":"{\"allow\":[{\"cidr\":\"192.0.2.0/24\",\"port\":80,\"port\":443,\"protocol\":\"tcp\"}]}"}],"expected":{"error":"duplicate JSON property: port"}}, + {"name":"vector: test_rejects_exclusions_outside_parent_or_wrong_family #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"198.51.100.0/24\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"vector: test_rejects_exclusions_outside_parent_or_wrong_family #2","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"2001:db8::/32\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"vector: test_rejects_exclusions_outside_parent_or_wrong_family #3","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::/32\", \"except\": [\"2001:db9::/48\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must be contained in 2001:db8::/32"}}, + {"name":"vector: test_rejects_exclusions_outside_parent_or_wrong_family #4","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::/32\", \"except\": [\"192.0.2.0/24\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must be contained in 2001:db8::/32"}}, + {"name":"vector: test_rejects_exclusions_outside_parent_or_wrong_family #5","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/96\", \"except\": [\"0.0.0.0/8\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must be contained in ::/96"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #1","input":[{"text":"{\"allow\": [{\"to\": []}]}"}],"expected":{"error":"allow[0].to must contain at least one destination"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #2","input":[{"text":"{\"allow\": [{\"to\": [{}]}]}"}],"expected":{"error":"allow[0].to[0].cidr is required"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #3","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"unknown\": true}]}]}"}],"expected":{"error":"allow[0].to[0] has unknown field 'unknown'"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #4","input":[{"text":"{\"allow\": [{\"ports\": []}]}"}],"expected":{"error":"allow[0].ports must contain at least one selector"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #5","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"icmp\", \"port\": 8}]}]}"}],"expected":{"error":"allow[0].ports[0].port is not supported with icmp"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #6","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"ICMP\"}]}]}"}],"expected":{"error":"allow[0].ports[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #7","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"sctp\"}]}]}"}],"expected":{"error":"allow[0].ports[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #8","input":[{"text":"{\"allow\": [{\"ports\": [{\"endPort\": 80}]}]}"}],"expected":{"error":"allow[0].ports[0].endPort requires port"}}, + {"name":"vector: test_rejects_invalid_mxc_rule_shapes #9","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"cidr\": \"192.0.2.0/24\"}]}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"endPort\": 80}]}"}],"expected":{"error":"allow[0].endPort requires port"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"any\", \"endPort\": 80}]}"}],"expected":{"error":"allow[0].endPort requires port"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #3","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 81, \"endPort\": 80}]}"}],"expected":{"error":"allow[0].endPort 80 cannot be below port 81"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #4","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 0}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #5","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"udp\", \"port\": 65536}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #6","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": true}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #7","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"any\", \"port\": 0}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #8","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"port\": 80}]}"}],"expected":{"error":"allow[0].port requires protocol"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #9","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\", \"port\": 8}]}"}],"expected":{"error":"allow[0].port is not supported with icmp"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #10","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\", \"endPort\": 8}]}"}],"expected":{"error":"allow[0].endPort requires port"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #11","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"ICMP\"}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"vector: test_rejects_invalid_port_shapes_and_values #12","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"sctp\"}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"vector: test_rejects_ipv6_scopes #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"fe80::%eth0/64\"}]}]}"}],"expected":{"error":"allow[0].to[0].cidr must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #2","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::%eth0/64\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #3","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::%eth0/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #4","input":[{"text":"{\"allow\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::%eth0/64\"]}]}"}],"expected":{"error":"allow[0].except[0] must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #5","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"fe80::1%1\"}]}]}"}],"expected":{"error":"allow[0].to[0].cidr must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #6","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #7","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%1\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #8","input":[{"text":"{\"allow\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1\"]}]}"}],"expected":{"error":"allow[0].except[0] must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #9","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"fe80::1%eth0/64\"}]}]}"}],"expected":{"error":"allow[0].to[0].cidr must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #10","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%eth0/64\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #11","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%eth0/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #12","input":[{"text":"{\"allow\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%eth0/64\"]}]}"}],"expected":{"error":"allow[0].except[0] must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #13","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"fe80::1%1/64\"}]}]}"}],"expected":{"error":"allow[0].to[0].cidr must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #14","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1/64\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #15","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%1/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #16","input":[{"text":"{\"allow\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1/64\"]}]}"}],"expected":{"error":"allow[0].except[0] must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #17","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::ffff:192.0.2.1%eth0/120\"}]}]}"}],"expected":{"error":"allow[0].to[0].cidr must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_ipv6_scopes #18","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"::ffff:192.0.2.1%eth0/120\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_ipv6_scopes #19","input":[{"text":"{\"allow\": [{\"cidr\": \"::ffff:192.0.2.1%eth0/120\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_ipv6_scopes #20","input":[{"text":"{\"allow\": [{\"cidr\": \"::/0\", \"except\": [\"::ffff:192.0.2.1%eth0/120\"]}]}"}],"expected":{"error":"allow[0].except[0] must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_ipv6_scopes #21","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"fe80::%eth0/64\"}]}]}"}],"expected":{"error":"deny[0].to[0].cidr must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #22","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::%eth0/64\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #23","input":[{"text":"{\"deny\": [{\"cidr\": \"fe80::%eth0/64\"}]}"}],"expected":{"error":"deny[0].cidr must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #24","input":[{"text":"{\"deny\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::%eth0/64\"]}]}"}],"expected":{"error":"deny[0].except[0] must not name an IPv6 scope: fe80::%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #25","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"fe80::1%1\"}]}]}"}],"expected":{"error":"deny[0].to[0].cidr must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #26","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #27","input":[{"text":"{\"deny\": [{\"cidr\": \"fe80::1%1\"}]}"}],"expected":{"error":"deny[0].cidr must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #28","input":[{"text":"{\"deny\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1\"]}]}"}],"expected":{"error":"deny[0].except[0] must not name an IPv6 scope: fe80::1%1"}}, + {"name":"vector: test_rejects_ipv6_scopes #29","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"fe80::1%eth0/64\"}]}]}"}],"expected":{"error":"deny[0].to[0].cidr must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #30","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%eth0/64\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #31","input":[{"text":"{\"deny\": [{\"cidr\": \"fe80::1%eth0/64\"}]}"}],"expected":{"error":"deny[0].cidr must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #32","input":[{"text":"{\"deny\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%eth0/64\"]}]}"}],"expected":{"error":"deny[0].except[0] must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #33","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"fe80::1%1/64\"}]}]}"}],"expected":{"error":"deny[0].to[0].cidr must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #34","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1/64\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #35","input":[{"text":"{\"deny\": [{\"cidr\": \"fe80::1%1/64\"}]}"}],"expected":{"error":"deny[0].cidr must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #36","input":[{"text":"{\"deny\": [{\"cidr\": \"::/0\", \"except\": [\"fe80::1%1/64\"]}]}"}],"expected":{"error":"deny[0].except[0] must not name an IPv6 scope: fe80::1%1/64"}}, + {"name":"vector: test_rejects_ipv6_scopes #37","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"::ffff:192.0.2.1%eth0/120\"}]}]}"}],"expected":{"error":"deny[0].to[0].cidr must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_ipv6_scopes #38","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"::/0\", \"except\": [\"::ffff:192.0.2.1%eth0/120\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_ipv6_scopes #39","input":[{"text":"{\"deny\": [{\"cidr\": \"::ffff:192.0.2.1%eth0/120\"}]}"}],"expected":{"error":"deny[0].cidr must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_ipv6_scopes #40","input":[{"text":"{\"deny\": [{\"cidr\": \"::/0\", \"except\": [\"::ffff:192.0.2.1%eth0/120\"]}]}"}],"expected":{"error":"deny[0].except[0] must not name an IPv6 scope: ::ffff:192.0.2.1%eth0/120"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #1","input":[{"text":"[]"}],"expected":{"error":"egress policy must be an object"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #2","input":[{"text":"{\"unknown\": []}"}],"expected":{"error":"egress policy has unknown field 'unknown'"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #3","input":[{"text":"{\"allow\": {}}"}],"expected":{"error":"allow must be an array"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #4","input":[{"text":"{\"allow\": [\"192.0.2.0/24\"]}"}],"expected":{"error":"allow[0] must be an object"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #5","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"unknown\": 1}]}"}],"expected":{"error":"allow[0] has unknown field 'unknown'"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #6","input":[{"text":"{\"allow\": [{\"cidr\": 7}]}"}],"expected":{"error":"allow[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #7","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": \"192.0.2.1\"}]}"}],"expected":{"error":"allow[0].except must be an array"}}, + {"name":"vector: test_rejects_unknown_and_malformed_fields #8","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": null}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #1","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 8010, \"endPort\": 8000}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort 8000 cannot be below port 8010"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #2","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"udp\", \"endPort\": 8010}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort requires port"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #3","input":[{"text":"{\"deny\": [{\"ports\": [{\"endPort\": 8010}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort requires port"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #4","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 65536}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #5","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 8010, \"endPort\": 8000}]}"}],"expected":{"error":"deny[0].endPort 8000 cannot be below port 8010"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #6","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"udp\", \"endPort\": 8010}]}"}],"expected":{"error":"deny[0].endPort requires port"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #7","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"endPort\": 8010}]}"}],"expected":{"error":"deny[0].endPort requires port"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #8","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 8000, \"endPort\": 65536}]}"}],"expected":{"error":"deny[0].endPort must be between 1 and 65535"}}, + {"name":"vector: test_reports_invalid_port_ranges_clearly #9","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 0, \"endPort\": 8010}]}"}],"expected":{"error":"deny[0].port must be between 1 and 65535"}}, + {"name":"vector: test_reports_oversized_json_integer_rejection #1","input":[{"text":"{\"allow\":[{\"cidr\":"},{"text":"9","repeat":10000},{"text":"}]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"vector: test_subtracts_ipv6_exclusions #1","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::/32\", \"except\": [\"2001:db8:1::/48\", \"2001:db8:1::/64\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 22}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::/48:tcp:22","2001:db8:2::/47:tcp:22","2001:db8:4::/46:tcp:22","2001:db8:8::/45:tcp:22","2001:db8:10::/44:tcp:22","2001:db8:20::/43:tcp:22","2001:db8:40::/42:tcp:22","2001:db8:80::/41:tcp:22","2001:db8:100::/40:tcp:22","2001:db8:200::/39:tcp:22","2001:db8:400::/38:tcp:22","2001:db8:800::/37:tcp:22","2001:db8:1000::/36:tcp:22","2001:db8:2000::/35:tcp:22","2001:db8:4000::/34:tcp:22","2001:db8:8000::/33:tcp:22"]}}, + {"name":"vector: test_subtracts_rule_local_cidr_exclusions #1","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\"]}]}, {\"to\": [{\"cidr\": \"192.0.2.200/32\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"198.51.100.0/24\", \"except\": [\"198.51.100.128/25\"]}]}]}"}],"expected":{"allow":["192.0.2.0/25","192.0.2.200/32"],"deny":["198.51.100.0/25"]}}, + {"name":"vector: test_validates_redundant_rules_before_canonicalization #1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"}, {\"cidr\": \"192.0.2.1\", \"unknown\": true}]}"}],"expected":{"error":"allow[1] has unknown field 'unknown'"}}, + {"name":"json: empty file","input":[{"text":""}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: whitespace only","input":[{"text":" \t\r\n"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: null","input":[{"text":"null"}],"expected":{"error":"egress policy must be an object"}}, + {"name":"json: array root","input":[{"text":"[]"}],"expected":{"error":"egress policy must be an object"}}, + {"name":"json: number root","input":[{"text":"1"}],"expected":{"error":"egress policy must be an object"}}, + {"name":"json: string root","input":[{"text":"\"allow\""}],"expected":{"error":"egress policy must be an object"}}, + {"name":"json: empty object","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"json: surrounding whitespace","input":[{"text":" \t\n\r{\"allow\": []}\r\n "}],"expected":{"allow":[],"deny":[]}}, + {"name":"json: form feed after","input":[{"text":"{\"allow\": []}\f"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: vertical tab before","input":[{"text":"\u000b{\"allow\": []}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: no-break space after","input":[{"text":"{\"allow\": []}\u00a0"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: trailing comma in object","input":[{"text":"{\"allow\": [],}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: trailing comma in array","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"},]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: comment","input":[{"text":"{\"allow\": []} // comment"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: single quotes","input":[{"text":"{'allow': []}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: unquoted name","input":[{"text":"{allow: []}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: missing colon","input":[{"text":"{\"allow\" []}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: missing comma","input":[{"text":"{\"allow\": [] \"deny\": []}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: unclosed object","input":[{"text":"{\"allow\": []"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: unclosed string","input":[{"text":"{\"allow"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: extra value","input":[{"text":"{\"allow\": []} {}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: two roots","input":[{"text":"{}{}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: constant prefix","input":[{"text":"{\"allow\": nullx}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: raw tab in string","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\t\"}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: escaped tab in string","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\\t\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 192.0.2.0/24\t"}}, + {"name":"json: raw newline in name","input":[{"text":"{\"al\nlow\": []}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: invalid escape","input":[{"text":"{\"allow\": [{\"cidr\": \"\\x41\"}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: short unicode escape","input":[{"text":"{\"allow\": [{\"cidr\": \"\\u41\"}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: solidus escape","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0\\/24\"}]}"}],"expected":{"allow":["192.0.2.0/24"],"deny":[]}}, + {"name":"json: escaped names","input":[{"text":"{\"\\u0061llow\": [{\"\\u0063idr\": \"192.0.2.7\"}]}"}],"expected":{"allow":["192.0.2.7/32"],"deny":[]}}, + {"name":"json: escaped duplicate","input":[{"text":"{\"allow\": [], \"\\u0061llow\": []}"}],"expected":{"error":"duplicate JSON property: allow"}}, + {"name":"json: deep arrays in a rule list","input":[{"text":"{\"allow\": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}"}],"expected":{"error":"allow[0] must be an object"}}, + {"name":"json: deep arrays","input":[{"text":"[","repeat":500},{"text":"]","repeat":500}],"expected":{"error":"egress policy must be an object"}}, + {"name":"json: deep objects","input":[{"text":"{\"allow\": [{\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": {\"a\": 1}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}]}"}],"expected":{"error":"allow[0] has unknown field 'a'"}}, + {"name":"json: port 01","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 01}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort 01","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 01}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port 1.","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1.}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort 1.","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 1.}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port .5","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": .5}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort .5","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": .5}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port +80","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": +80}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort +80","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": +80}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port 0x50","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 0x50}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort 0x50","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 0x50}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port 8e","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 8e}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort 8e","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 8e}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port 8e+","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 8e+}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort 8e+","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 8e+}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port nan","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": nan}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort nan","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": nan}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port -NaN","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": -NaN}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort -NaN","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": -NaN}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port Inf","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": Inf}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: endPort Inf","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": Inf}]}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: port NaN","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": NaN}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort NaN","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": NaN}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port Infinity","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": Infinity}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort Infinity","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": Infinity}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port -Infinity","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": -Infinity}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort -Infinity","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": -Infinity}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port 1e400","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1e400}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort 1e400","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 1e400}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port -1e400","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": -1e400}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort -1e400","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": -1e400}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port 80.0","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 80.0}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort 80.0","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 80.0}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port 8e1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 8e1}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort 8e1","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 8e1}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port 1E2","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1E2}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort 1E2","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 1E2}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port 1e-400","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1e-400}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort 1e-400","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 1e-400}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port -0","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": -0}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"json: endPort -0","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": -0}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"json: port 0","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 0}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"json: endPort 0","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 0}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"json: port true","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": true}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort true","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": true}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port false","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": false}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort false","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": false}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port null","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": null}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort null","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": null}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port \"80\"","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": \"80\"}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort \"80\"","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": \"80\"}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port [80]","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": [80]}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort [80]","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": [80]}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port {}","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": {}}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"json: endPort {}","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": {}}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be an integer"}}, + {"name":"json: port 65535","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 65535}]}"}],"expected":{"allow":["192.0.2.0/24:tcp:65535"],"deny":[]}}, + {"name":"json: endPort 65535","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 65535}]}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0:tcp:1-65535","0.0.0.0/0:udp:1-65535","::/0:tcp:1-65535","::/0:udp:1-65535"]}}, + {"name":"json: port 65536","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 65536}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"json: endPort 65536","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 65536}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"json: port -1","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": -1}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"json: endPort -1","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": -1}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"json: port 9999999999999999","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 9999999999999999999999999999999999999999999999999999999999999999}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"json: endPort 9999999999999999","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 9999999999999999999999999999999999999999999999999999999999999999}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"json: port -999999999999999","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": -9999999999999999999999999999999999999999999999999999999999999999}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"json: endPort -999999999999999","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": -9999999999999999999999999999999999999999999999999999999999999999}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"json: port 9999999999999999","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 99999999999999999999999999999999999999999999999999999999999999999}]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: endPort 9999999999999999","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 99999999999999999999999999999999999999999999999999999999999999999}]}]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: port -999999999999999","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": -99999999999999999999999999999999999999999999999999999999999999999}]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: endPort -999999999999999","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": -99999999999999999999999999999999999999999999999999999999999999999}]}]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: port 1000000000000000","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1"},{"text":"0","repeat":4300},{"text":"}]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: endPort 1000000000000000","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1, \"endPort\": 1"},{"text":"0","repeat":4300},{"text":"}]}]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: duplicate root field","input":[{"text":"{\"deny\": [{\"cidr\": \"0.0.0.0/0\"}], \"deny\": []}"}],"expected":{"error":"duplicate JSON property: deny"}}, + {"name":"json: duplicate rule field","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.0/8\", \"cidr\": \"::/0\"}]}"}],"expected":{"error":"duplicate JSON property: cidr"}}, + {"name":"json: duplicate port field","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": 1, \"port\": 2}]}]}"}],"expected":{"error":"duplicate JSON property: port"}}, + {"name":"json: duplicate peer field","input":[{"text":"{\"allow\": [{\"to\": [{\"except\": [], \"except\": []}]}]}"}],"expected":{"error":"duplicate JSON property: except"}}, + {"name":"json: first duplicate in order","input":[{"text":"{\"b\": 1, \"a\": 2, \"a\": 3, \"b\": 4}"}],"expected":{"error":"duplicate JSON property: a"}}, + {"name":"json: duplicate before long integer","input":[{"text":"{\"a\": 1, \"a\": 2, \"b\": 99999999999999999999999999999999999999999999999999999999999999999}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: inner duplicate first","input":[{"text":"{\"a\": {\"x\": 1, \"x\": 2}, \"b\": 99999999999999999999999999999999999999999999999999999999999999999}"}],"expected":{"error":"duplicate JSON property: x"}}, + {"name":"json: long integer first","input":[{"text":"{\"b\": 99999999999999999999999999999999999999999999999999999999999999999, \"a\": {\"x\": 1, \"x\": 2}}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: duplicate before syntax error","input":[{"text":"{\"a\": 1, \"a\": 2,}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: duplicate before extra data","input":[{"text":"{\"a\": 1, \"a\": 2} x"}],"expected":{"error":"duplicate JSON property: a"}}, + {"name":"json: long integer before syntax error","input":[{"text":"[99999999999999999999999999999999999999999999999999999999999999999x]"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: long integer in unknown field","input":[{"text":"{\"unknown\": [99999999999999999999999999999999999999999999999999999999999999999]}"}],"expected":{"error":"JSON integer exceeds 64-digit limit"}}, + {"name":"json: duplicate in unknown field","input":[{"text":"{\"unknown\": {\"x\": 1, \"x\": 2}}"}],"expected":{"error":"duplicate JSON property: x"}}, + {"name":"json: duplicate surrogate names","input":[{"text":"{\"\\ud800\": 1, \"\\ud800\": 2}"}],"expected":{"error":"duplicate JSON property: \\ud800"}}, + {"name":"json: distinct surrogate names","input":[{"text":"{\"\\ud800\": 1, \"\\udc00\": 2}"}],"expected":{"error":"egress policy has unknown field '\\ud800'"}}, + {"name":"json: surrogate root name","input":[{"text":"{\"\\ud800\": []}"}],"expected":{"error":"egress policy has unknown field '\\ud800'"}}, + {"name":"json: surrogate rule name","input":[{"text":"{\"allow\": [{\"\\udfff\": 1}]}"}],"expected":{"error":"allow[0] has unknown field '\\udfff'"}}, + {"name":"json: surrogate cidr","input":[{"text":"{\"allow\": [{\"cidr\": \"\\ud800\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: \\ud800"}}, + {"name":"json: surrogate scope","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%\\ud800/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%\\ud800/64"}}, + {"name":"json: surrogate protocol","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"\\udc00\"}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"json: paired surrogates","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%\\ud83d\\ude00/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%\ud83d\ude00/64"}}, + {"name":"json: reversed surrogates","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%\\ude00\\ud83d/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%\\ude00\\ud83d/64"}}, + {"name":"json: high surrogate before letter","input":[{"text":"{\"allow\": [{\"cidr\": \"\\ud800A\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: \\ud800A"}}, + {"name":"json: high surrogate before bad escape","input":[{"text":"{\"allow\": [{\"cidr\": \"\\ud800\\u12g4\"}]}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"json: surrogate exception","input":[{"text":"{\"allow\": [{\"cidr\": \"::/0\", \"except\": [\"\\udbff\"]}]}"}],"expected":{"error":"allow[0].except[0] is not a valid IPv4 or IPv6 CIDR: \\udbff"}}, + {"name":"utf-8: byte-order mark","input":[{"text":"\ufeff{\"allow\": []}"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"utf-8: invalid byte","input":[{"hex":"7b22616c6c6f77223a205bff5d7d"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"utf-8: invalid byte in string","input":[{"hex":"7b22616c6c6f77223a205b7b2263696472223a20223139322e302e322e302f3234fe227d5d7d"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"utf-8: overlong encoding","input":[{"hex":"7b22616c6c6f77223a205b7b2263696472223a2022c0af227d5d7d"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"utf-8: encoded surrogate","input":[{"hex":"7b22616c6c6f77223a205b7b2263696472223a2022eda080227d5d7d"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"utf-8: truncated sequence","input":[{"hex":"7b22e282223a205b5d7d"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"utf-8: utf-16","input":[{"hex":"fffe7b00220061006c006c006f00770022003a0020005b005d007d00"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"utf-8: non-ascii text","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\u00e9\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 192.0.2.0/24\u00e9"}}, + {"name":"unknown fields: ascii","input":[{"text":"{\"b\": 1, \"a\": 1, \"B\": 1}"}],"expected":{"error":"egress policy has unknown field 'B'"}}, + {"name":"unknown rule fields: ascii","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"b\": 1, \"a\": 1, \"B\": 1}]}"}],"expected":{"error":"allow[0] has unknown field 'B'"}}, + {"name":"unknown fields: non-ascii","input":[{"text":"{\"\u00e9\": 1, \"z\": 1, \"\u00ff\": 1}"}],"expected":{"error":"egress policy has unknown field 'z'"}}, + {"name":"unknown rule fields: non-ascii","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"\u00e9\": 1, \"z\": 1, \"\u00ff\": 1}]}"}],"expected":{"error":"allow[0] has unknown field 'z'"}}, + {"name":"unknown fields: astral and surrogate","input":[{"text":"{\"\\ud83d\\ude00\": 1, \"\\ud800\": 1, \"\\uffff\": 1}"}],"expected":{"error":"egress policy has unknown field '\\ud800'"}}, + {"name":"unknown rule fields: astral and surrogate","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"\\ud83d\\ude00\": 1, \"\\ud800\": 1, \"\\uffff\": 1}]}"}],"expected":{"error":"allow[0] has unknown field '\\ud800'"}}, + {"name":"allow: rule string","input":[{"text":"{\"allow\": [\"192.0.2.0/24\"]}"}],"expected":{"error":"allow[0] must be an object"}}, + {"name":"allow: rule number","input":[{"text":"{\"allow\": [7]}"}],"expected":{"error":"allow[0] must be an object"}}, + {"name":"allow: rule null","input":[{"text":"{\"allow\": [null]}"}],"expected":{"error":"allow[0] must be an object"}}, + {"name":"allow: rule array","input":[{"text":"{\"allow\": [[{\"cidr\": \"192.0.2.0/24\"}]]}"}],"expected":{"error":"allow[0] must be an object"}}, + {"name":"allow: empty rule","input":[{"text":"{\"allow\": [{}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":[]}}, + {"name":"allow: mixed forms","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"cidr\": \"192.0.2.0/24\"}]}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"allow: mixed ports","input":[{"text":"{\"allow\": [{\"ports\": [], \"port\": 80}]}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"allow: legacy unknown field","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"unknown\": 1}]}"}],"expected":{"error":"allow[0] has unknown field 'unknown'"}}, + {"name":"allow: legacy unknown before missing cidr","input":[{"text":"{\"allow\": [{\"port\": 80, \"unknown\": 1}]}"}],"expected":{"error":"allow[0] has unknown field 'unknown'"}}, + {"name":"allow: legacy missing cidr","input":[{"text":"{\"allow\": [{\"protocol\": \"tcp\", \"port\": 80}]}"}],"expected":{"error":"allow[0].cidr is required"}}, + {"name":"allow: legacy except only","input":[{"text":"{\"allow\": [{\"except\": []}]}"}],"expected":{"error":"allow[0].cidr is required"}}, + {"name":"allow: legacy cidr number","input":[{"text":"{\"allow\": [{\"cidr\": 7}]}"}],"expected":{"error":"allow[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"allow: legacy cidr null","input":[{"text":"{\"allow\": [{\"cidr\": null}]}"}],"expected":{"error":"allow[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"allow: legacy cidr invalid","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/33\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 192.0.2.0/33"}}, + {"name":"allow: legacy cidr scoped","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%eth0/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"allow: legacy except string","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": \"192.0.2.1\"}]}"}],"expected":{"error":"allow[0].except must be an array"}}, + {"name":"allow: legacy except null","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": null}]}"}],"expected":{"error":"allow[0].except must be an array"}}, + {"name":"allow: legacy except number","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [7]}]}"}],"expected":{"error":"allow[0].except[0] must be an IPv4 or IPv6 CIDR string"}}, + {"name":"allow: legacy except outside","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"198.51.100.0/24\"]}]}"}],"expected":{"error":"allow[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"allow: legacy except family","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"2001:db8::/32\"]}]}"}],"expected":{"error":"allow[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"allow: legacy except wider","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.0.0/16\"]}]}"}],"expected":{"error":"allow[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"allow: legacy except whole","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.0/24\"]}]}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"allow: legacy except invalid","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.0/24\", \"x\"]}]}"}],"expected":{"error":"allow[0].except[1] is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"allow: legacy endPort without port","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"endPort\": 80}]}"}],"expected":{"error":"allow[0].endPort requires port"}}, + {"name":"allow: legacy endPort with protocol","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"endPort\": 80}]}"}],"expected":{"error":"allow[0].endPort requires port"}}, + {"name":"allow: legacy port without protocol","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"port\": 80}]}"}],"expected":{"error":"allow[0].port requires protocol"}}, + {"name":"allow: legacy protocol upper","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"TCP\"}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"allow: legacy protocol unknown","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"sctp\"}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"allow: legacy protocol empty","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"\"}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"allow: legacy protocol null","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": null}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"allow: legacy protocol number","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": 6}]}"}],"expected":{"error":"allow[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"allow: legacy icmp port","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\", \"port\": 8}]}"}],"expected":{"error":"allow[0].port is not supported with icmp"}}, + {"name":"allow: legacy icmp endPort","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\", \"endPort\": 8}]}"}],"expected":{"error":"allow[0].endPort requires port"}}, + {"name":"allow: legacy port zero","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 0}]}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"allow: legacy port bool","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": true}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"allow: legacy port string","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": \"80\"}]}"}],"expected":{"error":"allow[0].port must be an integer"}}, + {"name":"allow: legacy endPort zero","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 0}]}"}],"expected":{"error":"allow[0].endPort must be between 1 and 65535"}}, + {"name":"allow: legacy endPort bool","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": false}]}"}],"expected":{"error":"allow[0].endPort must be an integer"}}, + {"name":"allow: legacy endPort below","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 10, \"endPort\": 5}]}"}],"expected":{"error":"allow[0].endPort 5 cannot be below port 10"}}, + {"name":"allow: legacy any range","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 9}]}"}],"expected":{"allow":["192.0.2.0/24:tcp:1-9","192.0.2.0/24:udp:1-9"],"deny":[]}}, + {"name":"allow: legacy any","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"any\"}]}"}],"expected":{"allow":["192.0.2.0/24"],"deny":[]}}, + {"name":"allow: legacy icmp","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\"}]}"}],"expected":{"allow":["192.0.2.0/24:icmp"],"deny":[]}}, + {"name":"allow: mxc unknown field","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"unknown\": 1}]}"}],"expected":{"error":"allow[0] has unknown field 'unknown'"}}, + {"name":"allow: mxc unknown only","input":[{"text":"{\"allow\": [{\"unknown\": 1}]}"}],"expected":{"error":"allow[0] has unknown field 'unknown'"}}, + {"name":"allow: mxc to object","input":[{"text":"{\"allow\": [{\"to\": {\"cidr\": \"192.0.2.0/24\"}}]}"}],"expected":{"error":"allow[0].to must be an array"}}, + {"name":"allow: mxc to null","input":[{"text":"{\"allow\": [{\"to\": null}]}"}],"expected":{"error":"allow[0].to must be an array"}}, + {"name":"allow: mxc to empty","input":[{"text":"{\"allow\": [{\"to\": []}]}"}],"expected":{"error":"allow[0].to must contain at least one destination"}}, + {"name":"allow: mxc peer string","input":[{"text":"{\"allow\": [{\"to\": [\"192.0.2.0/24\"]}]}"}],"expected":{"error":"allow[0].to[0] must be an object"}}, + {"name":"allow: mxc peer unknown","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"unknown\": 1}]}]}"}],"expected":{"error":"allow[0].to[0] has unknown field 'unknown'"}}, + {"name":"allow: mxc peer empty","input":[{"text":"{\"allow\": [{\"to\": [{}]}]}"}],"expected":{"error":"allow[0].to[0].cidr is required"}}, + {"name":"allow: mxc peer except only","input":[{"text":"{\"allow\": [{\"to\": [{\"except\": []}]}]}"}],"expected":{"error":"allow[0].to[0].cidr is required"}}, + {"name":"allow: mxc peer cidr number","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": 7}]}]}"}],"expected":{"error":"allow[0].to[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"allow: mxc peer second invalid","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}, {\"cidr\": \"x\"}]}]}"}],"expected":{"error":"allow[0].to[1].cidr is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"allow: mxc peer except null","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": null}]}]}"}],"expected":{"error":"allow[0].to[0].except must be an array"}}, + {"name":"allow: mxc peer except outside","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"10.0.0.0/8\"]}]}]}"}],"expected":{"error":"allow[0].to[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"allow: mxc ports object","input":[{"text":"{\"allow\": [{\"ports\": {\"port\": 80}}]}"}],"expected":{"error":"allow[0].ports must be an array"}}, + {"name":"allow: mxc ports empty","input":[{"text":"{\"allow\": [{\"ports\": []}]}"}],"expected":{"error":"allow[0].ports must contain at least one selector"}}, + {"name":"allow: mxc port string","input":[{"text":"{\"allow\": [{\"ports\": [\"tcp\"]}]}"}],"expected":{"error":"allow[0].ports[0] must be an object"}}, + {"name":"allow: mxc port unknown","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"tcp\", \"unknown\": 1}]}]}"}],"expected":{"error":"allow[0].ports[0] has unknown field 'unknown'"}}, + {"name":"allow: mxc port empty","input":[{"text":"{\"allow\": [{\"ports\": [{}]}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":[]}}, + {"name":"allow: mxc port protocol upper","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"Udp\"}]}]}"}],"expected":{"error":"allow[0].ports[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"allow: mxc port protocol null","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": null}]}]}"}],"expected":{"error":"allow[0].ports[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"allow: mxc port endPort only","input":[{"text":"{\"allow\": [{\"ports\": [{\"endPort\": 80}]}]}"}],"expected":{"error":"allow[0].ports[0].endPort requires port"}}, + {"name":"allow: mxc port icmp endPort","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"icmp\", \"endPort\": 80}]}]}"}],"expected":{"error":"allow[0].ports[0].endPort requires port"}}, + {"name":"allow: mxc port icmp port","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"icmp\", \"port\": 8}]}]}"}],"expected":{"error":"allow[0].ports[0].port is not supported with icmp"}}, + {"name":"allow: mxc port null","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": null}]}]}"}],"expected":{"error":"allow[0].ports[0].port must be an integer"}}, + {"name":"allow: mxc port float","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": 80.0}]}]}"}],"expected":{"error":"allow[0].ports[0].port must be an integer"}}, + {"name":"allow: mxc port large","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": 70000}]}]}"}],"expected":{"error":"allow[0].ports[0].port must be between 1 and 65535"}}, + {"name":"allow: mxc endPort large","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": 80, \"endPort\": 70000}]}]}"}],"expected":{"error":"allow[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"allow: mxc endPort below","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"udp\", \"port\": 9, \"endPort\": 8}]}]}"}],"expected":{"error":"allow[0].ports[0].endPort 8 cannot be below port 9"}}, + {"name":"allow: mxc second port invalid","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": 1}, {\"port\": 0}]}]}"}],"expected":{"error":"allow[0].ports[1].port must be between 1 and 65535"}}, + {"name":"allow: mxc to before ports","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"x\"}], \"ports\": [{\"port\": 0}]}]}"}],"expected":{"error":"allow[0].to[0].cidr is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"allow: mxc every destination","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"udp\", \"port\": 53}]}]}"}],"expected":{"allow":["0.0.0.0/0:udp:53","::/0:udp:53"],"deny":[]}}, + {"name":"allow: mxc every port","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}]}]}"}],"expected":{"allow":["192.0.2.0/24"],"deny":[]}}, + {"name":"allow: second rule invalid","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"TCP\"}]}"}],"expected":{"error":"allow[1].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: rule string","input":[{"text":"{\"deny\": [\"192.0.2.0/24\"]}"}],"expected":{"error":"deny[0] must be an object"}}, + {"name":"deny: rule number","input":[{"text":"{\"deny\": [7]}"}],"expected":{"error":"deny[0] must be an object"}}, + {"name":"deny: rule null","input":[{"text":"{\"deny\": [null]}"}],"expected":{"error":"deny[0] must be an object"}}, + {"name":"deny: rule array","input":[{"text":"{\"deny\": [[{\"cidr\": \"192.0.2.0/24\"}]]}"}],"expected":{"error":"deny[0] must be an object"}}, + {"name":"deny: empty rule","input":[{"text":"{\"deny\": [{}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"deny: mixed forms","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"cidr\": \"192.0.2.0/24\"}]}"}],"expected":{"error":"deny[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"deny: mixed ports","input":[{"text":"{\"deny\": [{\"ports\": [], \"port\": 80}]}"}],"expected":{"error":"deny[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"deny: legacy unknown field","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"unknown\": 1}]}"}],"expected":{"error":"deny[0] has unknown field 'unknown'"}}, + {"name":"deny: legacy unknown before missing cidr","input":[{"text":"{\"deny\": [{\"port\": 80, \"unknown\": 1}]}"}],"expected":{"error":"deny[0] has unknown field 'unknown'"}}, + {"name":"deny: legacy missing cidr","input":[{"text":"{\"deny\": [{\"protocol\": \"tcp\", \"port\": 80}]}"}],"expected":{"error":"deny[0].cidr is required"}}, + {"name":"deny: legacy except only","input":[{"text":"{\"deny\": [{\"except\": []}]}"}],"expected":{"error":"deny[0].cidr is required"}}, + {"name":"deny: legacy cidr number","input":[{"text":"{\"deny\": [{\"cidr\": 7}]}"}],"expected":{"error":"deny[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"deny: legacy cidr null","input":[{"text":"{\"deny\": [{\"cidr\": null}]}"}],"expected":{"error":"deny[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"deny: legacy cidr invalid","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/33\"}]}"}],"expected":{"error":"deny[0].cidr is not a valid IPv4 or IPv6 CIDR: 192.0.2.0/33"}}, + {"name":"deny: legacy cidr scoped","input":[{"text":"{\"deny\": [{\"cidr\": \"fe80::1%eth0/64\"}]}"}],"expected":{"error":"deny[0].cidr must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"deny: legacy except string","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": \"192.0.2.1\"}]}"}],"expected":{"error":"deny[0].except must be an array"}}, + {"name":"deny: legacy except null","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": null}]}"}],"expected":{"error":"deny[0].except must be an array"}}, + {"name":"deny: legacy except number","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [7]}]}"}],"expected":{"error":"deny[0].except[0] must be an IPv4 or IPv6 CIDR string"}}, + {"name":"deny: legacy except outside","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"198.51.100.0/24\"]}]}"}],"expected":{"error":"deny[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"deny: legacy except family","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"2001:db8::/32\"]}]}"}],"expected":{"error":"deny[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"deny: legacy except wider","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.0.0/16\"]}]}"}],"expected":{"error":"deny[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"deny: legacy except whole","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.0/24\"]}]}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"deny: legacy except invalid","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.0/24\", \"x\"]}]}"}],"expected":{"error":"deny[0].except[1] is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"deny: legacy endPort without port","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"endPort\": 80}]}"}],"expected":{"error":"deny[0].endPort requires port"}}, + {"name":"deny: legacy endPort with protocol","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"endPort\": 80}]}"}],"expected":{"error":"deny[0].endPort requires port"}}, + {"name":"deny: legacy port without protocol","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"port\": 80}]}"}],"expected":{"error":"deny[0].port requires protocol"}}, + {"name":"deny: legacy protocol upper","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"TCP\"}]}"}],"expected":{"error":"deny[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: legacy protocol unknown","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"sctp\"}]}"}],"expected":{"error":"deny[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: legacy protocol empty","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"\"}]}"}],"expected":{"error":"deny[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: legacy protocol null","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": null}]}"}],"expected":{"error":"deny[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: legacy protocol number","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": 6}]}"}],"expected":{"error":"deny[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: legacy icmp port","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\", \"port\": 8}]}"}],"expected":{"error":"deny[0].port is not supported with icmp"}}, + {"name":"deny: legacy icmp endPort","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\", \"endPort\": 8}]}"}],"expected":{"error":"deny[0].endPort requires port"}}, + {"name":"deny: legacy port zero","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 0}]}"}],"expected":{"error":"deny[0].port must be between 1 and 65535"}}, + {"name":"deny: legacy port bool","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": true}]}"}],"expected":{"error":"deny[0].port must be an integer"}}, + {"name":"deny: legacy port string","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": \"80\"}]}"}],"expected":{"error":"deny[0].port must be an integer"}}, + {"name":"deny: legacy endPort zero","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 0}]}"}],"expected":{"error":"deny[0].endPort must be between 1 and 65535"}}, + {"name":"deny: legacy endPort bool","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": false}]}"}],"expected":{"error":"deny[0].endPort must be an integer"}}, + {"name":"deny: legacy endPort below","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 10, \"endPort\": 5}]}"}],"expected":{"error":"deny[0].endPort 5 cannot be below port 10"}}, + {"name":"deny: legacy any range","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 9}]}"}],"expected":{"allow":[],"deny":["192.0.2.0/24:tcp:1-9","192.0.2.0/24:udp:1-9"]}}, + {"name":"deny: legacy any","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"any\"}]}"}],"expected":{"allow":[],"deny":["192.0.2.0/24"]}}, + {"name":"deny: legacy icmp","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\"}]}"}],"expected":{"allow":[],"deny":["192.0.2.0/24:icmp"]}}, + {"name":"deny: mxc unknown field","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}], \"unknown\": 1}]}"}],"expected":{"error":"deny[0] has unknown field 'unknown'"}}, + {"name":"deny: mxc unknown only","input":[{"text":"{\"deny\": [{\"unknown\": 1}]}"}],"expected":{"error":"deny[0] has unknown field 'unknown'"}}, + {"name":"deny: mxc to object","input":[{"text":"{\"deny\": [{\"to\": {\"cidr\": \"192.0.2.0/24\"}}]}"}],"expected":{"error":"deny[0].to must be an array"}}, + {"name":"deny: mxc to null","input":[{"text":"{\"deny\": [{\"to\": null}]}"}],"expected":{"error":"deny[0].to must be an array"}}, + {"name":"deny: mxc to empty","input":[{"text":"{\"deny\": [{\"to\": []}]}"}],"expected":{"error":"deny[0].to must contain at least one destination"}}, + {"name":"deny: mxc peer string","input":[{"text":"{\"deny\": [{\"to\": [\"192.0.2.0/24\"]}]}"}],"expected":{"error":"deny[0].to[0] must be an object"}}, + {"name":"deny: mxc peer unknown","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"unknown\": 1}]}]}"}],"expected":{"error":"deny[0].to[0] has unknown field 'unknown'"}}, + {"name":"deny: mxc peer empty","input":[{"text":"{\"deny\": [{\"to\": [{}]}]}"}],"expected":{"error":"deny[0].to[0].cidr is required"}}, + {"name":"deny: mxc peer except only","input":[{"text":"{\"deny\": [{\"to\": [{\"except\": []}]}]}"}],"expected":{"error":"deny[0].to[0].cidr is required"}}, + {"name":"deny: mxc peer cidr number","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": 7}]}]}"}],"expected":{"error":"deny[0].to[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"deny: mxc peer second invalid","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}, {\"cidr\": \"x\"}]}]}"}],"expected":{"error":"deny[0].to[1].cidr is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"deny: mxc peer except null","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": null}]}]}"}],"expected":{"error":"deny[0].to[0].except must be an array"}}, + {"name":"deny: mxc peer except outside","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"10.0.0.0/8\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"deny: mxc ports object","input":[{"text":"{\"deny\": [{\"ports\": {\"port\": 80}}]}"}],"expected":{"error":"deny[0].ports must be an array"}}, + {"name":"deny: mxc ports empty","input":[{"text":"{\"deny\": [{\"ports\": []}]}"}],"expected":{"error":"deny[0].ports must contain at least one selector"}}, + {"name":"deny: mxc port string","input":[{"text":"{\"deny\": [{\"ports\": [\"tcp\"]}]}"}],"expected":{"error":"deny[0].ports[0] must be an object"}}, + {"name":"deny: mxc port unknown","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"tcp\", \"unknown\": 1}]}]}"}],"expected":{"error":"deny[0].ports[0] has unknown field 'unknown'"}}, + {"name":"deny: mxc port empty","input":[{"text":"{\"deny\": [{\"ports\": [{}]}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"deny: mxc port protocol upper","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"Udp\"}]}]}"}],"expected":{"error":"deny[0].ports[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: mxc port protocol null","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": null}]}]}"}],"expected":{"error":"deny[0].ports[0].protocol must be tcp, udp, icmp, or any"}}, + {"name":"deny: mxc port endPort only","input":[{"text":"{\"deny\": [{\"ports\": [{\"endPort\": 80}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort requires port"}}, + {"name":"deny: mxc port icmp endPort","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"icmp\", \"endPort\": 80}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort requires port"}}, + {"name":"deny: mxc port icmp port","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"icmp\", \"port\": 8}]}]}"}],"expected":{"error":"deny[0].ports[0].port is not supported with icmp"}}, + {"name":"deny: mxc port null","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": null}]}]}"}],"expected":{"error":"deny[0].ports[0].port must be an integer"}}, + {"name":"deny: mxc port float","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 80.0}]}]}"}],"expected":{"error":"deny[0].ports[0].port must be an integer"}}, + {"name":"deny: mxc port large","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 70000}]}]}"}],"expected":{"error":"deny[0].ports[0].port must be between 1 and 65535"}}, + {"name":"deny: mxc endPort large","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 80, \"endPort\": 70000}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort must be between 1 and 65535"}}, + {"name":"deny: mxc endPort below","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"udp\", \"port\": 9, \"endPort\": 8}]}]}"}],"expected":{"error":"deny[0].ports[0].endPort 8 cannot be below port 9"}}, + {"name":"deny: mxc second port invalid","input":[{"text":"{\"deny\": [{\"ports\": [{\"port\": 1}, {\"port\": 0}]}]}"}],"expected":{"error":"deny[0].ports[1].port must be between 1 and 65535"}}, + {"name":"deny: mxc to before ports","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"x\"}], \"ports\": [{\"port\": 0}]}]}"}],"expected":{"error":"deny[0].to[0].cidr is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"deny: mxc every destination","input":[{"text":"{\"deny\": [{\"ports\": [{\"protocol\": \"udp\", \"port\": 53}]}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0:udp:53","::/0:udp:53"]}}, + {"name":"deny: mxc every port","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.0/24\"}]}]}"}],"expected":{"allow":[],"deny":["192.0.2.0/24"]}}, + {"name":"deny: second rule invalid","input":[{"text":"{\"deny\": [{\"cidr\": \"192.0.2.0/24\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"TCP\"}]}"}],"expected":{"error":"deny[1].protocol must be tcp, udp, icmp, or any"}}, + {"name":"policy: allow object","input":[{"text":"{\"allow\": {}}"}],"expected":{"error":"allow must be an array"}}, + {"name":"policy: allow null","input":[{"text":"{\"allow\": null}"}],"expected":{"error":"allow must be an array"}}, + {"name":"policy: deny string","input":[{"text":"{\"deny\": \"192.0.2.0/24\"}"}],"expected":{"error":"deny must be an array"}}, + {"name":"policy: deny number","input":[{"text":"{\"deny\": 1}"}],"expected":{"error":"deny must be an array"}}, + {"name":"policy: unknown root field","input":[{"text":"{\"allow\": [], \"denied\": []}"}],"expected":{"error":"egress policy has unknown field 'denied'"}}, + {"name":"policy: allow before deny errors","input":[{"text":"{\"allow\": [{\"cidr\": 7}], \"deny\": [{\"cidr\": 8}]}"}],"expected":{"error":"allow[0].cidr must be an IPv4 or IPv6 CIDR string"}}, + {"name":"policy: later rule error before lowering","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1\"}, {\"cidr\": \"10.0.1.1\"}, {\"cidr\": \"10.0.2.1\"}, {\"cidr\": \"10.0.3.1\"}, {\"cidr\": \"10.0.4.1\"}, {\"cidr\": \"10.0.5.1\"}, {\"cidr\": \"10.0.6.1\"}, {\"cidr\": \"10.0.7.1\"}, {\"cidr\": \"10.0.8.1\"}, {\"cidr\": \"10.0.9.1\"}, {\"cidr\": \"10.0.10.1\"}, {\"cidr\": \"10.0.11.1\"}, {\"cidr\": \"10.0.12.1\"}, {\"cidr\": \"10.0.13.1\"}, {\"cidr\": \"10.0.14.1\"}, {\"cidr\": \"10.0.15.1\"}, {\"cidr\": \"10.0.16.1\"}, {\"cidr\": \"10.0.17.1\"}, {\"cidr\": \"10.0.18.1\"}, {\"cidr\": \"10.0.19.1\"}, {\"cidr\": \"10.0.20.1\"}, {\"cidr\": \"10.0.21.1\"}, {\"cidr\": \"10.0.22.1\"}, {\"cidr\": \"10.0.23.1\"}, {\"cidr\": \"10.0.24.1\"}, {\"cidr\": \"10.0.25.1\"}, {\"cidr\": \"10.0.26.1\"}, {\"cidr\": \"10.0.27.1\"}, {\"cidr\": \"10.0.28.1\"}, {\"cidr\": \"10.0.29.1\"}, {\"cidr\": \"10.0.30.1\"}, {\"cidr\": \"10.0.31.1\"}, {\"cidr\": \"10.0.32.1\"}, {\"cidr\": \"10.0.33.1\"}, {\"cidr\": \"10.0.34.1\"}, {\"cidr\": \"10.0.35.1\"}, {\"cidr\": \"10.0.36.1\"}, {\"cidr\": \"10.0.37.1\"}, {\"cidr\": \"10.0.38.1\"}, {\"cidr\": \"10.0.39.1\"}, {\"cidr\": \"10.0.40.1\"}, {\"cidr\": \"10.0.41.1\"}, {\"cidr\": \"10.0.42.1\"}, {\"cidr\": \"10.0.43.1\"}, {\"cidr\": \"10.0.44.1\"}, {\"cidr\": \"10.0.45.1\"}, {\"cidr\": \"10.0.46.1\"}, {\"cidr\": \"10.0.47.1\"}, {\"cidr\": \"10.0.48.1\"}, {\"cidr\": \"10.0.49.1\"}, {\"cidr\": \"10.0.50.1\"}, {\"cidr\": \"10.0.51.1\"}, {\"cidr\": \"10.0.52.1\"}, {\"cidr\": \"10.0.53.1\"}, {\"cidr\": \"10.0.54.1\"}, {\"cidr\": \"10.0.55.1\"}, {\"cidr\": \"10.0.56.1\"}, {\"cidr\": \"10.0.57.1\"}, {\"cidr\": \"10.0.58.1\"}, {\"cidr\": \"10.0.59.1\"}, {\"cidr\": \"10.0.60.1\"}, {\"cidr\": \"10.0.61.1\"}, {\"cidr\": \"10.0.62.1\"}, {\"cidr\": \"10.0.63.1\"}, {\"cidr\": \"10.0.64.1\"}, {\"cidr\": \"10.0.65.1\"}, {\"cidr\": \"10.0.66.1\"}, {\"cidr\": \"10.0.67.1\"}, {\"cidr\": \"10.0.68.1\"}, {\"cidr\": \"10.0.69.1\"}, {\"cidr\": \"10.0.70.1\"}, {\"cidr\": \"10.0.71.1\"}, {\"cidr\": \"10.0.72.1\"}, {\"cidr\": \"10.0.73.1\"}, {\"cidr\": \"10.0.74.1\"}, {\"cidr\": \"10.0.75.1\"}, {\"cidr\": \"10.0.76.1\"}, {\"cidr\": \"10.0.77.1\"}, {\"cidr\": \"10.0.78.1\"}, {\"cidr\": \"10.0.79.1\"}, {\"cidr\": \"10.0.80.1\"}, {\"cidr\": \"10.0.81.1\"}, {\"cidr\": \"10.0.82.1\"}, {\"cidr\": \"10.0.83.1\"}, {\"cidr\": \"10.0.84.1\"}, {\"cidr\": \"10.0.85.1\"}, {\"cidr\": \"10.0.86.1\"}, {\"cidr\": \"10.0.87.1\"}, {\"cidr\": \"10.0.88.1\"}, {\"cidr\": \"10.0.89.1\"}, {\"cidr\": \"10.0.90.1\"}, {\"cidr\": \"10.0.91.1\"}, {\"cidr\": \"10.0.92.1\"}, {\"cidr\": \"10.0.93.1\"}, {\"cidr\": \"10.0.94.1\"}, {\"cidr\": \"10.0.95.1\"}, {\"cidr\": \"10.0.96.1\"}, {\"cidr\": \"10.0.97.1\"}, {\"cidr\": \"10.0.98.1\"}, {\"cidr\": \"10.0.99.1\"}, {\"cidr\": \"10.0.100.1\"}, {\"cidr\": \"10.0.101.1\"}, {\"cidr\": \"10.0.102.1\"}, {\"cidr\": \"10.0.103.1\"}, {\"cidr\": \"10.0.104.1\"}, {\"cidr\": \"10.0.105.1\"}, {\"cidr\": \"10.0.106.1\"}, {\"cidr\": \"10.0.107.1\"}, {\"cidr\": \"10.0.108.1\"}, {\"cidr\": \"10.0.109.1\"}, {\"cidr\": \"10.0.110.1\"}, {\"cidr\": \"10.0.111.1\"}, {\"cidr\": \"10.0.112.1\"}, {\"cidr\": \"10.0.113.1\"}, {\"cidr\": \"10.0.114.1\"}, {\"cidr\": \"10.0.115.1\"}, {\"cidr\": \"10.0.116.1\"}, {\"cidr\": \"10.0.117.1\"}, {\"cidr\": \"10.0.118.1\"}, {\"cidr\": \"10.0.119.1\"}, {\"cidr\": \"10.0.120.1\"}, {\"cidr\": \"10.0.121.1\"}, {\"cidr\": \"10.0.122.1\"}, {\"cidr\": \"10.0.123.1\"}, {\"cidr\": \"10.0.124.1\"}, {\"cidr\": \"10.0.125.1\"}, {\"cidr\": \"10.0.126.1\"}, {\"cidr\": \"10.0.127.1\"}, {\"cidr\": \"10.0.128.1\"}, {\"cidr\": \"10.0.129.1\"}, {\"cidr\": \"10.0.130.1\"}, {\"cidr\": \"10.0.131.1\"}, {\"cidr\": \"10.0.132.1\"}, {\"cidr\": \"10.0.133.1\"}, {\"cidr\": \"10.0.134.1\"}, {\"cidr\": \"10.0.135.1\"}, {\"cidr\": \"10.0.136.1\"}, {\"cidr\": \"10.0.137.1\"}, {\"cidr\": \"10.0.138.1\"}, {\"cidr\": \"10.0.139.1\"}, {\"cidr\": \"10.0.140.1\"}, {\"cidr\": \"10.0.141.1\"}, {\"cidr\": \"10.0.142.1\"}, {\"cidr\": \"10.0.143.1\"}, {\"cidr\": \"10.0.144.1\"}, {\"cidr\": \"10.0.145.1\"}, {\"cidr\": \"10.0.146.1\"}, {\"cidr\": \"10.0.147.1\"}, {\"cidr\": \"10.0.148.1\"}, {\"cidr\": \"10.0.149.1\"}, {\"cidr\": \"10.0.150.1\"}, {\"cidr\": \"10.0.151.1\"}, {\"cidr\": \"10.0.152.1\"}, {\"cidr\": \"10.0.153.1\"}, {\"cidr\": \"10.0.154.1\"}, {\"cidr\": \"10.0.155.1\"}, {\"cidr\": \"10.0.156.1\"}, {\"cidr\": \"10.0.157.1\"}, {\"cidr\": \"10.0.158.1\"}, {\"cidr\": \"10.0.159.1\"}, {\"cidr\": \"10.0.160.1\"}, {\"cidr\": \"10.0.161.1\"}, {\"cidr\": \"10.0.162.1\"}, {\"cidr\": \"10.0.163.1\"}, {\"cidr\": \"10.0.164.1\"}, {\"cidr\": \"10.0.165.1\"}, {\"cidr\": \"10.0.166.1\"}, {\"cidr\": \"10.0.167.1\"}, {\"cidr\": \"10.0.168.1\"}, {\"cidr\": \"10.0.169.1\"}, {\"cidr\": \"10.0.170.1\"}, {\"cidr\": \"10.0.171.1\"}, {\"cidr\": \"10.0.172.1\"}, {\"cidr\": \"10.0.173.1\"}, {\"cidr\": \"10.0.174.1\"}, {\"cidr\": \"10.0.175.1\"}, {\"cidr\": \"10.0.176.1\"}, {\"cidr\": \"10.0.177.1\"}, {\"cidr\": \"10.0.178.1\"}, {\"cidr\": \"10.0.179.1\"}, {\"cidr\": \"10.0.180.1\"}, {\"cidr\": \"10.0.181.1\"}, {\"cidr\": \"10.0.182.1\"}, {\"cidr\": \"10.0.183.1\"}, {\"cidr\": \"10.0.184.1\"}, {\"cidr\": \"10.0.185.1\"}, {\"cidr\": \"10.0.186.1\"}, {\"cidr\": \"10.0.187.1\"}, {\"cidr\": \"10.0.188.1\"}, {\"cidr\": \"10.0.189.1\"}, {\"cidr\": \"10.0.190.1\"}, {\"cidr\": \"10.0.191.1\"}, {\"cidr\": \"10.0.192.1\"}, {\"cidr\": \"10.0.193.1\"}, {\"cidr\": \"10.0.194.1\"}, {\"cidr\": \"10.0.195.1\"}, {\"cidr\": \"10.0.196.1\"}, {\"cidr\": \"10.0.197.1\"}, {\"cidr\": \"10.0.198.1\"}, {\"cidr\": \"10.0.199.1\"}, {\"cidr\": \"10.0.200.1\"}, {\"cidr\": \"10.0.201.1\"}, {\"cidr\": \"10.0.202.1\"}, {\"cidr\": \"10.0.203.1\"}, {\"cidr\": \"10.0.204.1\"}, {\"cidr\": \"10.0.205.1\"}, {\"cidr\": \"10.0.206.1\"}, {\"cidr\": \"10.0.207.1\"}, {\"cidr\": \"10.0.208.1\"}, {\"cidr\": \"10.0.209.1\"}, {\"cidr\": \"10.0.210.1\"}, {\"cidr\": \"10.0.211.1\"}, {\"cidr\": \"10.0.212.1\"}, {\"cidr\": \"10.0.213.1\"}, {\"cidr\": \"10.0.214.1\"}, {\"cidr\": \"10.0.215.1\"}, {\"cidr\": \"10.0.216.1\"}, {\"cidr\": \"10.0.217.1\"}, {\"cidr\": \"10.0.218.1\"}, {\"cidr\": \"10.0.219.1\"}, {\"cidr\": \"10.0.220.1\"}, {\"cidr\": \"10.0.221.1\"}, {\"cidr\": \"10.0.222.1\"}, {\"cidr\": \"10.0.223.1\"}, {\"cidr\": \"10.0.224.1\"}, {\"cidr\": \"10.0.225.1\"}, {\"cidr\": \"10.0.226.1\"}, {\"cidr\": \"10.0.227.1\"}, {\"cidr\": \"10.0.228.1\"}, {\"cidr\": \"10.0.229.1\"}, {\"cidr\": \"10.0.230.1\"}, {\"cidr\": \"10.0.231.1\"}, {\"cidr\": \"10.0.232.1\"}, {\"cidr\": \"10.0.233.1\"}, {\"cidr\": \"10.0.234.1\"}, {\"cidr\": \"10.0.235.1\"}, {\"cidr\": \"10.0.236.1\"}, {\"cidr\": \"10.0.237.1\"}, {\"cidr\": \"10.0.238.1\"}, {\"cidr\": \"10.0.239.1\"}, {\"cidr\": \"10.0.240.1\"}, {\"cidr\": \"10.0.241.1\"}, {\"cidr\": \"10.0.242.1\"}, {\"cidr\": \"10.0.243.1\"}, {\"cidr\": \"10.0.244.1\"}, {\"cidr\": \"10.0.245.1\"}, {\"cidr\": \"10.0.246.1\"}, {\"cidr\": \"10.0.247.1\"}, {\"cidr\": \"10.0.248.1\"}, {\"cidr\": \"10.0.249.1\"}, {\"cidr\": \"10.0.250.1\"}, {\"cidr\": \"10.0.251.1\"}, {\"cidr\": \"10.0.252.1\"}, {\"cidr\": \"10.0.253.1\"}, {\"cidr\": \"10.0.254.1\"}, {\"cidr\": \"10.0.255.1\"}, {\"cidr\": \"10.0.256.1\"}, {\"cidr\": \"10.0.257.1\"}, {\"cidr\": \"10.0.258.1\"}, {\"cidr\": \"10.0.259.1\"}, {\"cidr\": \"10.0.260.1\"}, {\"cidr\": \"10.0.261.1\"}, {\"cidr\": \"10.0.262.1\"}, {\"cidr\": \"10.0.263.1\"}, {\"cidr\": \"10.0.264.1\"}, {\"cidr\": \"10.0.265.1\"}, {\"cidr\": \"10.0.266.1\"}, {\"cidr\": \"10.0.267.1\"}, {\"cidr\": \"10.0.268.1\"}, {\"cidr\": \"10.0.269.1\"}, {\"cidr\": \"10.0.270.1\"}, {\"cidr\": \"10.0.271.1\"}, {\"cidr\": \"10.0.272.1\"}, {\"cidr\": \"10.0.273.1\"}, {\"cidr\": \"10.0.274.1\"}, {\"cidr\": \"10.0.275.1\"}, {\"cidr\": \"10.0.276.1\"}, {\"cidr\": \"10.0.277.1\"}, {\"cidr\": \"10.0.278.1\"}, {\"cidr\": \"10.0.279.1\"}, {\"cidr\": \"10.0.280.1\"}, {\"cidr\": \"10.0.281.1\"}, {\"cidr\": \"10.0.282.1\"}, {\"cidr\": \"10.0.283.1\"}, {\"cidr\": \"10.0.284.1\"}, {\"cidr\": \"10.0.285.1\"}, {\"cidr\": \"10.0.286.1\"}, {\"cidr\": \"10.0.287.1\"}, {\"cidr\": \"10.0.288.1\"}, {\"cidr\": \"10.0.289.1\"}, {\"cidr\": \"10.0.290.1\"}, {\"cidr\": \"10.0.291.1\"}, {\"cidr\": \"10.0.292.1\"}, {\"cidr\": \"10.0.293.1\"}, {\"cidr\": \"10.0.294.1\"}, {\"cidr\": \"10.0.295.1\"}, {\"cidr\": \"10.0.296.1\"}, {\"cidr\": \"10.0.297.1\"}, {\"cidr\": \"10.0.298.1\"}, {\"cidr\": \"10.0.299.1\"}, {\"cidr\": 7}]}"}],"expected":{"error":"allow[256].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.256.1"}}, + {"name":"policy: allow limit before deny errors","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1\"}, {\"cidr\": \"10.0.1.1\"}, {\"cidr\": \"10.0.2.1\"}, {\"cidr\": \"10.0.3.1\"}, {\"cidr\": \"10.0.4.1\"}, {\"cidr\": \"10.0.5.1\"}, {\"cidr\": \"10.0.6.1\"}, {\"cidr\": \"10.0.7.1\"}, {\"cidr\": \"10.0.8.1\"}, {\"cidr\": \"10.0.9.1\"}, {\"cidr\": \"10.0.10.1\"}, {\"cidr\": \"10.0.11.1\"}, {\"cidr\": \"10.0.12.1\"}, {\"cidr\": \"10.0.13.1\"}, {\"cidr\": \"10.0.14.1\"}, {\"cidr\": \"10.0.15.1\"}, {\"cidr\": \"10.0.16.1\"}, {\"cidr\": \"10.0.17.1\"}, {\"cidr\": \"10.0.18.1\"}, {\"cidr\": \"10.0.19.1\"}, {\"cidr\": \"10.0.20.1\"}, {\"cidr\": \"10.0.21.1\"}, {\"cidr\": \"10.0.22.1\"}, {\"cidr\": \"10.0.23.1\"}, {\"cidr\": \"10.0.24.1\"}, {\"cidr\": \"10.0.25.1\"}, {\"cidr\": \"10.0.26.1\"}, {\"cidr\": \"10.0.27.1\"}, {\"cidr\": \"10.0.28.1\"}, {\"cidr\": \"10.0.29.1\"}, {\"cidr\": \"10.0.30.1\"}, {\"cidr\": \"10.0.31.1\"}, {\"cidr\": \"10.0.32.1\"}, {\"cidr\": \"10.0.33.1\"}, {\"cidr\": \"10.0.34.1\"}, {\"cidr\": \"10.0.35.1\"}, {\"cidr\": \"10.0.36.1\"}, {\"cidr\": \"10.0.37.1\"}, {\"cidr\": \"10.0.38.1\"}, {\"cidr\": \"10.0.39.1\"}, {\"cidr\": \"10.0.40.1\"}, {\"cidr\": \"10.0.41.1\"}, {\"cidr\": \"10.0.42.1\"}, {\"cidr\": \"10.0.43.1\"}, {\"cidr\": \"10.0.44.1\"}, {\"cidr\": \"10.0.45.1\"}, {\"cidr\": \"10.0.46.1\"}, {\"cidr\": \"10.0.47.1\"}, {\"cidr\": \"10.0.48.1\"}, {\"cidr\": \"10.0.49.1\"}, {\"cidr\": \"10.0.50.1\"}, {\"cidr\": \"10.0.51.1\"}, {\"cidr\": \"10.0.52.1\"}, {\"cidr\": \"10.0.53.1\"}, {\"cidr\": \"10.0.54.1\"}, {\"cidr\": \"10.0.55.1\"}, {\"cidr\": \"10.0.56.1\"}, {\"cidr\": \"10.0.57.1\"}, {\"cidr\": \"10.0.58.1\"}, {\"cidr\": \"10.0.59.1\"}, {\"cidr\": \"10.0.60.1\"}, {\"cidr\": \"10.0.61.1\"}, {\"cidr\": \"10.0.62.1\"}, {\"cidr\": \"10.0.63.1\"}, {\"cidr\": \"10.0.64.1\"}, {\"cidr\": \"10.0.65.1\"}, {\"cidr\": \"10.0.66.1\"}, {\"cidr\": \"10.0.67.1\"}, {\"cidr\": \"10.0.68.1\"}, {\"cidr\": \"10.0.69.1\"}, {\"cidr\": \"10.0.70.1\"}, {\"cidr\": \"10.0.71.1\"}, {\"cidr\": \"10.0.72.1\"}, {\"cidr\": \"10.0.73.1\"}, {\"cidr\": \"10.0.74.1\"}, {\"cidr\": \"10.0.75.1\"}, {\"cidr\": \"10.0.76.1\"}, {\"cidr\": \"10.0.77.1\"}, {\"cidr\": \"10.0.78.1\"}, {\"cidr\": \"10.0.79.1\"}, {\"cidr\": \"10.0.80.1\"}, {\"cidr\": \"10.0.81.1\"}, {\"cidr\": \"10.0.82.1\"}, {\"cidr\": \"10.0.83.1\"}, {\"cidr\": \"10.0.84.1\"}, {\"cidr\": \"10.0.85.1\"}, {\"cidr\": \"10.0.86.1\"}, {\"cidr\": \"10.0.87.1\"}, {\"cidr\": \"10.0.88.1\"}, {\"cidr\": \"10.0.89.1\"}, {\"cidr\": \"10.0.90.1\"}, {\"cidr\": \"10.0.91.1\"}, {\"cidr\": \"10.0.92.1\"}, {\"cidr\": \"10.0.93.1\"}, {\"cidr\": \"10.0.94.1\"}, {\"cidr\": \"10.0.95.1\"}, {\"cidr\": \"10.0.96.1\"}, {\"cidr\": \"10.0.97.1\"}, {\"cidr\": \"10.0.98.1\"}, {\"cidr\": \"10.0.99.1\"}, {\"cidr\": \"10.0.100.1\"}, {\"cidr\": \"10.0.101.1\"}, {\"cidr\": \"10.0.102.1\"}, {\"cidr\": \"10.0.103.1\"}, {\"cidr\": \"10.0.104.1\"}, {\"cidr\": \"10.0.105.1\"}, {\"cidr\": \"10.0.106.1\"}, {\"cidr\": \"10.0.107.1\"}, {\"cidr\": \"10.0.108.1\"}, {\"cidr\": \"10.0.109.1\"}, {\"cidr\": \"10.0.110.1\"}, {\"cidr\": \"10.0.111.1\"}, {\"cidr\": \"10.0.112.1\"}, {\"cidr\": \"10.0.113.1\"}, {\"cidr\": \"10.0.114.1\"}, {\"cidr\": \"10.0.115.1\"}, {\"cidr\": \"10.0.116.1\"}, {\"cidr\": \"10.0.117.1\"}, {\"cidr\": \"10.0.118.1\"}, {\"cidr\": \"10.0.119.1\"}, {\"cidr\": \"10.0.120.1\"}, {\"cidr\": \"10.0.121.1\"}, {\"cidr\": \"10.0.122.1\"}, {\"cidr\": \"10.0.123.1\"}, {\"cidr\": \"10.0.124.1\"}, {\"cidr\": \"10.0.125.1\"}, {\"cidr\": \"10.0.126.1\"}, {\"cidr\": \"10.0.127.1\"}, {\"cidr\": \"10.0.128.1\"}, {\"cidr\": \"10.0.129.1\"}, {\"cidr\": \"10.0.130.1\"}, {\"cidr\": \"10.0.131.1\"}, {\"cidr\": \"10.0.132.1\"}, {\"cidr\": \"10.0.133.1\"}, {\"cidr\": \"10.0.134.1\"}, {\"cidr\": \"10.0.135.1\"}, {\"cidr\": \"10.0.136.1\"}, {\"cidr\": \"10.0.137.1\"}, {\"cidr\": \"10.0.138.1\"}, {\"cidr\": \"10.0.139.1\"}, {\"cidr\": \"10.0.140.1\"}, {\"cidr\": \"10.0.141.1\"}, {\"cidr\": \"10.0.142.1\"}, {\"cidr\": \"10.0.143.1\"}, {\"cidr\": \"10.0.144.1\"}, {\"cidr\": \"10.0.145.1\"}, {\"cidr\": \"10.0.146.1\"}, {\"cidr\": \"10.0.147.1\"}, {\"cidr\": \"10.0.148.1\"}, {\"cidr\": \"10.0.149.1\"}, {\"cidr\": \"10.0.150.1\"}, {\"cidr\": \"10.0.151.1\"}, {\"cidr\": \"10.0.152.1\"}, {\"cidr\": \"10.0.153.1\"}, {\"cidr\": \"10.0.154.1\"}, {\"cidr\": \"10.0.155.1\"}, {\"cidr\": \"10.0.156.1\"}, {\"cidr\": \"10.0.157.1\"}, {\"cidr\": \"10.0.158.1\"}, {\"cidr\": \"10.0.159.1\"}, {\"cidr\": \"10.0.160.1\"}, {\"cidr\": \"10.0.161.1\"}, {\"cidr\": \"10.0.162.1\"}, {\"cidr\": \"10.0.163.1\"}, {\"cidr\": \"10.0.164.1\"}, {\"cidr\": \"10.0.165.1\"}, {\"cidr\": \"10.0.166.1\"}, {\"cidr\": \"10.0.167.1\"}, {\"cidr\": \"10.0.168.1\"}, {\"cidr\": \"10.0.169.1\"}, {\"cidr\": \"10.0.170.1\"}, {\"cidr\": \"10.0.171.1\"}, {\"cidr\": \"10.0.172.1\"}, {\"cidr\": \"10.0.173.1\"}, {\"cidr\": \"10.0.174.1\"}, {\"cidr\": \"10.0.175.1\"}, {\"cidr\": \"10.0.176.1\"}, {\"cidr\": \"10.0.177.1\"}, {\"cidr\": \"10.0.178.1\"}, {\"cidr\": \"10.0.179.1\"}, {\"cidr\": \"10.0.180.1\"}, {\"cidr\": \"10.0.181.1\"}, {\"cidr\": \"10.0.182.1\"}, {\"cidr\": \"10.0.183.1\"}, {\"cidr\": \"10.0.184.1\"}, {\"cidr\": \"10.0.185.1\"}, {\"cidr\": \"10.0.186.1\"}, {\"cidr\": \"10.0.187.1\"}, {\"cidr\": \"10.0.188.1\"}, {\"cidr\": \"10.0.189.1\"}, {\"cidr\": \"10.0.190.1\"}, {\"cidr\": \"10.0.191.1\"}, {\"cidr\": \"10.0.192.1\"}, {\"cidr\": \"10.0.193.1\"}, {\"cidr\": \"10.0.194.1\"}, {\"cidr\": \"10.0.195.1\"}, {\"cidr\": \"10.0.196.1\"}, {\"cidr\": \"10.0.197.1\"}, {\"cidr\": \"10.0.198.1\"}, {\"cidr\": \"10.0.199.1\"}, {\"cidr\": \"10.0.200.1\"}, {\"cidr\": \"10.0.201.1\"}, {\"cidr\": \"10.0.202.1\"}, {\"cidr\": \"10.0.203.1\"}, {\"cidr\": \"10.0.204.1\"}, {\"cidr\": \"10.0.205.1\"}, {\"cidr\": \"10.0.206.1\"}, {\"cidr\": \"10.0.207.1\"}, {\"cidr\": \"10.0.208.1\"}, {\"cidr\": \"10.0.209.1\"}, {\"cidr\": \"10.0.210.1\"}, {\"cidr\": \"10.0.211.1\"}, {\"cidr\": \"10.0.212.1\"}, {\"cidr\": \"10.0.213.1\"}, {\"cidr\": \"10.0.214.1\"}, {\"cidr\": \"10.0.215.1\"}, {\"cidr\": \"10.0.216.1\"}, {\"cidr\": \"10.0.217.1\"}, {\"cidr\": \"10.0.218.1\"}, {\"cidr\": \"10.0.219.1\"}, {\"cidr\": \"10.0.220.1\"}, {\"cidr\": \"10.0.221.1\"}, {\"cidr\": \"10.0.222.1\"}, {\"cidr\": \"10.0.223.1\"}, {\"cidr\": \"10.0.224.1\"}, {\"cidr\": \"10.0.225.1\"}, {\"cidr\": \"10.0.226.1\"}, {\"cidr\": \"10.0.227.1\"}, {\"cidr\": \"10.0.228.1\"}, {\"cidr\": \"10.0.229.1\"}, {\"cidr\": \"10.0.230.1\"}, {\"cidr\": \"10.0.231.1\"}, {\"cidr\": \"10.0.232.1\"}, {\"cidr\": \"10.0.233.1\"}, {\"cidr\": \"10.0.234.1\"}, {\"cidr\": \"10.0.235.1\"}, {\"cidr\": \"10.0.236.1\"}, {\"cidr\": \"10.0.237.1\"}, {\"cidr\": \"10.0.238.1\"}, {\"cidr\": \"10.0.239.1\"}, {\"cidr\": \"10.0.240.1\"}, {\"cidr\": \"10.0.241.1\"}, {\"cidr\": \"10.0.242.1\"}, {\"cidr\": \"10.0.243.1\"}, {\"cidr\": \"10.0.244.1\"}, {\"cidr\": \"10.0.245.1\"}, {\"cidr\": \"10.0.246.1\"}, {\"cidr\": \"10.0.247.1\"}, {\"cidr\": \"10.0.248.1\"}, {\"cidr\": \"10.0.249.1\"}, {\"cidr\": \"10.0.250.1\"}, {\"cidr\": \"10.0.251.1\"}, {\"cidr\": \"10.0.252.1\"}, {\"cidr\": \"10.0.253.1\"}, {\"cidr\": \"10.0.254.1\"}, {\"cidr\": \"10.0.255.1\"}, {\"cidr\": \"10.0.256.1\"}], \"deny\": [7]}"}],"expected":{"error":"allow[256].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.256.1"}}, + {"name":"cidr: 192.0.2.7","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7\"}]}"}],"expected":{"allow":["192.0.2.7/32"],"deny":[]}}, + {"name":"cidr: 192.0.2.7/32","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7/32\"}]}"}],"expected":{"allow":["192.0.2.7/32"],"deny":[]}}, + {"name":"cidr: 192.0.2.7/24","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.7/24\"}]}"}],"expected":{"allow":["192.0.2.0/24"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/255.255.255.0","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/255.255.255.0\"}]}"}],"expected":{"allow":["10.0.0.0/24"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/0.0.0.255","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/0.0.0.255\"}]}"}],"expected":{"allow":["10.0.0.0/24"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/255.255.255.255","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/255.255.255.255\"}]}"}],"expected":{"allow":["10.0.0.5/32"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/0.0.0.0","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/0.0.0.0\"}]}"}],"expected":{"allow":["0.0.0.0/0"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/0","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/0\"}]}"}],"expected":{"allow":["0.0.0.0/0"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/08","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/08\"}]}"}],"expected":{"allow":["10.0.0.0/8"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/0008","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/0008\"}]}"}],"expected":{"allow":["10.0.0.0/8"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/0000000000000000000000000000000","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/"},{"text":"0","repeat":4299},{"text":"8\"}]}"}],"expected":{"allow":["10.0.0.0/8"],"deny":[]}}, + {"name":"cidr: 10.0.0.5/0000000000000000000000000000000","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/"},{"text":"0","repeat":4300},{"text":"8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008"}}, + {"name":"cidr: 10.0.0.5/255.0.255.0","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/255.0.255.0\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/255.0.255.0"}}, + {"name":"cidr: 10.0.0.5/255.255.255.000","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/255.255.255.000\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/255.255.255.000"}}, + {"name":"cidr: 10.0.0.5/+8","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/+8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/+8"}}, + {"name":"cidr: 10.0.0.5/-8","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/-8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/-8"}}, + {"name":"cidr: 10.0.0.5/8.0","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/8.0\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/8.0"}}, + {"name":"cidr: 10.0.0.5/0x8","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/0x8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/0x8"}}, + {"name":"cidr: 10.0.0.5/","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/"}}, + {"name":"cidr: 10.0.0.5//8","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5//8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5//8"}}, + {"name":"cidr: 10.0.0.5/8/8","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/8/8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/8/8"}}, + {"name":"cidr: 10.0.0.5/33","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5/33\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5/33"}}, + {"name":"cidr: 10.0.0.5","input":[{"text":"{\"allow\": [{\"cidr\": \" 10.0.0.5\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5"}}, + {"name":"cidr: 10.0.0.5 ","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.5 \"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.5 "}}, + {"name":"cidr: 010.0.0.5","input":[{"text":"{\"allow\": [{\"cidr\": \"010.0.0.5\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 010.0.0.5"}}, + {"name":"cidr: 10.0.0.05","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.05\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.05"}}, + {"name":"cidr: 256.0.0.0","input":[{"text":"{\"allow\": [{\"cidr\": \"256.0.0.0\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 256.0.0.0"}}, + {"name":"cidr: 10.0.0","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0"}}, + {"name":"cidr: 10.0.0.0.5","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.0.5\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.0.5"}}, + {"name":"cidr: 10.0.0.\u0665","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.\\u0665\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 10.0.0.\u0665"}}, + {"name":"cidr: ","input":[{"text":"{\"allow\": [{\"cidr\": \"\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: "}}, + {"name":"cidr: /8","input":[{"text":"{\"allow\": [{\"cidr\": \"/8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: /8"}}, + {"name":"cidr: ::","input":[{"text":"{\"allow\": [{\"cidr\": \"::\"}]}"}],"expected":{"allow":["::/128"],"deny":[]}}, + {"name":"cidr: ::/0","input":[{"text":"{\"allow\": [{\"cidr\": \"::/0\"}]}"}],"expected":{"allow":["::/0"],"deny":[]}}, + {"name":"cidr: ::/000128","input":[{"text":"{\"allow\": [{\"cidr\": \"::/000128\"}]}"}],"expected":{"allow":["::/128"],"deny":[]}}, + {"name":"cidr: ::/129","input":[{"text":"{\"allow\": [{\"cidr\": \"::/129\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: ::/129"}}, + {"name":"cidr: 2001:DB8::/32","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:DB8::/32\"}]}"}],"expected":{"allow":["2001:db8::/32"],"deny":[]}}, + {"name":"cidr: 2001:0db8:0000:0000::/32","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:0db8:0000:0000::/32\"}]}"}],"expected":{"allow":["2001:db8::/32"],"deny":[]}}, + {"name":"cidr: 2001:db8::7/64","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::7/64\"}]}"}],"expected":{"allow":["2001:db8::/64"],"deny":[]}}, + {"name":"cidr: 2001:db8:0:0:1:0:0:1","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8:0:0:1:0:0:1\"}]}"}],"expected":{"allow":["2001:db8::1:0:0:1/128"],"deny":[]}}, + {"name":"cidr: 1:0:0:2:0:0:0:3","input":[{"text":"{\"allow\": [{\"cidr\": \"1:0:0:2:0:0:0:3\"}]}"}],"expected":{"allow":["1:0:0:2::3/128"],"deny":[]}}, + {"name":"cidr: fe80::1","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1\"}]}"}],"expected":{"allow":["fe80::1/128"],"deny":[]}}, + {"name":"cidr: fe80::1%eth0","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%eth0\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%eth0"}}, + {"name":"cidr: fe80::1%eth0/64","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%eth0/64\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::1%eth0/64"}}, + {"name":"cidr: fe80::%1/10","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::%1/10\"}]}"}],"expected":{"error":"allow[0].cidr must not name an IPv6 scope: fe80::%1/10"}}, + {"name":"cidr: fe80::1%","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: fe80::1%"}}, + {"name":"cidr: fe80::1%a%b","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%a%b\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: fe80::1%a%b"}}, + {"name":"cidr: fe80::1%eth0/129","input":[{"text":"{\"allow\": [{\"cidr\": \"fe80::1%eth0/129\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: fe80::1%eth0/129"}}, + {"name":"cidr: ::ffff:192.0.2.1","input":[{"text":"{\"allow\": [{\"cidr\": \"::ffff:192.0.2.1\"}]}"}],"expected":{"allow":["::ffff:192.0.2.1/128"],"deny":[]}}, + {"name":"cidr: ::ffff:192.0.2.1/120","input":[{"text":"{\"allow\": [{\"cidr\": \"::ffff:192.0.2.1/120\"}]}"}],"expected":{"allow":["::ffff:192.0.2.0/120"],"deny":[]}}, + {"name":"cidr: ::ffff:c000:201/120","input":[{"text":"{\"allow\": [{\"cidr\": \"::ffff:c000:201/120\"}]}"}],"expected":{"allow":["::ffff:192.0.2.0/120"],"deny":[]}}, + {"name":"cidr: ::FFFF:192.0.2.1/96","input":[{"text":"{\"allow\": [{\"cidr\": \"::FFFF:192.0.2.1/96\"}]}"}],"expected":{"allow":["::ffff:0.0.0.0/96"],"deny":[]}}, + {"name":"cidr: ::ffff:0:0/95","input":[{"text":"{\"allow\": [{\"cidr\": \"::ffff:0:0/95\"}]}"}],"expected":{"allow":["::fffe:0:0/95"],"deny":[]}}, + {"name":"cidr: ::192.0.2.1","input":[{"text":"{\"allow\": [{\"cidr\": \"::192.0.2.1\"}]}"}],"expected":{"allow":["::c000:201/128"],"deny":[]}}, + {"name":"cidr: ::c000:200/120","input":[{"text":"{\"allow\": [{\"cidr\": \"::c000:200/120\"}]}"}],"expected":{"allow":["::c000:200/120"],"deny":[]}}, + {"name":"cidr: 64:ff9b::192.0.2.1/120","input":[{"text":"{\"allow\": [{\"cidr\": \"64:ff9b::192.0.2.1/120\"}]}"}],"expected":{"allow":["64:ff9b::c000:200/120"],"deny":[]}}, + {"name":"cidr: 1:2:3:4:5:6:1.2.3.4","input":[{"text":"{\"allow\": [{\"cidr\": \"1:2:3:4:5:6:1.2.3.4\"}]}"}],"expected":{"allow":["1:2:3:4:5:6:102:304/128"],"deny":[]}}, + {"name":"cidr: 1:2:3:4:5::1.2.3.4","input":[{"text":"{\"allow\": [{\"cidr\": \"1:2:3:4:5::1.2.3.4\"}]}"}],"expected":{"allow":["1:2:3:4:5:0:102:304/128"],"deny":[]}}, + {"name":"cidr: 1:2:3:4:5:6:7:1.2.3.4","input":[{"text":"{\"allow\": [{\"cidr\": \"1:2:3:4:5:6:7:1.2.3.4\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 1:2:3:4:5:6:7:1.2.3.4"}}, + {"name":"cidr: 1:2:3:4:5:6::1.2.3.4","input":[{"text":"{\"allow\": [{\"cidr\": \"1:2:3:4:5:6::1.2.3.4\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 1:2:3:4:5:6::1.2.3.4"}}, + {"name":"cidr: ::1.2.3","input":[{"text":"{\"allow\": [{\"cidr\": \"::1.2.3\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: ::1.2.3"}}, + {"name":"cidr: ::ffff:1.2.3.04","input":[{"text":"{\"allow\": [{\"cidr\": \"::ffff:1.2.3.04\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: ::ffff:1.2.3.04"}}, + {"name":"cidr: 1:2:3:4:5:6:7:8","input":[{"text":"{\"allow\": [{\"cidr\": \"1:2:3:4:5:6:7:8\"}]}"}],"expected":{"allow":["1:2:3:4:5:6:7:8/128"],"deny":[]}}, + {"name":"cidr: 1:2:3:4:5:6:7::","input":[{"text":"{\"allow\": [{\"cidr\": \"1:2:3:4:5:6:7::\"}]}"}],"expected":{"allow":["1:2:3:4:5:6:7:0/128"],"deny":[]}}, + {"name":"cidr: ::2:3:4:5:6:7:8","input":[{"text":"{\"allow\": [{\"cidr\": \"::2:3:4:5:6:7:8\"}]}"}],"expected":{"allow":["0:2:3:4:5:6:7:8/128"],"deny":[]}}, + {"name":"cidr: 1:2:3:4:5:6:7:8:9","input":[{"text":"{\"allow\": [{\"cidr\": \"1:2:3:4:5:6:7:8:9\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 1:2:3:4:5:6:7:8:9"}}, + {"name":"cidr: ::1:2:3:4:5:6:7:8","input":[{"text":"{\"allow\": [{\"cidr\": \"::1:2:3:4:5:6:7:8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: ::1:2:3:4:5:6:7:8"}}, + {"name":"cidr: 1::2::3","input":[{"text":"{\"allow\": [{\"cidr\": \"1::2::3\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 1::2::3"}}, + {"name":"cidr: :1::2","input":[{"text":"{\"allow\": [{\"cidr\": \":1::2\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: :1::2"}}, + {"name":"cidr: 1::2:","input":[{"text":"{\"allow\": [{\"cidr\": \"1::2:\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 1::2:"}}, + {"name":"cidr: :::","input":[{"text":"{\"allow\": [{\"cidr\": \":::\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: :::"}}, + {"name":"cidr: 00000::","input":[{"text":"{\"allow\": [{\"cidr\": \"00000::\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 00000::"}}, + {"name":"cidr: 0000::","input":[{"text":"{\"allow\": [{\"cidr\": \"0000::\"}]}"}],"expected":{"allow":["::/128"],"deny":[]}}, + {"name":"cidr: [::1]","input":[{"text":"{\"allow\": [{\"cidr\": \"[::1]\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: [::1]"}}, + {"name":"cidr: ::1/0x8","input":[{"text":"{\"allow\": [{\"cidr\": \"::1/0x8\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: ::1/0x8"}}, + {"name":"cidr: 2001:db8::g","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::g\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 2001:db8::g"}}, + {"name":"cidr: 2001:db8::\uff11","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::\\uff11\"}]}"}],"expected":{"error":"allow[0].cidr is not a valid IPv4 or IPv6 CIDR: 2001:db8::\uff11"}}, + {"name":"exception: 10.0.0.0/8","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"10.0.0.0/8\"]}]}]}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"exception: 10.1.2.3/16","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"10.1.2.3/16\"]}]}]}"}],"expected":{"allow":[],"deny":["10.0.0.0/16","10.2.0.0/15","10.4.0.0/14","10.8.0.0/13","10.16.0.0/12","10.32.0.0/11","10.64.0.0/10","10.128.0.0/9"]}}, + {"name":"exception: 10.255.255.255","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"10.255.255.255\"]}]}]}"}],"expected":{"allow":[],"deny":["10.0.0.0/9","10.128.0.0/10","10.192.0.0/11","10.224.0.0/12","10.240.0.0/13","10.248.0.0/14","10.252.0.0/15","10.254.0.0/16","10.255.0.0/17","10.255.128.0/18","10.255.192.0/19","10.255.224.0/20","10.255.240.0/21","10.255.248.0/22","10.255.252.0/23","10.255.254.0/24","10.255.255.0/25","10.255.255.128/26","10.255.255.192/27","10.255.255.224/28","10.255.255.240/29","10.255.255.248/30","10.255.255.252/31","10.255.255.254/32"]}}, + {"name":"exception: 10.0.0.0/255.255.0.0","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"10.0.0.0/255.255.0.0\"]}]}]}"}],"expected":{"allow":[],"deny":["10.1.0.0/16","10.2.0.0/15","10.4.0.0/14","10.8.0.0/13","10.16.0.0/12","10.32.0.0/11","10.64.0.0/10","10.128.0.0/9"]}}, + {"name":"exception: 10.0.0.0/7","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"10.0.0.0/7\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must be contained in 10.0.0.0/8"}}, + {"name":"exception: 11.0.0.0/8","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"11.0.0.0/8\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must be contained in 10.0.0.0/8"}}, + {"name":"exception: 0.0.0.0/0","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"0.0.0.0/0\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must be contained in 10.0.0.0/8"}}, + {"name":"exception: ::/96","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"::/96\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must be contained in 10.0.0.0/8"}}, + {"name":"exception: ::ffff:10.0.0.0/104","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"::ffff:10.0.0.0/104\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must be contained in 10.0.0.0/8"}}, + {"name":"exception: fe80::1%eth0","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"fe80::1%eth0\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] must not name an IPv6 scope: fe80::1%eth0"}}, + {"name":"exception: 10.0.0.0/+16","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/8\", \"except\": [\"10.0.0.0/+16\"]}]}]}"}],"expected":{"error":"deny[0].to[0].except[0] is not a valid IPv4 or IPv6 CIDR: 10.0.0.0/+16"}}, + {"name":"limit: address-only at the limit","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1\"}, {\"cidr\": \"10.0.1.1\"}, {\"cidr\": \"10.0.2.1\"}, {\"cidr\": \"10.0.3.1\"}, {\"cidr\": \"10.0.4.1\"}, {\"cidr\": \"10.0.5.1\"}, {\"cidr\": \"10.0.6.1\"}, {\"cidr\": \"10.0.7.1\"}, {\"cidr\": \"10.0.8.1\"}, {\"cidr\": \"10.0.9.1\"}, {\"cidr\": \"10.0.10.1\"}, {\"cidr\": \"10.0.11.1\"}, {\"cidr\": \"10.0.12.1\"}, {\"cidr\": \"10.0.13.1\"}, {\"cidr\": \"10.0.14.1\"}, {\"cidr\": \"10.0.15.1\"}, {\"cidr\": \"10.0.16.1\"}, {\"cidr\": \"10.0.17.1\"}, {\"cidr\": \"10.0.18.1\"}, {\"cidr\": \"10.0.19.1\"}, {\"cidr\": \"10.0.20.1\"}, {\"cidr\": \"10.0.21.1\"}, {\"cidr\": \"10.0.22.1\"}, {\"cidr\": \"10.0.23.1\"}, {\"cidr\": \"10.0.24.1\"}, {\"cidr\": \"10.0.25.1\"}, {\"cidr\": \"10.0.26.1\"}, {\"cidr\": \"10.0.27.1\"}, {\"cidr\": \"10.0.28.1\"}, {\"cidr\": \"10.0.29.1\"}, {\"cidr\": \"10.0.30.1\"}, {\"cidr\": \"10.0.31.1\"}, {\"cidr\": \"10.0.32.1\"}, {\"cidr\": \"10.0.33.1\"}, {\"cidr\": \"10.0.34.1\"}, {\"cidr\": \"10.0.35.1\"}, {\"cidr\": \"10.0.36.1\"}, {\"cidr\": \"10.0.37.1\"}, {\"cidr\": \"10.0.38.1\"}, {\"cidr\": \"10.0.39.1\"}, {\"cidr\": \"10.0.40.1\"}, {\"cidr\": \"10.0.41.1\"}, {\"cidr\": \"10.0.42.1\"}, {\"cidr\": \"10.0.43.1\"}, {\"cidr\": \"10.0.44.1\"}, {\"cidr\": \"10.0.45.1\"}, {\"cidr\": \"10.0.46.1\"}, {\"cidr\": \"10.0.47.1\"}, {\"cidr\": \"10.0.48.1\"}, {\"cidr\": \"10.0.49.1\"}, {\"cidr\": \"10.0.50.1\"}, {\"cidr\": \"10.0.51.1\"}, {\"cidr\": \"10.0.52.1\"}, {\"cidr\": \"10.0.53.1\"}, {\"cidr\": \"10.0.54.1\"}, {\"cidr\": \"10.0.55.1\"}, {\"cidr\": \"10.0.56.1\"}, {\"cidr\": \"10.0.57.1\"}, {\"cidr\": \"10.0.58.1\"}, {\"cidr\": \"10.0.59.1\"}, {\"cidr\": \"10.0.60.1\"}, {\"cidr\": \"10.0.61.1\"}, {\"cidr\": \"10.0.62.1\"}, {\"cidr\": \"10.0.63.1\"}, {\"cidr\": \"10.0.64.1\"}, {\"cidr\": \"10.0.65.1\"}, {\"cidr\": \"10.0.66.1\"}, {\"cidr\": \"10.0.67.1\"}, {\"cidr\": \"10.0.68.1\"}, {\"cidr\": \"10.0.69.1\"}, {\"cidr\": \"10.0.70.1\"}, {\"cidr\": \"10.0.71.1\"}, {\"cidr\": \"10.0.72.1\"}, {\"cidr\": \"10.0.73.1\"}, {\"cidr\": \"10.0.74.1\"}, {\"cidr\": \"10.0.75.1\"}, {\"cidr\": \"10.0.76.1\"}, {\"cidr\": \"10.0.77.1\"}, {\"cidr\": \"10.0.78.1\"}, {\"cidr\": \"10.0.79.1\"}, {\"cidr\": \"10.0.80.1\"}, {\"cidr\": \"10.0.81.1\"}, {\"cidr\": \"10.0.82.1\"}, {\"cidr\": \"10.0.83.1\"}, {\"cidr\": \"10.0.84.1\"}, {\"cidr\": \"10.0.85.1\"}, {\"cidr\": \"10.0.86.1\"}, {\"cidr\": \"10.0.87.1\"}, {\"cidr\": \"10.0.88.1\"}, {\"cidr\": \"10.0.89.1\"}, {\"cidr\": \"10.0.90.1\"}, {\"cidr\": \"10.0.91.1\"}, {\"cidr\": \"10.0.92.1\"}, {\"cidr\": \"10.0.93.1\"}, {\"cidr\": \"10.0.94.1\"}, {\"cidr\": \"10.0.95.1\"}, {\"cidr\": \"10.0.96.1\"}, {\"cidr\": \"10.0.97.1\"}, {\"cidr\": \"10.0.98.1\"}, {\"cidr\": \"10.0.99.1\"}, {\"cidr\": \"10.0.100.1\"}, {\"cidr\": \"10.0.101.1\"}, {\"cidr\": \"10.0.102.1\"}, {\"cidr\": \"10.0.103.1\"}, {\"cidr\": \"10.0.104.1\"}, {\"cidr\": \"10.0.105.1\"}, {\"cidr\": \"10.0.106.1\"}, {\"cidr\": \"10.0.107.1\"}, {\"cidr\": \"10.0.108.1\"}, {\"cidr\": \"10.0.109.1\"}, {\"cidr\": \"10.0.110.1\"}, {\"cidr\": \"10.0.111.1\"}, {\"cidr\": \"10.0.112.1\"}, {\"cidr\": \"10.0.113.1\"}, {\"cidr\": \"10.0.114.1\"}, {\"cidr\": \"10.0.115.1\"}, {\"cidr\": \"10.0.116.1\"}, {\"cidr\": \"10.0.117.1\"}, {\"cidr\": \"10.0.118.1\"}, {\"cidr\": \"10.0.119.1\"}, {\"cidr\": \"10.0.120.1\"}, {\"cidr\": \"10.0.121.1\"}, {\"cidr\": \"10.0.122.1\"}, {\"cidr\": \"10.0.123.1\"}, {\"cidr\": \"10.0.124.1\"}, {\"cidr\": \"10.0.125.1\"}, {\"cidr\": \"10.0.126.1\"}, {\"cidr\": \"10.0.127.1\"}, {\"cidr\": \"10.0.128.1\"}, {\"cidr\": \"10.0.129.1\"}, {\"cidr\": \"10.0.130.1\"}, {\"cidr\": \"10.0.131.1\"}, {\"cidr\": \"10.0.132.1\"}, {\"cidr\": \"10.0.133.1\"}, {\"cidr\": \"10.0.134.1\"}, {\"cidr\": \"10.0.135.1\"}, {\"cidr\": \"10.0.136.1\"}, {\"cidr\": \"10.0.137.1\"}, {\"cidr\": \"10.0.138.1\"}, {\"cidr\": \"10.0.139.1\"}, {\"cidr\": \"10.0.140.1\"}, {\"cidr\": \"10.0.141.1\"}, {\"cidr\": \"10.0.142.1\"}, {\"cidr\": \"10.0.143.1\"}, {\"cidr\": \"10.0.144.1\"}, {\"cidr\": \"10.0.145.1\"}, {\"cidr\": \"10.0.146.1\"}, {\"cidr\": \"10.0.147.1\"}, {\"cidr\": \"10.0.148.1\"}, {\"cidr\": \"10.0.149.1\"}, {\"cidr\": \"10.0.150.1\"}, {\"cidr\": \"10.0.151.1\"}, {\"cidr\": \"10.0.152.1\"}, {\"cidr\": \"10.0.153.1\"}, {\"cidr\": \"10.0.154.1\"}, {\"cidr\": \"10.0.155.1\"}, {\"cidr\": \"10.0.156.1\"}, {\"cidr\": \"10.0.157.1\"}, {\"cidr\": \"10.0.158.1\"}, {\"cidr\": \"10.0.159.1\"}, {\"cidr\": \"10.0.160.1\"}, {\"cidr\": \"10.0.161.1\"}, {\"cidr\": \"10.0.162.1\"}, {\"cidr\": \"10.0.163.1\"}, {\"cidr\": \"10.0.164.1\"}, {\"cidr\": \"10.0.165.1\"}, {\"cidr\": \"10.0.166.1\"}, {\"cidr\": \"10.0.167.1\"}, {\"cidr\": \"10.0.168.1\"}, {\"cidr\": \"10.0.169.1\"}, {\"cidr\": \"10.0.170.1\"}, {\"cidr\": \"10.0.171.1\"}, {\"cidr\": \"10.0.172.1\"}, {\"cidr\": \"10.0.173.1\"}, {\"cidr\": \"10.0.174.1\"}, {\"cidr\": \"10.0.175.1\"}, {\"cidr\": \"10.0.176.1\"}, {\"cidr\": \"10.0.177.1\"}, {\"cidr\": \"10.0.178.1\"}, {\"cidr\": \"10.0.179.1\"}, {\"cidr\": \"10.0.180.1\"}, {\"cidr\": \"10.0.181.1\"}, {\"cidr\": \"10.0.182.1\"}, {\"cidr\": \"10.0.183.1\"}, {\"cidr\": \"10.0.184.1\"}, {\"cidr\": \"10.0.185.1\"}, {\"cidr\": \"10.0.186.1\"}, {\"cidr\": \"10.0.187.1\"}, {\"cidr\": \"10.0.188.1\"}, {\"cidr\": \"10.0.189.1\"}, {\"cidr\": \"10.0.190.1\"}, {\"cidr\": \"10.0.191.1\"}, {\"cidr\": \"10.0.192.1\"}, {\"cidr\": \"10.0.193.1\"}, {\"cidr\": \"10.0.194.1\"}, {\"cidr\": \"10.0.195.1\"}, {\"cidr\": \"10.0.196.1\"}, {\"cidr\": \"10.0.197.1\"}, {\"cidr\": \"10.0.198.1\"}, {\"cidr\": \"10.0.199.1\"}, {\"cidr\": \"10.0.200.1\"}, {\"cidr\": \"10.0.201.1\"}, {\"cidr\": \"10.0.202.1\"}, {\"cidr\": \"10.0.203.1\"}, {\"cidr\": \"10.0.204.1\"}, {\"cidr\": \"10.0.205.1\"}, {\"cidr\": \"10.0.206.1\"}, {\"cidr\": \"10.0.207.1\"}, {\"cidr\": \"10.0.208.1\"}, {\"cidr\": \"10.0.209.1\"}, {\"cidr\": \"10.0.210.1\"}, {\"cidr\": \"10.0.211.1\"}, {\"cidr\": \"10.0.212.1\"}, {\"cidr\": \"10.0.213.1\"}, {\"cidr\": \"10.0.214.1\"}, {\"cidr\": \"10.0.215.1\"}, {\"cidr\": \"10.0.216.1\"}, {\"cidr\": \"10.0.217.1\"}, {\"cidr\": \"10.0.218.1\"}, {\"cidr\": \"10.0.219.1\"}, {\"cidr\": \"10.0.220.1\"}, {\"cidr\": \"10.0.221.1\"}, {\"cidr\": \"10.0.222.1\"}, {\"cidr\": \"10.0.223.1\"}, {\"cidr\": \"10.0.224.1\"}, {\"cidr\": \"10.0.225.1\"}, {\"cidr\": \"10.0.226.1\"}, {\"cidr\": \"10.0.227.1\"}, {\"cidr\": \"10.0.228.1\"}, {\"cidr\": \"10.0.229.1\"}, {\"cidr\": \"10.0.230.1\"}, {\"cidr\": \"10.0.231.1\"}, {\"cidr\": \"10.0.232.1\"}, {\"cidr\": \"10.0.233.1\"}, {\"cidr\": \"10.0.234.1\"}, {\"cidr\": \"10.0.235.1\"}, {\"cidr\": \"10.0.236.1\"}, {\"cidr\": \"10.0.237.1\"}, {\"cidr\": \"10.0.238.1\"}, {\"cidr\": \"10.0.239.1\"}, {\"cidr\": \"10.0.240.1\"}, {\"cidr\": \"10.0.241.1\"}, {\"cidr\": \"10.0.242.1\"}, {\"cidr\": \"10.0.243.1\"}, {\"cidr\": \"10.0.244.1\"}, {\"cidr\": \"10.0.245.1\"}, {\"cidr\": \"10.0.246.1\"}, {\"cidr\": \"10.0.247.1\"}, {\"cidr\": \"10.0.248.1\"}, {\"cidr\": \"10.0.249.1\"}, {\"cidr\": \"10.0.250.1\"}, {\"cidr\": \"10.0.251.1\"}, {\"cidr\": \"10.0.252.1\"}, {\"cidr\": \"10.0.253.1\"}, {\"cidr\": \"10.0.254.1\"}, {\"cidr\": \"10.0.255.1\"}]}"}],"expected":{"allow":["10.0.0.1/32","10.0.1.1/32","10.0.2.1/32","10.0.3.1/32","10.0.4.1/32","10.0.5.1/32","10.0.6.1/32","10.0.7.1/32","10.0.8.1/32","10.0.9.1/32","10.0.10.1/32","10.0.11.1/32","10.0.12.1/32","10.0.13.1/32","10.0.14.1/32","10.0.15.1/32","10.0.16.1/32","10.0.17.1/32","10.0.18.1/32","10.0.19.1/32","10.0.20.1/32","10.0.21.1/32","10.0.22.1/32","10.0.23.1/32","10.0.24.1/32","10.0.25.1/32","10.0.26.1/32","10.0.27.1/32","10.0.28.1/32","10.0.29.1/32","10.0.30.1/32","10.0.31.1/32","10.0.32.1/32","10.0.33.1/32","10.0.34.1/32","10.0.35.1/32","10.0.36.1/32","10.0.37.1/32","10.0.38.1/32","10.0.39.1/32","10.0.40.1/32","10.0.41.1/32","10.0.42.1/32","10.0.43.1/32","10.0.44.1/32","10.0.45.1/32","10.0.46.1/32","10.0.47.1/32","10.0.48.1/32","10.0.49.1/32","10.0.50.1/32","10.0.51.1/32","10.0.52.1/32","10.0.53.1/32","10.0.54.1/32","10.0.55.1/32","10.0.56.1/32","10.0.57.1/32","10.0.58.1/32","10.0.59.1/32","10.0.60.1/32","10.0.61.1/32","10.0.62.1/32","10.0.63.1/32","10.0.64.1/32","10.0.65.1/32","10.0.66.1/32","10.0.67.1/32","10.0.68.1/32","10.0.69.1/32","10.0.70.1/32","10.0.71.1/32","10.0.72.1/32","10.0.73.1/32","10.0.74.1/32","10.0.75.1/32","10.0.76.1/32","10.0.77.1/32","10.0.78.1/32","10.0.79.1/32","10.0.80.1/32","10.0.81.1/32","10.0.82.1/32","10.0.83.1/32","10.0.84.1/32","10.0.85.1/32","10.0.86.1/32","10.0.87.1/32","10.0.88.1/32","10.0.89.1/32","10.0.90.1/32","10.0.91.1/32","10.0.92.1/32","10.0.93.1/32","10.0.94.1/32","10.0.95.1/32","10.0.96.1/32","10.0.97.1/32","10.0.98.1/32","10.0.99.1/32","10.0.100.1/32","10.0.101.1/32","10.0.102.1/32","10.0.103.1/32","10.0.104.1/32","10.0.105.1/32","10.0.106.1/32","10.0.107.1/32","10.0.108.1/32","10.0.109.1/32","10.0.110.1/32","10.0.111.1/32","10.0.112.1/32","10.0.113.1/32","10.0.114.1/32","10.0.115.1/32","10.0.116.1/32","10.0.117.1/32","10.0.118.1/32","10.0.119.1/32","10.0.120.1/32","10.0.121.1/32","10.0.122.1/32","10.0.123.1/32","10.0.124.1/32","10.0.125.1/32","10.0.126.1/32","10.0.127.1/32","10.0.128.1/32","10.0.129.1/32","10.0.130.1/32","10.0.131.1/32","10.0.132.1/32","10.0.133.1/32","10.0.134.1/32","10.0.135.1/32","10.0.136.1/32","10.0.137.1/32","10.0.138.1/32","10.0.139.1/32","10.0.140.1/32","10.0.141.1/32","10.0.142.1/32","10.0.143.1/32","10.0.144.1/32","10.0.145.1/32","10.0.146.1/32","10.0.147.1/32","10.0.148.1/32","10.0.149.1/32","10.0.150.1/32","10.0.151.1/32","10.0.152.1/32","10.0.153.1/32","10.0.154.1/32","10.0.155.1/32","10.0.156.1/32","10.0.157.1/32","10.0.158.1/32","10.0.159.1/32","10.0.160.1/32","10.0.161.1/32","10.0.162.1/32","10.0.163.1/32","10.0.164.1/32","10.0.165.1/32","10.0.166.1/32","10.0.167.1/32","10.0.168.1/32","10.0.169.1/32","10.0.170.1/32","10.0.171.1/32","10.0.172.1/32","10.0.173.1/32","10.0.174.1/32","10.0.175.1/32","10.0.176.1/32","10.0.177.1/32","10.0.178.1/32","10.0.179.1/32","10.0.180.1/32","10.0.181.1/32","10.0.182.1/32","10.0.183.1/32","10.0.184.1/32","10.0.185.1/32","10.0.186.1/32","10.0.187.1/32","10.0.188.1/32","10.0.189.1/32","10.0.190.1/32","10.0.191.1/32","10.0.192.1/32","10.0.193.1/32","10.0.194.1/32","10.0.195.1/32","10.0.196.1/32","10.0.197.1/32","10.0.198.1/32","10.0.199.1/32","10.0.200.1/32","10.0.201.1/32","10.0.202.1/32","10.0.203.1/32","10.0.204.1/32","10.0.205.1/32","10.0.206.1/32","10.0.207.1/32","10.0.208.1/32","10.0.209.1/32","10.0.210.1/32","10.0.211.1/32","10.0.212.1/32","10.0.213.1/32","10.0.214.1/32","10.0.215.1/32","10.0.216.1/32","10.0.217.1/32","10.0.218.1/32","10.0.219.1/32","10.0.220.1/32","10.0.221.1/32","10.0.222.1/32","10.0.223.1/32","10.0.224.1/32","10.0.225.1/32","10.0.226.1/32","10.0.227.1/32","10.0.228.1/32","10.0.229.1/32","10.0.230.1/32","10.0.231.1/32","10.0.232.1/32","10.0.233.1/32","10.0.234.1/32","10.0.235.1/32","10.0.236.1/32","10.0.237.1/32","10.0.238.1/32","10.0.239.1/32","10.0.240.1/32","10.0.241.1/32","10.0.242.1/32","10.0.243.1/32","10.0.244.1/32","10.0.245.1/32","10.0.246.1/32","10.0.247.1/32","10.0.248.1/32","10.0.249.1/32","10.0.250.1/32","10.0.251.1/32","10.0.252.1/32","10.0.253.1/32","10.0.254.1/32","10.0.255.1/32"],"deny":[]}}, + {"name":"limit: address-only over the limit","input":[{"text":"{\"deny\": [{\"cidr\": \"10.0.0.1\"}, {\"cidr\": \"10.0.1.1\"}, {\"cidr\": \"10.0.2.1\"}, {\"cidr\": \"10.0.3.1\"}, {\"cidr\": \"10.0.4.1\"}, {\"cidr\": \"10.0.5.1\"}, {\"cidr\": \"10.0.6.1\"}, {\"cidr\": \"10.0.7.1\"}, {\"cidr\": \"10.0.8.1\"}, {\"cidr\": \"10.0.9.1\"}, {\"cidr\": \"10.0.10.1\"}, {\"cidr\": \"10.0.11.1\"}, {\"cidr\": \"10.0.12.1\"}, {\"cidr\": \"10.0.13.1\"}, {\"cidr\": \"10.0.14.1\"}, {\"cidr\": \"10.0.15.1\"}, {\"cidr\": \"10.0.16.1\"}, {\"cidr\": \"10.0.17.1\"}, {\"cidr\": \"10.0.18.1\"}, {\"cidr\": \"10.0.19.1\"}, {\"cidr\": \"10.0.20.1\"}, {\"cidr\": \"10.0.21.1\"}, {\"cidr\": \"10.0.22.1\"}, {\"cidr\": \"10.0.23.1\"}, {\"cidr\": \"10.0.24.1\"}, {\"cidr\": \"10.0.25.1\"}, {\"cidr\": \"10.0.26.1\"}, {\"cidr\": \"10.0.27.1\"}, {\"cidr\": \"10.0.28.1\"}, {\"cidr\": \"10.0.29.1\"}, {\"cidr\": \"10.0.30.1\"}, {\"cidr\": \"10.0.31.1\"}, {\"cidr\": \"10.0.32.1\"}, {\"cidr\": \"10.0.33.1\"}, {\"cidr\": \"10.0.34.1\"}, {\"cidr\": \"10.0.35.1\"}, {\"cidr\": \"10.0.36.1\"}, {\"cidr\": \"10.0.37.1\"}, {\"cidr\": \"10.0.38.1\"}, {\"cidr\": \"10.0.39.1\"}, {\"cidr\": \"10.0.40.1\"}, {\"cidr\": \"10.0.41.1\"}, {\"cidr\": \"10.0.42.1\"}, {\"cidr\": \"10.0.43.1\"}, {\"cidr\": \"10.0.44.1\"}, {\"cidr\": \"10.0.45.1\"}, {\"cidr\": \"10.0.46.1\"}, {\"cidr\": \"10.0.47.1\"}, {\"cidr\": \"10.0.48.1\"}, {\"cidr\": \"10.0.49.1\"}, {\"cidr\": \"10.0.50.1\"}, {\"cidr\": \"10.0.51.1\"}, {\"cidr\": \"10.0.52.1\"}, {\"cidr\": \"10.0.53.1\"}, {\"cidr\": \"10.0.54.1\"}, {\"cidr\": \"10.0.55.1\"}, {\"cidr\": \"10.0.56.1\"}, {\"cidr\": \"10.0.57.1\"}, {\"cidr\": \"10.0.58.1\"}, {\"cidr\": \"10.0.59.1\"}, {\"cidr\": \"10.0.60.1\"}, {\"cidr\": \"10.0.61.1\"}, {\"cidr\": \"10.0.62.1\"}, {\"cidr\": \"10.0.63.1\"}, {\"cidr\": \"10.0.64.1\"}, {\"cidr\": \"10.0.65.1\"}, {\"cidr\": \"10.0.66.1\"}, {\"cidr\": \"10.0.67.1\"}, {\"cidr\": \"10.0.68.1\"}, {\"cidr\": \"10.0.69.1\"}, {\"cidr\": \"10.0.70.1\"}, {\"cidr\": \"10.0.71.1\"}, {\"cidr\": \"10.0.72.1\"}, {\"cidr\": \"10.0.73.1\"}, {\"cidr\": \"10.0.74.1\"}, {\"cidr\": \"10.0.75.1\"}, {\"cidr\": \"10.0.76.1\"}, {\"cidr\": \"10.0.77.1\"}, {\"cidr\": \"10.0.78.1\"}, {\"cidr\": \"10.0.79.1\"}, {\"cidr\": \"10.0.80.1\"}, {\"cidr\": \"10.0.81.1\"}, {\"cidr\": \"10.0.82.1\"}, {\"cidr\": \"10.0.83.1\"}, {\"cidr\": \"10.0.84.1\"}, {\"cidr\": \"10.0.85.1\"}, {\"cidr\": \"10.0.86.1\"}, {\"cidr\": \"10.0.87.1\"}, {\"cidr\": \"10.0.88.1\"}, {\"cidr\": \"10.0.89.1\"}, {\"cidr\": \"10.0.90.1\"}, {\"cidr\": \"10.0.91.1\"}, {\"cidr\": \"10.0.92.1\"}, {\"cidr\": \"10.0.93.1\"}, {\"cidr\": \"10.0.94.1\"}, {\"cidr\": \"10.0.95.1\"}, {\"cidr\": \"10.0.96.1\"}, {\"cidr\": \"10.0.97.1\"}, {\"cidr\": \"10.0.98.1\"}, {\"cidr\": \"10.0.99.1\"}, {\"cidr\": \"10.0.100.1\"}, {\"cidr\": \"10.0.101.1\"}, {\"cidr\": \"10.0.102.1\"}, {\"cidr\": \"10.0.103.1\"}, {\"cidr\": \"10.0.104.1\"}, {\"cidr\": \"10.0.105.1\"}, {\"cidr\": \"10.0.106.1\"}, {\"cidr\": \"10.0.107.1\"}, {\"cidr\": \"10.0.108.1\"}, {\"cidr\": \"10.0.109.1\"}, {\"cidr\": \"10.0.110.1\"}, {\"cidr\": \"10.0.111.1\"}, {\"cidr\": \"10.0.112.1\"}, {\"cidr\": \"10.0.113.1\"}, {\"cidr\": \"10.0.114.1\"}, {\"cidr\": \"10.0.115.1\"}, {\"cidr\": \"10.0.116.1\"}, {\"cidr\": \"10.0.117.1\"}, {\"cidr\": \"10.0.118.1\"}, {\"cidr\": \"10.0.119.1\"}, {\"cidr\": \"10.0.120.1\"}, {\"cidr\": \"10.0.121.1\"}, {\"cidr\": \"10.0.122.1\"}, {\"cidr\": \"10.0.123.1\"}, {\"cidr\": \"10.0.124.1\"}, {\"cidr\": \"10.0.125.1\"}, {\"cidr\": \"10.0.126.1\"}, {\"cidr\": \"10.0.127.1\"}, {\"cidr\": \"10.0.128.1\"}, {\"cidr\": \"10.0.129.1\"}, {\"cidr\": \"10.0.130.1\"}, {\"cidr\": \"10.0.131.1\"}, {\"cidr\": \"10.0.132.1\"}, {\"cidr\": \"10.0.133.1\"}, {\"cidr\": \"10.0.134.1\"}, {\"cidr\": \"10.0.135.1\"}, {\"cidr\": \"10.0.136.1\"}, {\"cidr\": \"10.0.137.1\"}, {\"cidr\": \"10.0.138.1\"}, {\"cidr\": \"10.0.139.1\"}, {\"cidr\": \"10.0.140.1\"}, {\"cidr\": \"10.0.141.1\"}, {\"cidr\": \"10.0.142.1\"}, {\"cidr\": \"10.0.143.1\"}, {\"cidr\": \"10.0.144.1\"}, {\"cidr\": \"10.0.145.1\"}, {\"cidr\": \"10.0.146.1\"}, {\"cidr\": \"10.0.147.1\"}, {\"cidr\": \"10.0.148.1\"}, {\"cidr\": \"10.0.149.1\"}, {\"cidr\": \"10.0.150.1\"}, {\"cidr\": \"10.0.151.1\"}, {\"cidr\": \"10.0.152.1\"}, {\"cidr\": \"10.0.153.1\"}, {\"cidr\": \"10.0.154.1\"}, {\"cidr\": \"10.0.155.1\"}, {\"cidr\": \"10.0.156.1\"}, {\"cidr\": \"10.0.157.1\"}, {\"cidr\": \"10.0.158.1\"}, {\"cidr\": \"10.0.159.1\"}, {\"cidr\": \"10.0.160.1\"}, {\"cidr\": \"10.0.161.1\"}, {\"cidr\": \"10.0.162.1\"}, {\"cidr\": \"10.0.163.1\"}, {\"cidr\": \"10.0.164.1\"}, {\"cidr\": \"10.0.165.1\"}, {\"cidr\": \"10.0.166.1\"}, {\"cidr\": \"10.0.167.1\"}, {\"cidr\": \"10.0.168.1\"}, {\"cidr\": \"10.0.169.1\"}, {\"cidr\": \"10.0.170.1\"}, {\"cidr\": \"10.0.171.1\"}, {\"cidr\": \"10.0.172.1\"}, {\"cidr\": \"10.0.173.1\"}, {\"cidr\": \"10.0.174.1\"}, {\"cidr\": \"10.0.175.1\"}, {\"cidr\": \"10.0.176.1\"}, {\"cidr\": \"10.0.177.1\"}, {\"cidr\": \"10.0.178.1\"}, {\"cidr\": \"10.0.179.1\"}, {\"cidr\": \"10.0.180.1\"}, {\"cidr\": \"10.0.181.1\"}, {\"cidr\": \"10.0.182.1\"}, {\"cidr\": \"10.0.183.1\"}, {\"cidr\": \"10.0.184.1\"}, {\"cidr\": \"10.0.185.1\"}, {\"cidr\": \"10.0.186.1\"}, {\"cidr\": \"10.0.187.1\"}, {\"cidr\": \"10.0.188.1\"}, {\"cidr\": \"10.0.189.1\"}, {\"cidr\": \"10.0.190.1\"}, {\"cidr\": \"10.0.191.1\"}, {\"cidr\": \"10.0.192.1\"}, {\"cidr\": \"10.0.193.1\"}, {\"cidr\": \"10.0.194.1\"}, {\"cidr\": \"10.0.195.1\"}, {\"cidr\": \"10.0.196.1\"}, {\"cidr\": \"10.0.197.1\"}, {\"cidr\": \"10.0.198.1\"}, {\"cidr\": \"10.0.199.1\"}, {\"cidr\": \"10.0.200.1\"}, {\"cidr\": \"10.0.201.1\"}, {\"cidr\": \"10.0.202.1\"}, {\"cidr\": \"10.0.203.1\"}, {\"cidr\": \"10.0.204.1\"}, {\"cidr\": \"10.0.205.1\"}, {\"cidr\": \"10.0.206.1\"}, {\"cidr\": \"10.0.207.1\"}, {\"cidr\": \"10.0.208.1\"}, {\"cidr\": \"10.0.209.1\"}, {\"cidr\": \"10.0.210.1\"}, {\"cidr\": \"10.0.211.1\"}, {\"cidr\": \"10.0.212.1\"}, {\"cidr\": \"10.0.213.1\"}, {\"cidr\": \"10.0.214.1\"}, {\"cidr\": \"10.0.215.1\"}, {\"cidr\": \"10.0.216.1\"}, {\"cidr\": \"10.0.217.1\"}, {\"cidr\": \"10.0.218.1\"}, {\"cidr\": \"10.0.219.1\"}, {\"cidr\": \"10.0.220.1\"}, {\"cidr\": \"10.0.221.1\"}, {\"cidr\": \"10.0.222.1\"}, {\"cidr\": \"10.0.223.1\"}, {\"cidr\": \"10.0.224.1\"}, {\"cidr\": \"10.0.225.1\"}, {\"cidr\": \"10.0.226.1\"}, {\"cidr\": \"10.0.227.1\"}, {\"cidr\": \"10.0.228.1\"}, {\"cidr\": \"10.0.229.1\"}, {\"cidr\": \"10.0.230.1\"}, {\"cidr\": \"10.0.231.1\"}, {\"cidr\": \"10.0.232.1\"}, {\"cidr\": \"10.0.233.1\"}, {\"cidr\": \"10.0.234.1\"}, {\"cidr\": \"10.0.235.1\"}, {\"cidr\": \"10.0.236.1\"}, {\"cidr\": \"10.0.237.1\"}, {\"cidr\": \"10.0.238.1\"}, {\"cidr\": \"10.0.239.1\"}, {\"cidr\": \"10.0.240.1\"}, {\"cidr\": \"10.0.241.1\"}, {\"cidr\": \"10.0.242.1\"}, {\"cidr\": \"10.0.243.1\"}, {\"cidr\": \"10.0.244.1\"}, {\"cidr\": \"10.0.245.1\"}, {\"cidr\": \"10.0.246.1\"}, {\"cidr\": \"10.0.247.1\"}, {\"cidr\": \"10.0.248.1\"}, {\"cidr\": \"10.0.249.1\"}, {\"cidr\": \"10.0.250.1\"}, {\"cidr\": \"10.0.251.1\"}, {\"cidr\": \"10.0.252.1\"}, {\"cidr\": \"10.0.253.1\"}, {\"cidr\": \"10.0.254.1\"}, {\"cidr\": \"10.0.255.1\"}, {\"cidr\": \"10.1.0.1\"}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"limit: tcp for every port over the limit","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1\"}, {\"cidr\": \"10.0.1.1\"}, {\"cidr\": \"10.0.2.1\"}, {\"cidr\": \"10.0.3.1\"}, {\"cidr\": \"10.0.4.1\"}, {\"cidr\": \"10.0.5.1\"}, {\"cidr\": \"10.0.6.1\"}, {\"cidr\": \"10.0.7.1\"}, {\"cidr\": \"10.0.8.1\"}, {\"cidr\": \"10.0.9.1\"}, {\"cidr\": \"10.0.10.1\"}, {\"cidr\": \"10.0.11.1\"}, {\"cidr\": \"10.0.12.1\"}, {\"cidr\": \"10.0.13.1\"}, {\"cidr\": \"10.0.14.1\"}, {\"cidr\": \"10.0.15.1\"}, {\"cidr\": \"10.0.16.1\"}, {\"cidr\": \"10.0.17.1\"}, {\"cidr\": \"10.0.18.1\"}, {\"cidr\": \"10.0.19.1\"}, {\"cidr\": \"10.0.20.1\"}, {\"cidr\": \"10.0.21.1\"}, {\"cidr\": \"10.0.22.1\"}, {\"cidr\": \"10.0.23.1\"}, {\"cidr\": \"10.0.24.1\"}, {\"cidr\": \"10.0.25.1\"}, {\"cidr\": \"10.0.26.1\"}, {\"cidr\": \"10.0.27.1\"}, {\"cidr\": \"10.0.28.1\"}, {\"cidr\": \"10.0.29.1\"}, {\"cidr\": \"10.0.30.1\"}, {\"cidr\": \"10.0.31.1\"}, {\"cidr\": \"10.0.32.1\"}, {\"cidr\": \"10.0.33.1\"}, {\"cidr\": \"10.0.34.1\"}, {\"cidr\": \"10.0.35.1\"}, {\"cidr\": \"10.0.36.1\"}, {\"cidr\": \"10.0.37.1\"}, {\"cidr\": \"10.0.38.1\"}, {\"cidr\": \"10.0.39.1\"}, {\"cidr\": \"10.0.40.1\"}, {\"cidr\": \"10.0.41.1\"}, {\"cidr\": \"10.0.42.1\"}, {\"cidr\": \"10.0.43.1\"}, {\"cidr\": \"10.0.44.1\"}, {\"cidr\": \"10.0.45.1\"}, {\"cidr\": \"10.0.46.1\"}, {\"cidr\": \"10.0.47.1\"}, {\"cidr\": \"10.0.48.1\"}, {\"cidr\": \"10.0.49.1\"}, {\"cidr\": \"10.0.50.1\"}, {\"cidr\": \"10.0.51.1\"}, {\"cidr\": \"10.0.52.1\"}, {\"cidr\": \"10.0.53.1\"}, {\"cidr\": \"10.0.54.1\"}, {\"cidr\": \"10.0.55.1\"}, {\"cidr\": \"10.0.56.1\"}, {\"cidr\": \"10.0.57.1\"}, {\"cidr\": \"10.0.58.1\"}, {\"cidr\": \"10.0.59.1\"}, {\"cidr\": \"10.0.60.1\"}, {\"cidr\": \"10.0.61.1\"}, {\"cidr\": \"10.0.62.1\"}, {\"cidr\": \"10.0.63.1\"}, {\"cidr\": \"10.0.64.1\"}, {\"cidr\": \"10.0.65.1\"}, {\"cidr\": \"10.0.66.1\"}, {\"cidr\": \"10.0.67.1\"}, {\"cidr\": \"10.0.68.1\"}, {\"cidr\": \"10.0.69.1\"}, {\"cidr\": \"10.0.70.1\"}, {\"cidr\": \"10.0.71.1\"}, {\"cidr\": \"10.0.72.1\"}, {\"cidr\": \"10.0.73.1\"}, {\"cidr\": \"10.0.74.1\"}, {\"cidr\": \"10.0.75.1\"}, {\"cidr\": \"10.0.76.1\"}, {\"cidr\": \"10.0.77.1\"}, {\"cidr\": \"10.0.78.1\"}, {\"cidr\": \"10.0.79.1\"}, {\"cidr\": \"10.0.80.1\"}, {\"cidr\": \"10.0.81.1\"}, {\"cidr\": \"10.0.82.1\"}, {\"cidr\": \"10.0.83.1\"}, {\"cidr\": \"10.0.84.1\"}, {\"cidr\": \"10.0.85.1\"}, {\"cidr\": \"10.0.86.1\"}, {\"cidr\": \"10.0.87.1\"}, {\"cidr\": \"10.0.88.1\"}, {\"cidr\": \"10.0.89.1\"}, {\"cidr\": \"10.0.90.1\"}, {\"cidr\": \"10.0.91.1\"}, {\"cidr\": \"10.0.92.1\"}, {\"cidr\": \"10.0.93.1\"}, {\"cidr\": \"10.0.94.1\"}, {\"cidr\": \"10.0.95.1\"}, {\"cidr\": \"10.0.96.1\"}, {\"cidr\": \"10.0.97.1\"}, {\"cidr\": \"10.0.98.1\"}, {\"cidr\": \"10.0.99.1\"}, {\"cidr\": \"10.0.100.1\"}, {\"cidr\": \"10.0.101.1\"}, {\"cidr\": \"10.0.102.1\"}, {\"cidr\": \"10.0.103.1\"}, {\"cidr\": \"10.0.104.1\"}, {\"cidr\": \"10.0.105.1\"}, {\"cidr\": \"10.0.106.1\"}, {\"cidr\": \"10.0.107.1\"}, {\"cidr\": \"10.0.108.1\"}, {\"cidr\": \"10.0.109.1\"}, {\"cidr\": \"10.0.110.1\"}, {\"cidr\": \"10.0.111.1\"}, {\"cidr\": \"10.0.112.1\"}, {\"cidr\": \"10.0.113.1\"}, {\"cidr\": \"10.0.114.1\"}, {\"cidr\": \"10.0.115.1\"}, {\"cidr\": \"10.0.116.1\"}, {\"cidr\": \"10.0.117.1\"}, {\"cidr\": \"10.0.118.1\"}, {\"cidr\": \"10.0.119.1\"}, {\"cidr\": \"10.0.120.1\"}, {\"cidr\": \"10.0.121.1\"}, {\"cidr\": \"10.0.122.1\"}, {\"cidr\": \"10.0.123.1\"}, {\"cidr\": \"10.0.124.1\"}, {\"cidr\": \"10.0.125.1\"}, {\"cidr\": \"10.0.126.1\"}, {\"cidr\": \"10.0.127.1\"}, {\"cidr\": \"10.0.128.1\"}, {\"cidr\": \"10.0.129.1\"}, {\"cidr\": \"10.0.130.1\"}, {\"cidr\": \"10.0.131.1\"}, {\"cidr\": \"10.0.132.1\"}, {\"cidr\": \"10.0.133.1\"}, {\"cidr\": \"10.0.134.1\"}, {\"cidr\": \"10.0.135.1\"}, {\"cidr\": \"10.0.136.1\"}, {\"cidr\": \"10.0.137.1\"}, {\"cidr\": \"10.0.138.1\"}, {\"cidr\": \"10.0.139.1\"}, {\"cidr\": \"10.0.140.1\"}, {\"cidr\": \"10.0.141.1\"}, {\"cidr\": \"10.0.142.1\"}, {\"cidr\": \"10.0.143.1\"}, {\"cidr\": \"10.0.144.1\"}, {\"cidr\": \"10.0.145.1\"}, {\"cidr\": \"10.0.146.1\"}, {\"cidr\": \"10.0.147.1\"}, {\"cidr\": \"10.0.148.1\"}, {\"cidr\": \"10.0.149.1\"}, {\"cidr\": \"10.0.150.1\"}, {\"cidr\": \"10.0.151.1\"}, {\"cidr\": \"10.0.152.1\"}, {\"cidr\": \"10.0.153.1\"}, {\"cidr\": \"10.0.154.1\"}, {\"cidr\": \"10.0.155.1\"}, {\"cidr\": \"10.0.156.1\"}, {\"cidr\": \"10.0.157.1\"}, {\"cidr\": \"10.0.158.1\"}, {\"cidr\": \"10.0.159.1\"}, {\"cidr\": \"10.0.160.1\"}, {\"cidr\": \"10.0.161.1\"}, {\"cidr\": \"10.0.162.1\"}, {\"cidr\": \"10.0.163.1\"}, {\"cidr\": \"10.0.164.1\"}, {\"cidr\": \"10.0.165.1\"}, {\"cidr\": \"10.0.166.1\"}, {\"cidr\": \"10.0.167.1\"}, {\"cidr\": \"10.0.168.1\"}, {\"cidr\": \"10.0.169.1\"}, {\"cidr\": \"10.0.170.1\"}, {\"cidr\": \"10.0.171.1\"}, {\"cidr\": \"10.0.172.1\"}, {\"cidr\": \"10.0.173.1\"}, {\"cidr\": \"10.0.174.1\"}, {\"cidr\": \"10.0.175.1\"}, {\"cidr\": \"10.0.176.1\"}, {\"cidr\": \"10.0.177.1\"}, {\"cidr\": \"10.0.178.1\"}, {\"cidr\": \"10.0.179.1\"}, {\"cidr\": \"10.0.180.1\"}, {\"cidr\": \"10.0.181.1\"}, {\"cidr\": \"10.0.182.1\"}, {\"cidr\": \"10.0.183.1\"}, {\"cidr\": \"10.0.184.1\"}, {\"cidr\": \"10.0.185.1\"}, {\"cidr\": \"10.0.186.1\"}, {\"cidr\": \"10.0.187.1\"}, {\"cidr\": \"10.0.188.1\"}, {\"cidr\": \"10.0.189.1\"}, {\"cidr\": \"10.0.190.1\"}, {\"cidr\": \"10.0.191.1\"}, {\"cidr\": \"10.0.192.1\"}, {\"cidr\": \"10.0.193.1\"}, {\"cidr\": \"10.0.194.1\"}, {\"cidr\": \"10.0.195.1\"}, {\"cidr\": \"10.0.196.1\"}, {\"cidr\": \"10.0.197.1\"}, {\"cidr\": \"10.0.198.1\"}, {\"cidr\": \"10.0.199.1\"}, {\"cidr\": \"10.0.200.1\"}, {\"cidr\": \"10.0.201.1\"}, {\"cidr\": \"10.0.202.1\"}, {\"cidr\": \"10.0.203.1\"}, {\"cidr\": \"10.0.204.1\"}, {\"cidr\": \"10.0.205.1\"}, {\"cidr\": \"10.0.206.1\"}, {\"cidr\": \"10.0.207.1\"}, {\"cidr\": \"10.0.208.1\"}, {\"cidr\": \"10.0.209.1\"}, {\"cidr\": \"10.0.210.1\"}, {\"cidr\": \"10.0.211.1\"}, {\"cidr\": \"10.0.212.1\"}, {\"cidr\": \"10.0.213.1\"}, {\"cidr\": \"10.0.214.1\"}, {\"cidr\": \"10.0.215.1\"}, {\"cidr\": \"10.0.216.1\"}, {\"cidr\": \"10.0.217.1\"}, {\"cidr\": \"10.0.218.1\"}, {\"cidr\": \"10.0.219.1\"}, {\"cidr\": \"10.0.220.1\"}, {\"cidr\": \"10.0.221.1\"}, {\"cidr\": \"10.0.222.1\"}, {\"cidr\": \"10.0.223.1\"}, {\"cidr\": \"10.0.224.1\"}, {\"cidr\": \"10.0.225.1\"}, {\"cidr\": \"10.0.226.1\"}, {\"cidr\": \"10.0.227.1\"}, {\"cidr\": \"10.0.228.1\"}, {\"cidr\": \"10.0.229.1\"}, {\"cidr\": \"10.0.230.1\"}, {\"cidr\": \"10.0.231.1\"}, {\"cidr\": \"10.0.232.1\"}, {\"cidr\": \"10.0.233.1\"}, {\"cidr\": \"10.0.234.1\"}, {\"cidr\": \"10.0.235.1\"}, {\"cidr\": \"10.0.236.1\"}, {\"cidr\": \"10.0.237.1\"}, {\"cidr\": \"10.0.238.1\"}, {\"cidr\": \"10.0.239.1\"}, {\"cidr\": \"10.0.240.1\"}, {\"cidr\": \"10.0.241.1\"}, {\"cidr\": \"10.0.242.1\"}, {\"cidr\": \"10.0.243.1\"}, {\"cidr\": \"10.0.244.1\"}, {\"cidr\": \"10.0.245.1\"}, {\"cidr\": \"10.0.246.1\"}, {\"cidr\": \"10.0.247.1\"}, {\"cidr\": \"10.0.248.1\"}, {\"cidr\": \"10.0.249.1\"}, {\"cidr\": \"10.0.250.1\"}, {\"cidr\": \"10.0.251.1\"}, {\"cidr\": \"10.0.252.1\"}, {\"cidr\": \"10.0.253.1\"}, {\"cidr\": \"10.0.254.1\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\"}, {\"cidr\": \"198.51.100.0/24\", \"protocol\": \"tcp\"}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"limit: udp for every port over the limit","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1\"}, {\"cidr\": \"10.0.1.1\"}, {\"cidr\": \"10.0.2.1\"}, {\"cidr\": \"10.0.3.1\"}, {\"cidr\": \"10.0.4.1\"}, {\"cidr\": \"10.0.5.1\"}, {\"cidr\": \"10.0.6.1\"}, {\"cidr\": \"10.0.7.1\"}, {\"cidr\": \"10.0.8.1\"}, {\"cidr\": \"10.0.9.1\"}, {\"cidr\": \"10.0.10.1\"}, {\"cidr\": \"10.0.11.1\"}, {\"cidr\": \"10.0.12.1\"}, {\"cidr\": \"10.0.13.1\"}, {\"cidr\": \"10.0.14.1\"}, {\"cidr\": \"10.0.15.1\"}, {\"cidr\": \"10.0.16.1\"}, {\"cidr\": \"10.0.17.1\"}, {\"cidr\": \"10.0.18.1\"}, {\"cidr\": \"10.0.19.1\"}, {\"cidr\": \"10.0.20.1\"}, {\"cidr\": \"10.0.21.1\"}, {\"cidr\": \"10.0.22.1\"}, {\"cidr\": \"10.0.23.1\"}, {\"cidr\": \"10.0.24.1\"}, {\"cidr\": \"10.0.25.1\"}, {\"cidr\": \"10.0.26.1\"}, {\"cidr\": \"10.0.27.1\"}, {\"cidr\": \"10.0.28.1\"}, {\"cidr\": \"10.0.29.1\"}, {\"cidr\": \"10.0.30.1\"}, {\"cidr\": \"10.0.31.1\"}, {\"cidr\": \"10.0.32.1\"}, {\"cidr\": \"10.0.33.1\"}, {\"cidr\": \"10.0.34.1\"}, {\"cidr\": \"10.0.35.1\"}, {\"cidr\": \"10.0.36.1\"}, {\"cidr\": \"10.0.37.1\"}, {\"cidr\": \"10.0.38.1\"}, {\"cidr\": \"10.0.39.1\"}, {\"cidr\": \"10.0.40.1\"}, {\"cidr\": \"10.0.41.1\"}, {\"cidr\": \"10.0.42.1\"}, {\"cidr\": \"10.0.43.1\"}, {\"cidr\": \"10.0.44.1\"}, {\"cidr\": \"10.0.45.1\"}, {\"cidr\": \"10.0.46.1\"}, {\"cidr\": \"10.0.47.1\"}, {\"cidr\": \"10.0.48.1\"}, {\"cidr\": \"10.0.49.1\"}, {\"cidr\": \"10.0.50.1\"}, {\"cidr\": \"10.0.51.1\"}, {\"cidr\": \"10.0.52.1\"}, {\"cidr\": \"10.0.53.1\"}, {\"cidr\": \"10.0.54.1\"}, {\"cidr\": \"10.0.55.1\"}, {\"cidr\": \"10.0.56.1\"}, {\"cidr\": \"10.0.57.1\"}, {\"cidr\": \"10.0.58.1\"}, {\"cidr\": \"10.0.59.1\"}, {\"cidr\": \"10.0.60.1\"}, {\"cidr\": \"10.0.61.1\"}, {\"cidr\": \"10.0.62.1\"}, {\"cidr\": \"10.0.63.1\"}, {\"cidr\": \"10.0.64.1\"}, {\"cidr\": \"10.0.65.1\"}, {\"cidr\": \"10.0.66.1\"}, {\"cidr\": \"10.0.67.1\"}, {\"cidr\": \"10.0.68.1\"}, {\"cidr\": \"10.0.69.1\"}, {\"cidr\": \"10.0.70.1\"}, {\"cidr\": \"10.0.71.1\"}, {\"cidr\": \"10.0.72.1\"}, {\"cidr\": \"10.0.73.1\"}, {\"cidr\": \"10.0.74.1\"}, {\"cidr\": \"10.0.75.1\"}, {\"cidr\": \"10.0.76.1\"}, {\"cidr\": \"10.0.77.1\"}, {\"cidr\": \"10.0.78.1\"}, {\"cidr\": \"10.0.79.1\"}, {\"cidr\": \"10.0.80.1\"}, {\"cidr\": \"10.0.81.1\"}, {\"cidr\": \"10.0.82.1\"}, {\"cidr\": \"10.0.83.1\"}, {\"cidr\": \"10.0.84.1\"}, {\"cidr\": \"10.0.85.1\"}, {\"cidr\": \"10.0.86.1\"}, {\"cidr\": \"10.0.87.1\"}, {\"cidr\": \"10.0.88.1\"}, {\"cidr\": \"10.0.89.1\"}, {\"cidr\": \"10.0.90.1\"}, {\"cidr\": \"10.0.91.1\"}, {\"cidr\": \"10.0.92.1\"}, {\"cidr\": \"10.0.93.1\"}, {\"cidr\": \"10.0.94.1\"}, {\"cidr\": \"10.0.95.1\"}, {\"cidr\": \"10.0.96.1\"}, {\"cidr\": \"10.0.97.1\"}, {\"cidr\": \"10.0.98.1\"}, {\"cidr\": \"10.0.99.1\"}, {\"cidr\": \"10.0.100.1\"}, {\"cidr\": \"10.0.101.1\"}, {\"cidr\": \"10.0.102.1\"}, {\"cidr\": \"10.0.103.1\"}, {\"cidr\": \"10.0.104.1\"}, {\"cidr\": \"10.0.105.1\"}, {\"cidr\": \"10.0.106.1\"}, {\"cidr\": \"10.0.107.1\"}, {\"cidr\": \"10.0.108.1\"}, {\"cidr\": \"10.0.109.1\"}, {\"cidr\": \"10.0.110.1\"}, {\"cidr\": \"10.0.111.1\"}, {\"cidr\": \"10.0.112.1\"}, {\"cidr\": \"10.0.113.1\"}, {\"cidr\": \"10.0.114.1\"}, {\"cidr\": \"10.0.115.1\"}, {\"cidr\": \"10.0.116.1\"}, {\"cidr\": \"10.0.117.1\"}, {\"cidr\": \"10.0.118.1\"}, {\"cidr\": \"10.0.119.1\"}, {\"cidr\": \"10.0.120.1\"}, {\"cidr\": \"10.0.121.1\"}, {\"cidr\": \"10.0.122.1\"}, {\"cidr\": \"10.0.123.1\"}, {\"cidr\": \"10.0.124.1\"}, {\"cidr\": \"10.0.125.1\"}, {\"cidr\": \"10.0.126.1\"}, {\"cidr\": \"10.0.127.1\"}, {\"cidr\": \"10.0.128.1\"}, {\"cidr\": \"10.0.129.1\"}, {\"cidr\": \"10.0.130.1\"}, {\"cidr\": \"10.0.131.1\"}, {\"cidr\": \"10.0.132.1\"}, {\"cidr\": \"10.0.133.1\"}, {\"cidr\": \"10.0.134.1\"}, {\"cidr\": \"10.0.135.1\"}, {\"cidr\": \"10.0.136.1\"}, {\"cidr\": \"10.0.137.1\"}, {\"cidr\": \"10.0.138.1\"}, {\"cidr\": \"10.0.139.1\"}, {\"cidr\": \"10.0.140.1\"}, {\"cidr\": \"10.0.141.1\"}, {\"cidr\": \"10.0.142.1\"}, {\"cidr\": \"10.0.143.1\"}, {\"cidr\": \"10.0.144.1\"}, {\"cidr\": \"10.0.145.1\"}, {\"cidr\": \"10.0.146.1\"}, {\"cidr\": \"10.0.147.1\"}, {\"cidr\": \"10.0.148.1\"}, {\"cidr\": \"10.0.149.1\"}, {\"cidr\": \"10.0.150.1\"}, {\"cidr\": \"10.0.151.1\"}, {\"cidr\": \"10.0.152.1\"}, {\"cidr\": \"10.0.153.1\"}, {\"cidr\": \"10.0.154.1\"}, {\"cidr\": \"10.0.155.1\"}, {\"cidr\": \"10.0.156.1\"}, {\"cidr\": \"10.0.157.1\"}, {\"cidr\": \"10.0.158.1\"}, {\"cidr\": \"10.0.159.1\"}, {\"cidr\": \"10.0.160.1\"}, {\"cidr\": \"10.0.161.1\"}, {\"cidr\": \"10.0.162.1\"}, {\"cidr\": \"10.0.163.1\"}, {\"cidr\": \"10.0.164.1\"}, {\"cidr\": \"10.0.165.1\"}, {\"cidr\": \"10.0.166.1\"}, {\"cidr\": \"10.0.167.1\"}, {\"cidr\": \"10.0.168.1\"}, {\"cidr\": \"10.0.169.1\"}, {\"cidr\": \"10.0.170.1\"}, {\"cidr\": \"10.0.171.1\"}, {\"cidr\": \"10.0.172.1\"}, {\"cidr\": \"10.0.173.1\"}, {\"cidr\": \"10.0.174.1\"}, {\"cidr\": \"10.0.175.1\"}, {\"cidr\": \"10.0.176.1\"}, {\"cidr\": \"10.0.177.1\"}, {\"cidr\": \"10.0.178.1\"}, {\"cidr\": \"10.0.179.1\"}, {\"cidr\": \"10.0.180.1\"}, {\"cidr\": \"10.0.181.1\"}, {\"cidr\": \"10.0.182.1\"}, {\"cidr\": \"10.0.183.1\"}, {\"cidr\": \"10.0.184.1\"}, {\"cidr\": \"10.0.185.1\"}, {\"cidr\": \"10.0.186.1\"}, {\"cidr\": \"10.0.187.1\"}, {\"cidr\": \"10.0.188.1\"}, {\"cidr\": \"10.0.189.1\"}, {\"cidr\": \"10.0.190.1\"}, {\"cidr\": \"10.0.191.1\"}, {\"cidr\": \"10.0.192.1\"}, {\"cidr\": \"10.0.193.1\"}, {\"cidr\": \"10.0.194.1\"}, {\"cidr\": \"10.0.195.1\"}, {\"cidr\": \"10.0.196.1\"}, {\"cidr\": \"10.0.197.1\"}, {\"cidr\": \"10.0.198.1\"}, {\"cidr\": \"10.0.199.1\"}, {\"cidr\": \"10.0.200.1\"}, {\"cidr\": \"10.0.201.1\"}, {\"cidr\": \"10.0.202.1\"}, {\"cidr\": \"10.0.203.1\"}, {\"cidr\": \"10.0.204.1\"}, {\"cidr\": \"10.0.205.1\"}, {\"cidr\": \"10.0.206.1\"}, {\"cidr\": \"10.0.207.1\"}, {\"cidr\": \"10.0.208.1\"}, {\"cidr\": \"10.0.209.1\"}, {\"cidr\": \"10.0.210.1\"}, {\"cidr\": \"10.0.211.1\"}, {\"cidr\": \"10.0.212.1\"}, {\"cidr\": \"10.0.213.1\"}, {\"cidr\": \"10.0.214.1\"}, {\"cidr\": \"10.0.215.1\"}, {\"cidr\": \"10.0.216.1\"}, {\"cidr\": \"10.0.217.1\"}, {\"cidr\": \"10.0.218.1\"}, {\"cidr\": \"10.0.219.1\"}, {\"cidr\": \"10.0.220.1\"}, {\"cidr\": \"10.0.221.1\"}, {\"cidr\": \"10.0.222.1\"}, {\"cidr\": \"10.0.223.1\"}, {\"cidr\": \"10.0.224.1\"}, {\"cidr\": \"10.0.225.1\"}, {\"cidr\": \"10.0.226.1\"}, {\"cidr\": \"10.0.227.1\"}, {\"cidr\": \"10.0.228.1\"}, {\"cidr\": \"10.0.229.1\"}, {\"cidr\": \"10.0.230.1\"}, {\"cidr\": \"10.0.231.1\"}, {\"cidr\": \"10.0.232.1\"}, {\"cidr\": \"10.0.233.1\"}, {\"cidr\": \"10.0.234.1\"}, {\"cidr\": \"10.0.235.1\"}, {\"cidr\": \"10.0.236.1\"}, {\"cidr\": \"10.0.237.1\"}, {\"cidr\": \"10.0.238.1\"}, {\"cidr\": \"10.0.239.1\"}, {\"cidr\": \"10.0.240.1\"}, {\"cidr\": \"10.0.241.1\"}, {\"cidr\": \"10.0.242.1\"}, {\"cidr\": \"10.0.243.1\"}, {\"cidr\": \"10.0.244.1\"}, {\"cidr\": \"10.0.245.1\"}, {\"cidr\": \"10.0.246.1\"}, {\"cidr\": \"10.0.247.1\"}, {\"cidr\": \"10.0.248.1\"}, {\"cidr\": \"10.0.249.1\"}, {\"cidr\": \"10.0.250.1\"}, {\"cidr\": \"10.0.251.1\"}, {\"cidr\": \"10.0.252.1\"}, {\"cidr\": \"10.0.253.1\"}, {\"cidr\": \"10.0.254.1\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"udp\"}, {\"cidr\": \"198.51.100.0/24\", \"protocol\": \"udp\"}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"limit: icmp for every port over the limit","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.1\"}, {\"cidr\": \"10.0.1.1\"}, {\"cidr\": \"10.0.2.1\"}, {\"cidr\": \"10.0.3.1\"}, {\"cidr\": \"10.0.4.1\"}, {\"cidr\": \"10.0.5.1\"}, {\"cidr\": \"10.0.6.1\"}, {\"cidr\": \"10.0.7.1\"}, {\"cidr\": \"10.0.8.1\"}, {\"cidr\": \"10.0.9.1\"}, {\"cidr\": \"10.0.10.1\"}, {\"cidr\": \"10.0.11.1\"}, {\"cidr\": \"10.0.12.1\"}, {\"cidr\": \"10.0.13.1\"}, {\"cidr\": \"10.0.14.1\"}, {\"cidr\": \"10.0.15.1\"}, {\"cidr\": \"10.0.16.1\"}, {\"cidr\": \"10.0.17.1\"}, {\"cidr\": \"10.0.18.1\"}, {\"cidr\": \"10.0.19.1\"}, {\"cidr\": \"10.0.20.1\"}, {\"cidr\": \"10.0.21.1\"}, {\"cidr\": \"10.0.22.1\"}, {\"cidr\": \"10.0.23.1\"}, {\"cidr\": \"10.0.24.1\"}, {\"cidr\": \"10.0.25.1\"}, {\"cidr\": \"10.0.26.1\"}, {\"cidr\": \"10.0.27.1\"}, {\"cidr\": \"10.0.28.1\"}, {\"cidr\": \"10.0.29.1\"}, {\"cidr\": \"10.0.30.1\"}, {\"cidr\": \"10.0.31.1\"}, {\"cidr\": \"10.0.32.1\"}, {\"cidr\": \"10.0.33.1\"}, {\"cidr\": \"10.0.34.1\"}, {\"cidr\": \"10.0.35.1\"}, {\"cidr\": \"10.0.36.1\"}, {\"cidr\": \"10.0.37.1\"}, {\"cidr\": \"10.0.38.1\"}, {\"cidr\": \"10.0.39.1\"}, {\"cidr\": \"10.0.40.1\"}, {\"cidr\": \"10.0.41.1\"}, {\"cidr\": \"10.0.42.1\"}, {\"cidr\": \"10.0.43.1\"}, {\"cidr\": \"10.0.44.1\"}, {\"cidr\": \"10.0.45.1\"}, {\"cidr\": \"10.0.46.1\"}, {\"cidr\": \"10.0.47.1\"}, {\"cidr\": \"10.0.48.1\"}, {\"cidr\": \"10.0.49.1\"}, {\"cidr\": \"10.0.50.1\"}, {\"cidr\": \"10.0.51.1\"}, {\"cidr\": \"10.0.52.1\"}, {\"cidr\": \"10.0.53.1\"}, {\"cidr\": \"10.0.54.1\"}, {\"cidr\": \"10.0.55.1\"}, {\"cidr\": \"10.0.56.1\"}, {\"cidr\": \"10.0.57.1\"}, {\"cidr\": \"10.0.58.1\"}, {\"cidr\": \"10.0.59.1\"}, {\"cidr\": \"10.0.60.1\"}, {\"cidr\": \"10.0.61.1\"}, {\"cidr\": \"10.0.62.1\"}, {\"cidr\": \"10.0.63.1\"}, {\"cidr\": \"10.0.64.1\"}, {\"cidr\": \"10.0.65.1\"}, {\"cidr\": \"10.0.66.1\"}, {\"cidr\": \"10.0.67.1\"}, {\"cidr\": \"10.0.68.1\"}, {\"cidr\": \"10.0.69.1\"}, {\"cidr\": \"10.0.70.1\"}, {\"cidr\": \"10.0.71.1\"}, {\"cidr\": \"10.0.72.1\"}, {\"cidr\": \"10.0.73.1\"}, {\"cidr\": \"10.0.74.1\"}, {\"cidr\": \"10.0.75.1\"}, {\"cidr\": \"10.0.76.1\"}, {\"cidr\": \"10.0.77.1\"}, {\"cidr\": \"10.0.78.1\"}, {\"cidr\": \"10.0.79.1\"}, {\"cidr\": \"10.0.80.1\"}, {\"cidr\": \"10.0.81.1\"}, {\"cidr\": \"10.0.82.1\"}, {\"cidr\": \"10.0.83.1\"}, {\"cidr\": \"10.0.84.1\"}, {\"cidr\": \"10.0.85.1\"}, {\"cidr\": \"10.0.86.1\"}, {\"cidr\": \"10.0.87.1\"}, {\"cidr\": \"10.0.88.1\"}, {\"cidr\": \"10.0.89.1\"}, {\"cidr\": \"10.0.90.1\"}, {\"cidr\": \"10.0.91.1\"}, {\"cidr\": \"10.0.92.1\"}, {\"cidr\": \"10.0.93.1\"}, {\"cidr\": \"10.0.94.1\"}, {\"cidr\": \"10.0.95.1\"}, {\"cidr\": \"10.0.96.1\"}, {\"cidr\": \"10.0.97.1\"}, {\"cidr\": \"10.0.98.1\"}, {\"cidr\": \"10.0.99.1\"}, {\"cidr\": \"10.0.100.1\"}, {\"cidr\": \"10.0.101.1\"}, {\"cidr\": \"10.0.102.1\"}, {\"cidr\": \"10.0.103.1\"}, {\"cidr\": \"10.0.104.1\"}, {\"cidr\": \"10.0.105.1\"}, {\"cidr\": \"10.0.106.1\"}, {\"cidr\": \"10.0.107.1\"}, {\"cidr\": \"10.0.108.1\"}, {\"cidr\": \"10.0.109.1\"}, {\"cidr\": \"10.0.110.1\"}, {\"cidr\": \"10.0.111.1\"}, {\"cidr\": \"10.0.112.1\"}, {\"cidr\": \"10.0.113.1\"}, {\"cidr\": \"10.0.114.1\"}, {\"cidr\": \"10.0.115.1\"}, {\"cidr\": \"10.0.116.1\"}, {\"cidr\": \"10.0.117.1\"}, {\"cidr\": \"10.0.118.1\"}, {\"cidr\": \"10.0.119.1\"}, {\"cidr\": \"10.0.120.1\"}, {\"cidr\": \"10.0.121.1\"}, {\"cidr\": \"10.0.122.1\"}, {\"cidr\": \"10.0.123.1\"}, {\"cidr\": \"10.0.124.1\"}, {\"cidr\": \"10.0.125.1\"}, {\"cidr\": \"10.0.126.1\"}, {\"cidr\": \"10.0.127.1\"}, {\"cidr\": \"10.0.128.1\"}, {\"cidr\": \"10.0.129.1\"}, {\"cidr\": \"10.0.130.1\"}, {\"cidr\": \"10.0.131.1\"}, {\"cidr\": \"10.0.132.1\"}, {\"cidr\": \"10.0.133.1\"}, {\"cidr\": \"10.0.134.1\"}, {\"cidr\": \"10.0.135.1\"}, {\"cidr\": \"10.0.136.1\"}, {\"cidr\": \"10.0.137.1\"}, {\"cidr\": \"10.0.138.1\"}, {\"cidr\": \"10.0.139.1\"}, {\"cidr\": \"10.0.140.1\"}, {\"cidr\": \"10.0.141.1\"}, {\"cidr\": \"10.0.142.1\"}, {\"cidr\": \"10.0.143.1\"}, {\"cidr\": \"10.0.144.1\"}, {\"cidr\": \"10.0.145.1\"}, {\"cidr\": \"10.0.146.1\"}, {\"cidr\": \"10.0.147.1\"}, {\"cidr\": \"10.0.148.1\"}, {\"cidr\": \"10.0.149.1\"}, {\"cidr\": \"10.0.150.1\"}, {\"cidr\": \"10.0.151.1\"}, {\"cidr\": \"10.0.152.1\"}, {\"cidr\": \"10.0.153.1\"}, {\"cidr\": \"10.0.154.1\"}, {\"cidr\": \"10.0.155.1\"}, {\"cidr\": \"10.0.156.1\"}, {\"cidr\": \"10.0.157.1\"}, {\"cidr\": \"10.0.158.1\"}, {\"cidr\": \"10.0.159.1\"}, {\"cidr\": \"10.0.160.1\"}, {\"cidr\": \"10.0.161.1\"}, {\"cidr\": \"10.0.162.1\"}, {\"cidr\": \"10.0.163.1\"}, {\"cidr\": \"10.0.164.1\"}, {\"cidr\": \"10.0.165.1\"}, {\"cidr\": \"10.0.166.1\"}, {\"cidr\": \"10.0.167.1\"}, {\"cidr\": \"10.0.168.1\"}, {\"cidr\": \"10.0.169.1\"}, {\"cidr\": \"10.0.170.1\"}, {\"cidr\": \"10.0.171.1\"}, {\"cidr\": \"10.0.172.1\"}, {\"cidr\": \"10.0.173.1\"}, {\"cidr\": \"10.0.174.1\"}, {\"cidr\": \"10.0.175.1\"}, {\"cidr\": \"10.0.176.1\"}, {\"cidr\": \"10.0.177.1\"}, {\"cidr\": \"10.0.178.1\"}, {\"cidr\": \"10.0.179.1\"}, {\"cidr\": \"10.0.180.1\"}, {\"cidr\": \"10.0.181.1\"}, {\"cidr\": \"10.0.182.1\"}, {\"cidr\": \"10.0.183.1\"}, {\"cidr\": \"10.0.184.1\"}, {\"cidr\": \"10.0.185.1\"}, {\"cidr\": \"10.0.186.1\"}, {\"cidr\": \"10.0.187.1\"}, {\"cidr\": \"10.0.188.1\"}, {\"cidr\": \"10.0.189.1\"}, {\"cidr\": \"10.0.190.1\"}, {\"cidr\": \"10.0.191.1\"}, {\"cidr\": \"10.0.192.1\"}, {\"cidr\": \"10.0.193.1\"}, {\"cidr\": \"10.0.194.1\"}, {\"cidr\": \"10.0.195.1\"}, {\"cidr\": \"10.0.196.1\"}, {\"cidr\": \"10.0.197.1\"}, {\"cidr\": \"10.0.198.1\"}, {\"cidr\": \"10.0.199.1\"}, {\"cidr\": \"10.0.200.1\"}, {\"cidr\": \"10.0.201.1\"}, {\"cidr\": \"10.0.202.1\"}, {\"cidr\": \"10.0.203.1\"}, {\"cidr\": \"10.0.204.1\"}, {\"cidr\": \"10.0.205.1\"}, {\"cidr\": \"10.0.206.1\"}, {\"cidr\": \"10.0.207.1\"}, {\"cidr\": \"10.0.208.1\"}, {\"cidr\": \"10.0.209.1\"}, {\"cidr\": \"10.0.210.1\"}, {\"cidr\": \"10.0.211.1\"}, {\"cidr\": \"10.0.212.1\"}, {\"cidr\": \"10.0.213.1\"}, {\"cidr\": \"10.0.214.1\"}, {\"cidr\": \"10.0.215.1\"}, {\"cidr\": \"10.0.216.1\"}, {\"cidr\": \"10.0.217.1\"}, {\"cidr\": \"10.0.218.1\"}, {\"cidr\": \"10.0.219.1\"}, {\"cidr\": \"10.0.220.1\"}, {\"cidr\": \"10.0.221.1\"}, {\"cidr\": \"10.0.222.1\"}, {\"cidr\": \"10.0.223.1\"}, {\"cidr\": \"10.0.224.1\"}, {\"cidr\": \"10.0.225.1\"}, {\"cidr\": \"10.0.226.1\"}, {\"cidr\": \"10.0.227.1\"}, {\"cidr\": \"10.0.228.1\"}, {\"cidr\": \"10.0.229.1\"}, {\"cidr\": \"10.0.230.1\"}, {\"cidr\": \"10.0.231.1\"}, {\"cidr\": \"10.0.232.1\"}, {\"cidr\": \"10.0.233.1\"}, {\"cidr\": \"10.0.234.1\"}, {\"cidr\": \"10.0.235.1\"}, {\"cidr\": \"10.0.236.1\"}, {\"cidr\": \"10.0.237.1\"}, {\"cidr\": \"10.0.238.1\"}, {\"cidr\": \"10.0.239.1\"}, {\"cidr\": \"10.0.240.1\"}, {\"cidr\": \"10.0.241.1\"}, {\"cidr\": \"10.0.242.1\"}, {\"cidr\": \"10.0.243.1\"}, {\"cidr\": \"10.0.244.1\"}, {\"cidr\": \"10.0.245.1\"}, {\"cidr\": \"10.0.246.1\"}, {\"cidr\": \"10.0.247.1\"}, {\"cidr\": \"10.0.248.1\"}, {\"cidr\": \"10.0.249.1\"}, {\"cidr\": \"10.0.250.1\"}, {\"cidr\": \"10.0.251.1\"}, {\"cidr\": \"10.0.252.1\"}, {\"cidr\": \"10.0.253.1\"}, {\"cidr\": \"10.0.254.1\"}, {\"cidr\": \"192.0.2.0/24\", \"protocol\": \"icmp\"}, {\"cidr\": \"198.51.100.0/24\", \"protocol\": \"icmp\"}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"limit: 256 separate ports","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"port\": 1, \"protocol\": \"udp\"}, {\"port\": 3, \"protocol\": \"udp\"}, {\"port\": 5, \"protocol\": \"udp\"}, {\"port\": 7, \"protocol\": \"udp\"}, {\"port\": 9, \"protocol\": \"udp\"}, {\"port\": 11, \"protocol\": \"udp\"}, {\"port\": 13, \"protocol\": \"udp\"}, {\"port\": 15, \"protocol\": \"udp\"}, {\"port\": 17, \"protocol\": \"udp\"}, {\"port\": 19, \"protocol\": \"udp\"}, {\"port\": 21, \"protocol\": \"udp\"}, {\"port\": 23, \"protocol\": \"udp\"}, {\"port\": 25, \"protocol\": \"udp\"}, {\"port\": 27, \"protocol\": \"udp\"}, {\"port\": 29, \"protocol\": \"udp\"}, {\"port\": 31, \"protocol\": \"udp\"}, {\"port\": 33, \"protocol\": \"udp\"}, {\"port\": 35, \"protocol\": \"udp\"}, {\"port\": 37, \"protocol\": \"udp\"}, {\"port\": 39, \"protocol\": \"udp\"}, {\"port\": 41, \"protocol\": \"udp\"}, {\"port\": 43, \"protocol\": \"udp\"}, {\"port\": 45, \"protocol\": \"udp\"}, {\"port\": 47, \"protocol\": \"udp\"}, {\"port\": 49, \"protocol\": \"udp\"}, {\"port\": 51, \"protocol\": \"udp\"}, {\"port\": 53, \"protocol\": \"udp\"}, {\"port\": 55, \"protocol\": \"udp\"}, {\"port\": 57, \"protocol\": \"udp\"}, {\"port\": 59, \"protocol\": \"udp\"}, {\"port\": 61, \"protocol\": \"udp\"}, {\"port\": 63, \"protocol\": \"udp\"}, {\"port\": 65, \"protocol\": \"udp\"}, {\"port\": 67, \"protocol\": \"udp\"}, {\"port\": 69, \"protocol\": \"udp\"}, {\"port\": 71, \"protocol\": \"udp\"}, {\"port\": 73, \"protocol\": \"udp\"}, {\"port\": 75, \"protocol\": \"udp\"}, {\"port\": 77, \"protocol\": \"udp\"}, {\"port\": 79, \"protocol\": \"udp\"}, {\"port\": 81, \"protocol\": \"udp\"}, {\"port\": 83, \"protocol\": \"udp\"}, {\"port\": 85, \"protocol\": \"udp\"}, {\"port\": 87, \"protocol\": \"udp\"}, {\"port\": 89, \"protocol\": \"udp\"}, {\"port\": 91, \"protocol\": \"udp\"}, {\"port\": 93, \"protocol\": \"udp\"}, {\"port\": 95, \"protocol\": \"udp\"}, {\"port\": 97, \"protocol\": \"udp\"}, {\"port\": 99, \"protocol\": \"udp\"}, {\"port\": 101, \"protocol\": \"udp\"}, {\"port\": 103, \"protocol\": \"udp\"}, {\"port\": 105, \"protocol\": \"udp\"}, {\"port\": 107, \"protocol\": \"udp\"}, {\"port\": 109, \"protocol\": \"udp\"}, {\"port\": 111, \"protocol\": \"udp\"}, {\"port\": 113, \"protocol\": \"udp\"}, {\"port\": 115, \"protocol\": \"udp\"}, {\"port\": 117, \"protocol\": \"udp\"}, {\"port\": 119, \"protocol\": \"udp\"}, {\"port\": 121, \"protocol\": \"udp\"}, {\"port\": 123, \"protocol\": \"udp\"}, {\"port\": 125, \"protocol\": \"udp\"}, {\"port\": 127, \"protocol\": \"udp\"}, {\"port\": 129, \"protocol\": \"udp\"}, {\"port\": 131, \"protocol\": \"udp\"}, {\"port\": 133, \"protocol\": \"udp\"}, {\"port\": 135, \"protocol\": \"udp\"}, {\"port\": 137, \"protocol\": \"udp\"}, {\"port\": 139, \"protocol\": \"udp\"}, {\"port\": 141, \"protocol\": \"udp\"}, {\"port\": 143, \"protocol\": \"udp\"}, {\"port\": 145, \"protocol\": \"udp\"}, {\"port\": 147, \"protocol\": \"udp\"}, {\"port\": 149, \"protocol\": \"udp\"}, {\"port\": 151, \"protocol\": \"udp\"}, {\"port\": 153, \"protocol\": \"udp\"}, {\"port\": 155, \"protocol\": \"udp\"}, {\"port\": 157, \"protocol\": \"udp\"}, {\"port\": 159, \"protocol\": \"udp\"}, {\"port\": 161, \"protocol\": \"udp\"}, {\"port\": 163, \"protocol\": \"udp\"}, {\"port\": 165, \"protocol\": \"udp\"}, {\"port\": 167, \"protocol\": \"udp\"}, {\"port\": 169, \"protocol\": \"udp\"}, {\"port\": 171, \"protocol\": \"udp\"}, {\"port\": 173, \"protocol\": \"udp\"}, {\"port\": 175, \"protocol\": \"udp\"}, {\"port\": 177, \"protocol\": \"udp\"}, {\"port\": 179, \"protocol\": \"udp\"}, {\"port\": 181, \"protocol\": \"udp\"}, {\"port\": 183, \"protocol\": \"udp\"}, {\"port\": 185, \"protocol\": \"udp\"}, {\"port\": 187, \"protocol\": \"udp\"}, {\"port\": 189, \"protocol\": \"udp\"}, {\"port\": 191, \"protocol\": \"udp\"}, {\"port\": 193, \"protocol\": \"udp\"}, {\"port\": 195, \"protocol\": \"udp\"}, {\"port\": 197, \"protocol\": \"udp\"}, {\"port\": 199, \"protocol\": \"udp\"}, {\"port\": 201, \"protocol\": \"udp\"}, {\"port\": 203, \"protocol\": \"udp\"}, {\"port\": 205, \"protocol\": \"udp\"}, {\"port\": 207, \"protocol\": \"udp\"}, {\"port\": 209, \"protocol\": \"udp\"}, {\"port\": 211, \"protocol\": \"udp\"}, {\"port\": 213, \"protocol\": \"udp\"}, {\"port\": 215, \"protocol\": \"udp\"}, {\"port\": 217, \"protocol\": \"udp\"}, {\"port\": 219, \"protocol\": \"udp\"}, {\"port\": 221, \"protocol\": \"udp\"}, {\"port\": 223, \"protocol\": \"udp\"}, {\"port\": 225, \"protocol\": \"udp\"}, {\"port\": 227, \"protocol\": \"udp\"}, {\"port\": 229, \"protocol\": \"udp\"}, {\"port\": 231, \"protocol\": \"udp\"}, {\"port\": 233, \"protocol\": \"udp\"}, {\"port\": 235, \"protocol\": \"udp\"}, {\"port\": 237, \"protocol\": \"udp\"}, {\"port\": 239, \"protocol\": \"udp\"}, {\"port\": 241, \"protocol\": \"udp\"}, {\"port\": 243, \"protocol\": \"udp\"}, {\"port\": 245, \"protocol\": \"udp\"}, {\"port\": 247, \"protocol\": \"udp\"}, {\"port\": 249, \"protocol\": \"udp\"}, {\"port\": 251, \"protocol\": \"udp\"}, {\"port\": 253, \"protocol\": \"udp\"}, {\"port\": 255, \"protocol\": \"udp\"}, {\"port\": 257, \"protocol\": \"udp\"}, {\"port\": 259, \"protocol\": \"udp\"}, {\"port\": 261, \"protocol\": \"udp\"}, {\"port\": 263, \"protocol\": \"udp\"}, {\"port\": 265, \"protocol\": \"udp\"}, {\"port\": 267, \"protocol\": \"udp\"}, {\"port\": 269, \"protocol\": \"udp\"}, {\"port\": 271, \"protocol\": \"udp\"}, {\"port\": 273, \"protocol\": \"udp\"}, {\"port\": 275, \"protocol\": \"udp\"}, {\"port\": 277, \"protocol\": \"udp\"}, {\"port\": 279, \"protocol\": \"udp\"}, {\"port\": 281, \"protocol\": \"udp\"}, {\"port\": 283, \"protocol\": \"udp\"}, {\"port\": 285, \"protocol\": \"udp\"}, {\"port\": 287, \"protocol\": \"udp\"}, {\"port\": 289, \"protocol\": \"udp\"}, {\"port\": 291, \"protocol\": \"udp\"}, {\"port\": 293, \"protocol\": \"udp\"}, {\"port\": 295, \"protocol\": \"udp\"}, {\"port\": 297, \"protocol\": \"udp\"}, {\"port\": 299, \"protocol\": \"udp\"}, {\"port\": 301, \"protocol\": \"udp\"}, {\"port\": 303, \"protocol\": \"udp\"}, {\"port\": 305, \"protocol\": \"udp\"}, {\"port\": 307, \"protocol\": \"udp\"}, {\"port\": 309, \"protocol\": \"udp\"}, {\"port\": 311, \"protocol\": \"udp\"}, {\"port\": 313, \"protocol\": \"udp\"}, {\"port\": 315, \"protocol\": \"udp\"}, {\"port\": 317, \"protocol\": \"udp\"}, {\"port\": 319, \"protocol\": \"udp\"}, {\"port\": 321, \"protocol\": \"udp\"}, {\"port\": 323, \"protocol\": \"udp\"}, {\"port\": 325, \"protocol\": \"udp\"}, {\"port\": 327, \"protocol\": \"udp\"}, {\"port\": 329, \"protocol\": \"udp\"}, {\"port\": 331, \"protocol\": \"udp\"}, {\"port\": 333, \"protocol\": \"udp\"}, {\"port\": 335, \"protocol\": \"udp\"}, {\"port\": 337, \"protocol\": \"udp\"}, {\"port\": 339, \"protocol\": \"udp\"}, {\"port\": 341, \"protocol\": \"udp\"}, {\"port\": 343, \"protocol\": \"udp\"}, {\"port\": 345, \"protocol\": \"udp\"}, {\"port\": 347, \"protocol\": \"udp\"}, {\"port\": 349, \"protocol\": \"udp\"}, {\"port\": 351, \"protocol\": \"udp\"}, {\"port\": 353, \"protocol\": \"udp\"}, {\"port\": 355, \"protocol\": \"udp\"}, {\"port\": 357, \"protocol\": \"udp\"}, {\"port\": 359, \"protocol\": \"udp\"}, {\"port\": 361, \"protocol\": \"udp\"}, {\"port\": 363, \"protocol\": \"udp\"}, {\"port\": 365, \"protocol\": \"udp\"}, {\"port\": 367, \"protocol\": \"udp\"}, {\"port\": 369, \"protocol\": \"udp\"}, {\"port\": 371, \"protocol\": \"udp\"}, {\"port\": 373, \"protocol\": \"udp\"}, {\"port\": 375, \"protocol\": \"udp\"}, {\"port\": 377, \"protocol\": \"udp\"}, {\"port\": 379, \"protocol\": \"udp\"}, {\"port\": 381, \"protocol\": \"udp\"}, {\"port\": 383, \"protocol\": \"udp\"}, {\"port\": 385, \"protocol\": \"udp\"}, {\"port\": 387, \"protocol\": \"udp\"}, {\"port\": 389, \"protocol\": \"udp\"}, {\"port\": 391, \"protocol\": \"udp\"}, {\"port\": 393, \"protocol\": \"udp\"}, {\"port\": 395, \"protocol\": \"udp\"}, {\"port\": 397, \"protocol\": \"udp\"}, {\"port\": 399, \"protocol\": \"udp\"}, {\"port\": 401, \"protocol\": \"udp\"}, {\"port\": 403, \"protocol\": \"udp\"}, {\"port\": 405, \"protocol\": \"udp\"}, {\"port\": 407, \"protocol\": \"udp\"}, {\"port\": 409, \"protocol\": \"udp\"}, {\"port\": 411, \"protocol\": \"udp\"}, {\"port\": 413, \"protocol\": \"udp\"}, {\"port\": 415, \"protocol\": \"udp\"}, {\"port\": 417, \"protocol\": \"udp\"}, {\"port\": 419, \"protocol\": \"udp\"}, {\"port\": 421, \"protocol\": \"udp\"}, {\"port\": 423, \"protocol\": \"udp\"}, {\"port\": 425, \"protocol\": \"udp\"}, {\"port\": 427, \"protocol\": \"udp\"}, {\"port\": 429, \"protocol\": \"udp\"}, {\"port\": 431, \"protocol\": \"udp\"}, {\"port\": 433, \"protocol\": \"udp\"}, {\"port\": 435, \"protocol\": \"udp\"}, {\"port\": 437, \"protocol\": \"udp\"}, {\"port\": 439, \"protocol\": \"udp\"}, {\"port\": 441, \"protocol\": \"udp\"}, {\"port\": 443, \"protocol\": \"udp\"}, {\"port\": 445, \"protocol\": \"udp\"}, {\"port\": 447, \"protocol\": \"udp\"}, {\"port\": 449, \"protocol\": \"udp\"}, {\"port\": 451, \"protocol\": \"udp\"}, {\"port\": 453, \"protocol\": \"udp\"}, {\"port\": 455, \"protocol\": \"udp\"}, {\"port\": 457, \"protocol\": \"udp\"}, {\"port\": 459, \"protocol\": \"udp\"}, {\"port\": 461, \"protocol\": \"udp\"}, {\"port\": 463, \"protocol\": \"udp\"}, {\"port\": 465, \"protocol\": \"udp\"}, {\"port\": 467, \"protocol\": \"udp\"}, {\"port\": 469, \"protocol\": \"udp\"}, {\"port\": 471, \"protocol\": \"udp\"}, {\"port\": 473, \"protocol\": \"udp\"}, {\"port\": 475, \"protocol\": \"udp\"}, {\"port\": 477, \"protocol\": \"udp\"}, {\"port\": 479, \"protocol\": \"udp\"}, {\"port\": 481, \"protocol\": \"udp\"}, {\"port\": 483, \"protocol\": \"udp\"}, {\"port\": 485, \"protocol\": \"udp\"}, {\"port\": 487, \"protocol\": \"udp\"}, {\"port\": 489, \"protocol\": \"udp\"}, {\"port\": 491, \"protocol\": \"udp\"}, {\"port\": 493, \"protocol\": \"udp\"}, {\"port\": 495, \"protocol\": \"udp\"}, {\"port\": 497, \"protocol\": \"udp\"}, {\"port\": 499, \"protocol\": \"udp\"}, {\"port\": 501, \"protocol\": \"udp\"}, {\"port\": 503, \"protocol\": \"udp\"}, {\"port\": 505, \"protocol\": \"udp\"}, {\"port\": 507, \"protocol\": \"udp\"}, {\"port\": 509, \"protocol\": \"udp\"}, {\"port\": 511, \"protocol\": \"udp\"}]}]}"}],"expected":{"allow":["192.0.2.1/32:udp:1","192.0.2.1/32:udp:3","192.0.2.1/32:udp:5","192.0.2.1/32:udp:7","192.0.2.1/32:udp:9","192.0.2.1/32:udp:11","192.0.2.1/32:udp:13","192.0.2.1/32:udp:15","192.0.2.1/32:udp:17","192.0.2.1/32:udp:19","192.0.2.1/32:udp:21","192.0.2.1/32:udp:23","192.0.2.1/32:udp:25","192.0.2.1/32:udp:27","192.0.2.1/32:udp:29","192.0.2.1/32:udp:31","192.0.2.1/32:udp:33","192.0.2.1/32:udp:35","192.0.2.1/32:udp:37","192.0.2.1/32:udp:39","192.0.2.1/32:udp:41","192.0.2.1/32:udp:43","192.0.2.1/32:udp:45","192.0.2.1/32:udp:47","192.0.2.1/32:udp:49","192.0.2.1/32:udp:51","192.0.2.1/32:udp:53","192.0.2.1/32:udp:55","192.0.2.1/32:udp:57","192.0.2.1/32:udp:59","192.0.2.1/32:udp:61","192.0.2.1/32:udp:63","192.0.2.1/32:udp:65","192.0.2.1/32:udp:67","192.0.2.1/32:udp:69","192.0.2.1/32:udp:71","192.0.2.1/32:udp:73","192.0.2.1/32:udp:75","192.0.2.1/32:udp:77","192.0.2.1/32:udp:79","192.0.2.1/32:udp:81","192.0.2.1/32:udp:83","192.0.2.1/32:udp:85","192.0.2.1/32:udp:87","192.0.2.1/32:udp:89","192.0.2.1/32:udp:91","192.0.2.1/32:udp:93","192.0.2.1/32:udp:95","192.0.2.1/32:udp:97","192.0.2.1/32:udp:99","192.0.2.1/32:udp:101","192.0.2.1/32:udp:103","192.0.2.1/32:udp:105","192.0.2.1/32:udp:107","192.0.2.1/32:udp:109","192.0.2.1/32:udp:111","192.0.2.1/32:udp:113","192.0.2.1/32:udp:115","192.0.2.1/32:udp:117","192.0.2.1/32:udp:119","192.0.2.1/32:udp:121","192.0.2.1/32:udp:123","192.0.2.1/32:udp:125","192.0.2.1/32:udp:127","192.0.2.1/32:udp:129","192.0.2.1/32:udp:131","192.0.2.1/32:udp:133","192.0.2.1/32:udp:135","192.0.2.1/32:udp:137","192.0.2.1/32:udp:139","192.0.2.1/32:udp:141","192.0.2.1/32:udp:143","192.0.2.1/32:udp:145","192.0.2.1/32:udp:147","192.0.2.1/32:udp:149","192.0.2.1/32:udp:151","192.0.2.1/32:udp:153","192.0.2.1/32:udp:155","192.0.2.1/32:udp:157","192.0.2.1/32:udp:159","192.0.2.1/32:udp:161","192.0.2.1/32:udp:163","192.0.2.1/32:udp:165","192.0.2.1/32:udp:167","192.0.2.1/32:udp:169","192.0.2.1/32:udp:171","192.0.2.1/32:udp:173","192.0.2.1/32:udp:175","192.0.2.1/32:udp:177","192.0.2.1/32:udp:179","192.0.2.1/32:udp:181","192.0.2.1/32:udp:183","192.0.2.1/32:udp:185","192.0.2.1/32:udp:187","192.0.2.1/32:udp:189","192.0.2.1/32:udp:191","192.0.2.1/32:udp:193","192.0.2.1/32:udp:195","192.0.2.1/32:udp:197","192.0.2.1/32:udp:199","192.0.2.1/32:udp:201","192.0.2.1/32:udp:203","192.0.2.1/32:udp:205","192.0.2.1/32:udp:207","192.0.2.1/32:udp:209","192.0.2.1/32:udp:211","192.0.2.1/32:udp:213","192.0.2.1/32:udp:215","192.0.2.1/32:udp:217","192.0.2.1/32:udp:219","192.0.2.1/32:udp:221","192.0.2.1/32:udp:223","192.0.2.1/32:udp:225","192.0.2.1/32:udp:227","192.0.2.1/32:udp:229","192.0.2.1/32:udp:231","192.0.2.1/32:udp:233","192.0.2.1/32:udp:235","192.0.2.1/32:udp:237","192.0.2.1/32:udp:239","192.0.2.1/32:udp:241","192.0.2.1/32:udp:243","192.0.2.1/32:udp:245","192.0.2.1/32:udp:247","192.0.2.1/32:udp:249","192.0.2.1/32:udp:251","192.0.2.1/32:udp:253","192.0.2.1/32:udp:255","192.0.2.1/32:udp:257","192.0.2.1/32:udp:259","192.0.2.1/32:udp:261","192.0.2.1/32:udp:263","192.0.2.1/32:udp:265","192.0.2.1/32:udp:267","192.0.2.1/32:udp:269","192.0.2.1/32:udp:271","192.0.2.1/32:udp:273","192.0.2.1/32:udp:275","192.0.2.1/32:udp:277","192.0.2.1/32:udp:279","192.0.2.1/32:udp:281","192.0.2.1/32:udp:283","192.0.2.1/32:udp:285","192.0.2.1/32:udp:287","192.0.2.1/32:udp:289","192.0.2.1/32:udp:291","192.0.2.1/32:udp:293","192.0.2.1/32:udp:295","192.0.2.1/32:udp:297","192.0.2.1/32:udp:299","192.0.2.1/32:udp:301","192.0.2.1/32:udp:303","192.0.2.1/32:udp:305","192.0.2.1/32:udp:307","192.0.2.1/32:udp:309","192.0.2.1/32:udp:311","192.0.2.1/32:udp:313","192.0.2.1/32:udp:315","192.0.2.1/32:udp:317","192.0.2.1/32:udp:319","192.0.2.1/32:udp:321","192.0.2.1/32:udp:323","192.0.2.1/32:udp:325","192.0.2.1/32:udp:327","192.0.2.1/32:udp:329","192.0.2.1/32:udp:331","192.0.2.1/32:udp:333","192.0.2.1/32:udp:335","192.0.2.1/32:udp:337","192.0.2.1/32:udp:339","192.0.2.1/32:udp:341","192.0.2.1/32:udp:343","192.0.2.1/32:udp:345","192.0.2.1/32:udp:347","192.0.2.1/32:udp:349","192.0.2.1/32:udp:351","192.0.2.1/32:udp:353","192.0.2.1/32:udp:355","192.0.2.1/32:udp:357","192.0.2.1/32:udp:359","192.0.2.1/32:udp:361","192.0.2.1/32:udp:363","192.0.2.1/32:udp:365","192.0.2.1/32:udp:367","192.0.2.1/32:udp:369","192.0.2.1/32:udp:371","192.0.2.1/32:udp:373","192.0.2.1/32:udp:375","192.0.2.1/32:udp:377","192.0.2.1/32:udp:379","192.0.2.1/32:udp:381","192.0.2.1/32:udp:383","192.0.2.1/32:udp:385","192.0.2.1/32:udp:387","192.0.2.1/32:udp:389","192.0.2.1/32:udp:391","192.0.2.1/32:udp:393","192.0.2.1/32:udp:395","192.0.2.1/32:udp:397","192.0.2.1/32:udp:399","192.0.2.1/32:udp:401","192.0.2.1/32:udp:403","192.0.2.1/32:udp:405","192.0.2.1/32:udp:407","192.0.2.1/32:udp:409","192.0.2.1/32:udp:411","192.0.2.1/32:udp:413","192.0.2.1/32:udp:415","192.0.2.1/32:udp:417","192.0.2.1/32:udp:419","192.0.2.1/32:udp:421","192.0.2.1/32:udp:423","192.0.2.1/32:udp:425","192.0.2.1/32:udp:427","192.0.2.1/32:udp:429","192.0.2.1/32:udp:431","192.0.2.1/32:udp:433","192.0.2.1/32:udp:435","192.0.2.1/32:udp:437","192.0.2.1/32:udp:439","192.0.2.1/32:udp:441","192.0.2.1/32:udp:443","192.0.2.1/32:udp:445","192.0.2.1/32:udp:447","192.0.2.1/32:udp:449","192.0.2.1/32:udp:451","192.0.2.1/32:udp:453","192.0.2.1/32:udp:455","192.0.2.1/32:udp:457","192.0.2.1/32:udp:459","192.0.2.1/32:udp:461","192.0.2.1/32:udp:463","192.0.2.1/32:udp:465","192.0.2.1/32:udp:467","192.0.2.1/32:udp:469","192.0.2.1/32:udp:471","192.0.2.1/32:udp:473","192.0.2.1/32:udp:475","192.0.2.1/32:udp:477","192.0.2.1/32:udp:479","192.0.2.1/32:udp:481","192.0.2.1/32:udp:483","192.0.2.1/32:udp:485","192.0.2.1/32:udp:487","192.0.2.1/32:udp:489","192.0.2.1/32:udp:491","192.0.2.1/32:udp:493","192.0.2.1/32:udp:495","192.0.2.1/32:udp:497","192.0.2.1/32:udp:499","192.0.2.1/32:udp:501","192.0.2.1/32:udp:503","192.0.2.1/32:udp:505","192.0.2.1/32:udp:507","192.0.2.1/32:udp:509","192.0.2.1/32:udp:511"],"deny":[]}}, + {"name":"limit: 257 separate ports","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"port\": 1, \"protocol\": \"udp\"}, {\"port\": 3, \"protocol\": \"udp\"}, {\"port\": 5, \"protocol\": \"udp\"}, {\"port\": 7, \"protocol\": \"udp\"}, {\"port\": 9, \"protocol\": \"udp\"}, {\"port\": 11, \"protocol\": \"udp\"}, {\"port\": 13, \"protocol\": \"udp\"}, {\"port\": 15, \"protocol\": \"udp\"}, {\"port\": 17, \"protocol\": \"udp\"}, {\"port\": 19, \"protocol\": \"udp\"}, {\"port\": 21, \"protocol\": \"udp\"}, {\"port\": 23, \"protocol\": \"udp\"}, {\"port\": 25, \"protocol\": \"udp\"}, {\"port\": 27, \"protocol\": \"udp\"}, {\"port\": 29, \"protocol\": \"udp\"}, {\"port\": 31, \"protocol\": \"udp\"}, {\"port\": 33, \"protocol\": \"udp\"}, {\"port\": 35, \"protocol\": \"udp\"}, {\"port\": 37, \"protocol\": \"udp\"}, {\"port\": 39, \"protocol\": \"udp\"}, {\"port\": 41, \"protocol\": \"udp\"}, {\"port\": 43, \"protocol\": \"udp\"}, {\"port\": 45, \"protocol\": \"udp\"}, {\"port\": 47, \"protocol\": \"udp\"}, {\"port\": 49, \"protocol\": \"udp\"}, {\"port\": 51, \"protocol\": \"udp\"}, {\"port\": 53, \"protocol\": \"udp\"}, {\"port\": 55, \"protocol\": \"udp\"}, {\"port\": 57, \"protocol\": \"udp\"}, {\"port\": 59, \"protocol\": \"udp\"}, {\"port\": 61, \"protocol\": \"udp\"}, {\"port\": 63, \"protocol\": \"udp\"}, {\"port\": 65, \"protocol\": \"udp\"}, {\"port\": 67, \"protocol\": \"udp\"}, {\"port\": 69, \"protocol\": \"udp\"}, {\"port\": 71, \"protocol\": \"udp\"}, {\"port\": 73, \"protocol\": \"udp\"}, {\"port\": 75, \"protocol\": \"udp\"}, {\"port\": 77, \"protocol\": \"udp\"}, {\"port\": 79, \"protocol\": \"udp\"}, {\"port\": 81, \"protocol\": \"udp\"}, {\"port\": 83, \"protocol\": \"udp\"}, {\"port\": 85, \"protocol\": \"udp\"}, {\"port\": 87, \"protocol\": \"udp\"}, {\"port\": 89, \"protocol\": \"udp\"}, {\"port\": 91, \"protocol\": \"udp\"}, {\"port\": 93, \"protocol\": \"udp\"}, {\"port\": 95, \"protocol\": \"udp\"}, {\"port\": 97, \"protocol\": \"udp\"}, {\"port\": 99, \"protocol\": \"udp\"}, {\"port\": 101, \"protocol\": \"udp\"}, {\"port\": 103, \"protocol\": \"udp\"}, {\"port\": 105, \"protocol\": \"udp\"}, {\"port\": 107, \"protocol\": \"udp\"}, {\"port\": 109, \"protocol\": \"udp\"}, {\"port\": 111, \"protocol\": \"udp\"}, {\"port\": 113, \"protocol\": \"udp\"}, {\"port\": 115, \"protocol\": \"udp\"}, {\"port\": 117, \"protocol\": \"udp\"}, {\"port\": 119, \"protocol\": \"udp\"}, {\"port\": 121, \"protocol\": \"udp\"}, {\"port\": 123, \"protocol\": \"udp\"}, {\"port\": 125, \"protocol\": \"udp\"}, {\"port\": 127, \"protocol\": \"udp\"}, {\"port\": 129, \"protocol\": \"udp\"}, {\"port\": 131, \"protocol\": \"udp\"}, {\"port\": 133, \"protocol\": \"udp\"}, {\"port\": 135, \"protocol\": \"udp\"}, {\"port\": 137, \"protocol\": \"udp\"}, {\"port\": 139, \"protocol\": \"udp\"}, {\"port\": 141, \"protocol\": \"udp\"}, {\"port\": 143, \"protocol\": \"udp\"}, {\"port\": 145, \"protocol\": \"udp\"}, {\"port\": 147, \"protocol\": \"udp\"}, {\"port\": 149, \"protocol\": \"udp\"}, {\"port\": 151, \"protocol\": \"udp\"}, {\"port\": 153, \"protocol\": \"udp\"}, {\"port\": 155, \"protocol\": \"udp\"}, {\"port\": 157, \"protocol\": \"udp\"}, {\"port\": 159, \"protocol\": \"udp\"}, {\"port\": 161, \"protocol\": \"udp\"}, {\"port\": 163, \"protocol\": \"udp\"}, {\"port\": 165, \"protocol\": \"udp\"}, {\"port\": 167, \"protocol\": \"udp\"}, {\"port\": 169, \"protocol\": \"udp\"}, {\"port\": 171, \"protocol\": \"udp\"}, {\"port\": 173, \"protocol\": \"udp\"}, {\"port\": 175, \"protocol\": \"udp\"}, {\"port\": 177, \"protocol\": \"udp\"}, {\"port\": 179, \"protocol\": \"udp\"}, {\"port\": 181, \"protocol\": \"udp\"}, {\"port\": 183, \"protocol\": \"udp\"}, {\"port\": 185, \"protocol\": \"udp\"}, {\"port\": 187, \"protocol\": \"udp\"}, {\"port\": 189, \"protocol\": \"udp\"}, {\"port\": 191, \"protocol\": \"udp\"}, {\"port\": 193, \"protocol\": \"udp\"}, {\"port\": 195, \"protocol\": \"udp\"}, {\"port\": 197, \"protocol\": \"udp\"}, {\"port\": 199, \"protocol\": \"udp\"}, {\"port\": 201, \"protocol\": \"udp\"}, {\"port\": 203, \"protocol\": \"udp\"}, {\"port\": 205, \"protocol\": \"udp\"}, {\"port\": 207, \"protocol\": \"udp\"}, {\"port\": 209, \"protocol\": \"udp\"}, {\"port\": 211, \"protocol\": \"udp\"}, {\"port\": 213, \"protocol\": \"udp\"}, {\"port\": 215, \"protocol\": \"udp\"}, {\"port\": 217, \"protocol\": \"udp\"}, {\"port\": 219, \"protocol\": \"udp\"}, {\"port\": 221, \"protocol\": \"udp\"}, {\"port\": 223, \"protocol\": \"udp\"}, {\"port\": 225, \"protocol\": \"udp\"}, {\"port\": 227, \"protocol\": \"udp\"}, {\"port\": 229, \"protocol\": \"udp\"}, {\"port\": 231, \"protocol\": \"udp\"}, {\"port\": 233, \"protocol\": \"udp\"}, {\"port\": 235, \"protocol\": \"udp\"}, {\"port\": 237, \"protocol\": \"udp\"}, {\"port\": 239, \"protocol\": \"udp\"}, {\"port\": 241, \"protocol\": \"udp\"}, {\"port\": 243, \"protocol\": \"udp\"}, {\"port\": 245, \"protocol\": \"udp\"}, {\"port\": 247, \"protocol\": \"udp\"}, {\"port\": 249, \"protocol\": \"udp\"}, {\"port\": 251, \"protocol\": \"udp\"}, {\"port\": 253, \"protocol\": \"udp\"}, {\"port\": 255, \"protocol\": \"udp\"}, {\"port\": 257, \"protocol\": \"udp\"}, {\"port\": 259, \"protocol\": \"udp\"}, {\"port\": 261, \"protocol\": \"udp\"}, {\"port\": 263, \"protocol\": \"udp\"}, {\"port\": 265, \"protocol\": \"udp\"}, {\"port\": 267, \"protocol\": \"udp\"}, {\"port\": 269, \"protocol\": \"udp\"}, {\"port\": 271, \"protocol\": \"udp\"}, {\"port\": 273, \"protocol\": \"udp\"}, {\"port\": 275, \"protocol\": \"udp\"}, {\"port\": 277, \"protocol\": \"udp\"}, {\"port\": 279, \"protocol\": \"udp\"}, {\"port\": 281, \"protocol\": \"udp\"}, {\"port\": 283, \"protocol\": \"udp\"}, {\"port\": 285, \"protocol\": \"udp\"}, {\"port\": 287, \"protocol\": \"udp\"}, {\"port\": 289, \"protocol\": \"udp\"}, {\"port\": 291, \"protocol\": \"udp\"}, {\"port\": 293, \"protocol\": \"udp\"}, {\"port\": 295, \"protocol\": \"udp\"}, {\"port\": 297, \"protocol\": \"udp\"}, {\"port\": 299, \"protocol\": \"udp\"}, {\"port\": 301, \"protocol\": \"udp\"}, {\"port\": 303, \"protocol\": \"udp\"}, {\"port\": 305, \"protocol\": \"udp\"}, {\"port\": 307, \"protocol\": \"udp\"}, {\"port\": 309, \"protocol\": \"udp\"}, {\"port\": 311, \"protocol\": \"udp\"}, {\"port\": 313, \"protocol\": \"udp\"}, {\"port\": 315, \"protocol\": \"udp\"}, {\"port\": 317, \"protocol\": \"udp\"}, {\"port\": 319, \"protocol\": \"udp\"}, {\"port\": 321, \"protocol\": \"udp\"}, {\"port\": 323, \"protocol\": \"udp\"}, {\"port\": 325, \"protocol\": \"udp\"}, {\"port\": 327, \"protocol\": \"udp\"}, {\"port\": 329, \"protocol\": \"udp\"}, {\"port\": 331, \"protocol\": \"udp\"}, {\"port\": 333, \"protocol\": \"udp\"}, {\"port\": 335, \"protocol\": \"udp\"}, {\"port\": 337, \"protocol\": \"udp\"}, {\"port\": 339, \"protocol\": \"udp\"}, {\"port\": 341, \"protocol\": \"udp\"}, {\"port\": 343, \"protocol\": \"udp\"}, {\"port\": 345, \"protocol\": \"udp\"}, {\"port\": 347, \"protocol\": \"udp\"}, {\"port\": 349, \"protocol\": \"udp\"}, {\"port\": 351, \"protocol\": \"udp\"}, {\"port\": 353, \"protocol\": \"udp\"}, {\"port\": 355, \"protocol\": \"udp\"}, {\"port\": 357, \"protocol\": \"udp\"}, {\"port\": 359, \"protocol\": \"udp\"}, {\"port\": 361, \"protocol\": \"udp\"}, {\"port\": 363, \"protocol\": \"udp\"}, {\"port\": 365, \"protocol\": \"udp\"}, {\"port\": 367, \"protocol\": \"udp\"}, {\"port\": 369, \"protocol\": \"udp\"}, {\"port\": 371, \"protocol\": \"udp\"}, {\"port\": 373, \"protocol\": \"udp\"}, {\"port\": 375, \"protocol\": \"udp\"}, {\"port\": 377, \"protocol\": \"udp\"}, {\"port\": 379, \"protocol\": \"udp\"}, {\"port\": 381, \"protocol\": \"udp\"}, {\"port\": 383, \"protocol\": \"udp\"}, {\"port\": 385, \"protocol\": \"udp\"}, {\"port\": 387, \"protocol\": \"udp\"}, {\"port\": 389, \"protocol\": \"udp\"}, {\"port\": 391, \"protocol\": \"udp\"}, {\"port\": 393, \"protocol\": \"udp\"}, {\"port\": 395, \"protocol\": \"udp\"}, {\"port\": 397, \"protocol\": \"udp\"}, {\"port\": 399, \"protocol\": \"udp\"}, {\"port\": 401, \"protocol\": \"udp\"}, {\"port\": 403, \"protocol\": \"udp\"}, {\"port\": 405, \"protocol\": \"udp\"}, {\"port\": 407, \"protocol\": \"udp\"}, {\"port\": 409, \"protocol\": \"udp\"}, {\"port\": 411, \"protocol\": \"udp\"}, {\"port\": 413, \"protocol\": \"udp\"}, {\"port\": 415, \"protocol\": \"udp\"}, {\"port\": 417, \"protocol\": \"udp\"}, {\"port\": 419, \"protocol\": \"udp\"}, {\"port\": 421, \"protocol\": \"udp\"}, {\"port\": 423, \"protocol\": \"udp\"}, {\"port\": 425, \"protocol\": \"udp\"}, {\"port\": 427, \"protocol\": \"udp\"}, {\"port\": 429, \"protocol\": \"udp\"}, {\"port\": 431, \"protocol\": \"udp\"}, {\"port\": 433, \"protocol\": \"udp\"}, {\"port\": 435, \"protocol\": \"udp\"}, {\"port\": 437, \"protocol\": \"udp\"}, {\"port\": 439, \"protocol\": \"udp\"}, {\"port\": 441, \"protocol\": \"udp\"}, {\"port\": 443, \"protocol\": \"udp\"}, {\"port\": 445, \"protocol\": \"udp\"}, {\"port\": 447, \"protocol\": \"udp\"}, {\"port\": 449, \"protocol\": \"udp\"}, {\"port\": 451, \"protocol\": \"udp\"}, {\"port\": 453, \"protocol\": \"udp\"}, {\"port\": 455, \"protocol\": \"udp\"}, {\"port\": 457, \"protocol\": \"udp\"}, {\"port\": 459, \"protocol\": \"udp\"}, {\"port\": 461, \"protocol\": \"udp\"}, {\"port\": 463, \"protocol\": \"udp\"}, {\"port\": 465, \"protocol\": \"udp\"}, {\"port\": 467, \"protocol\": \"udp\"}, {\"port\": 469, \"protocol\": \"udp\"}, {\"port\": 471, \"protocol\": \"udp\"}, {\"port\": 473, \"protocol\": \"udp\"}, {\"port\": 475, \"protocol\": \"udp\"}, {\"port\": 477, \"protocol\": \"udp\"}, {\"port\": 479, \"protocol\": \"udp\"}, {\"port\": 481, \"protocol\": \"udp\"}, {\"port\": 483, \"protocol\": \"udp\"}, {\"port\": 485, \"protocol\": \"udp\"}, {\"port\": 487, \"protocol\": \"udp\"}, {\"port\": 489, \"protocol\": \"udp\"}, {\"port\": 491, \"protocol\": \"udp\"}, {\"port\": 493, \"protocol\": \"udp\"}, {\"port\": 495, \"protocol\": \"udp\"}, {\"port\": 497, \"protocol\": \"udp\"}, {\"port\": 499, \"protocol\": \"udp\"}, {\"port\": 501, \"protocol\": \"udp\"}, {\"port\": 503, \"protocol\": \"udp\"}, {\"port\": 505, \"protocol\": \"udp\"}, {\"port\": 507, \"protocol\": \"udp\"}, {\"port\": 509, \"protocol\": \"udp\"}, {\"port\": 511, \"protocol\": \"udp\"}, {\"port\": 513, \"protocol\": \"udp\"}]}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"limit: 128 IPv4 and 128 IPv6 ports","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}]}, {\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}]}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"limit: 129 IPv4 and 128 IPv6 ports","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}, {\"port\": 257}]}, {\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}]}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"limit: 128 IPv4 and 129 IPv6 ports","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}]}, {\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}, {\"port\": 257}]}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"limit: 200 IPv4 and 56 IPv6 ports","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"192.0.2.1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}, {\"port\": 257}, {\"port\": 259}, {\"port\": 261}, {\"port\": 263}, {\"port\": 265}, {\"port\": 267}, {\"port\": 269}, {\"port\": 271}, {\"port\": 273}, {\"port\": 275}, {\"port\": 277}, {\"port\": 279}, {\"port\": 281}, {\"port\": 283}, {\"port\": 285}, {\"port\": 287}, {\"port\": 289}, {\"port\": 291}, {\"port\": 293}, {\"port\": 295}, {\"port\": 297}, {\"port\": 299}, {\"port\": 301}, {\"port\": 303}, {\"port\": 305}, {\"port\": 307}, {\"port\": 309}, {\"port\": 311}, {\"port\": 313}, {\"port\": 315}, {\"port\": 317}, {\"port\": 319}, {\"port\": 321}, {\"port\": 323}, {\"port\": 325}, {\"port\": 327}, {\"port\": 329}, {\"port\": 331}, {\"port\": 333}, {\"port\": 335}, {\"port\": 337}, {\"port\": 339}, {\"port\": 341}, {\"port\": 343}, {\"port\": 345}, {\"port\": 347}, {\"port\": 349}, {\"port\": 351}, {\"port\": 353}, {\"port\": 355}, {\"port\": 357}, {\"port\": 359}, {\"port\": 361}, {\"port\": 363}, {\"port\": 365}, {\"port\": 367}, {\"port\": 369}, {\"port\": 371}, {\"port\": 373}, {\"port\": 375}, {\"port\": 377}, {\"port\": 379}, {\"port\": 381}, {\"port\": 383}, {\"port\": 385}, {\"port\": 387}, {\"port\": 389}, {\"port\": 391}, {\"port\": 393}, {\"port\": 395}, {\"port\": 397}, {\"port\": 399}]}, {\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}]}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"limit: 0 IPv4 and 257 IPv6 ports","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::1\"}], \"ports\": [{\"port\": 1}, {\"port\": 3}, {\"port\": 5}, {\"port\": 7}, {\"port\": 9}, {\"port\": 11}, {\"port\": 13}, {\"port\": 15}, {\"port\": 17}, {\"port\": 19}, {\"port\": 21}, {\"port\": 23}, {\"port\": 25}, {\"port\": 27}, {\"port\": 29}, {\"port\": 31}, {\"port\": 33}, {\"port\": 35}, {\"port\": 37}, {\"port\": 39}, {\"port\": 41}, {\"port\": 43}, {\"port\": 45}, {\"port\": 47}, {\"port\": 49}, {\"port\": 51}, {\"port\": 53}, {\"port\": 55}, {\"port\": 57}, {\"port\": 59}, {\"port\": 61}, {\"port\": 63}, {\"port\": 65}, {\"port\": 67}, {\"port\": 69}, {\"port\": 71}, {\"port\": 73}, {\"port\": 75}, {\"port\": 77}, {\"port\": 79}, {\"port\": 81}, {\"port\": 83}, {\"port\": 85}, {\"port\": 87}, {\"port\": 89}, {\"port\": 91}, {\"port\": 93}, {\"port\": 95}, {\"port\": 97}, {\"port\": 99}, {\"port\": 101}, {\"port\": 103}, {\"port\": 105}, {\"port\": 107}, {\"port\": 109}, {\"port\": 111}, {\"port\": 113}, {\"port\": 115}, {\"port\": 117}, {\"port\": 119}, {\"port\": 121}, {\"port\": 123}, {\"port\": 125}, {\"port\": 127}, {\"port\": 129}, {\"port\": 131}, {\"port\": 133}, {\"port\": 135}, {\"port\": 137}, {\"port\": 139}, {\"port\": 141}, {\"port\": 143}, {\"port\": 145}, {\"port\": 147}, {\"port\": 149}, {\"port\": 151}, {\"port\": 153}, {\"port\": 155}, {\"port\": 157}, {\"port\": 159}, {\"port\": 161}, {\"port\": 163}, {\"port\": 165}, {\"port\": 167}, {\"port\": 169}, {\"port\": 171}, {\"port\": 173}, {\"port\": 175}, {\"port\": 177}, {\"port\": 179}, {\"port\": 181}, {\"port\": 183}, {\"port\": 185}, {\"port\": 187}, {\"port\": 189}, {\"port\": 191}, {\"port\": 193}, {\"port\": 195}, {\"port\": 197}, {\"port\": 199}, {\"port\": 201}, {\"port\": 203}, {\"port\": 205}, {\"port\": 207}, {\"port\": 209}, {\"port\": 211}, {\"port\": 213}, {\"port\": 215}, {\"port\": 217}, {\"port\": 219}, {\"port\": 221}, {\"port\": 223}, {\"port\": 225}, {\"port\": 227}, {\"port\": 229}, {\"port\": 231}, {\"port\": 233}, {\"port\": 235}, {\"port\": 237}, {\"port\": 239}, {\"port\": 241}, {\"port\": 243}, {\"port\": 245}, {\"port\": 247}, {\"port\": 249}, {\"port\": 251}, {\"port\": 253}, {\"port\": 255}, {\"port\": 257}, {\"port\": 259}, {\"port\": 261}, {\"port\": 263}, {\"port\": 265}, {\"port\": 267}, {\"port\": 269}, {\"port\": 271}, {\"port\": 273}, {\"port\": 275}, {\"port\": 277}, {\"port\": 279}, {\"port\": 281}, {\"port\": 283}, {\"port\": 285}, {\"port\": 287}, {\"port\": 289}, {\"port\": 291}, {\"port\": 293}, {\"port\": 295}, {\"port\": 297}, {\"port\": 299}, {\"port\": 301}, {\"port\": 303}, {\"port\": 305}, {\"port\": 307}, {\"port\": 309}, {\"port\": 311}, {\"port\": 313}, {\"port\": 315}, {\"port\": 317}, {\"port\": 319}, {\"port\": 321}, {\"port\": 323}, {\"port\": 325}, {\"port\": 327}, {\"port\": 329}, {\"port\": 331}, {\"port\": 333}, {\"port\": 335}, {\"port\": 337}, {\"port\": 339}, {\"port\": 341}, {\"port\": 343}, {\"port\": 345}, {\"port\": 347}, {\"port\": 349}, {\"port\": 351}, {\"port\": 353}, {\"port\": 355}, {\"port\": 357}, {\"port\": 359}, {\"port\": 361}, {\"port\": 363}, {\"port\": 365}, {\"port\": 367}, {\"port\": 369}, {\"port\": 371}, {\"port\": 373}, {\"port\": 375}, {\"port\": 377}, {\"port\": 379}, {\"port\": 381}, {\"port\": 383}, {\"port\": 385}, {\"port\": 387}, {\"port\": 389}, {\"port\": 391}, {\"port\": 393}, {\"port\": 395}, {\"port\": 397}, {\"port\": 399}, {\"port\": 401}, {\"port\": 403}, {\"port\": 405}, {\"port\": 407}, {\"port\": 409}, {\"port\": 411}, {\"port\": 413}, {\"port\": 415}, {\"port\": 417}, {\"port\": 419}, {\"port\": 421}, {\"port\": 423}, {\"port\": 425}, {\"port\": 427}, {\"port\": 429}, {\"port\": 431}, {\"port\": 433}, {\"port\": 435}, {\"port\": 437}, {\"port\": 439}, {\"port\": 441}, {\"port\": 443}, {\"port\": 445}, {\"port\": 447}, {\"port\": 449}, {\"port\": 451}, {\"port\": 453}, {\"port\": 455}, {\"port\": 457}, {\"port\": 459}, {\"port\": 461}, {\"port\": 463}, {\"port\": 465}, {\"port\": 467}, {\"port\": 469}, {\"port\": 471}, {\"port\": 473}, {\"port\": 475}, {\"port\": 477}, {\"port\": 479}, {\"port\": 481}, {\"port\": 483}, {\"port\": 485}, {\"port\": 487}, {\"port\": 489}, {\"port\": 491}, {\"port\": 493}, {\"port\": 495}, {\"port\": 497}, {\"port\": 499}, {\"port\": 501}, {\"port\": 503}, {\"port\": 505}, {\"port\": 507}, {\"port\": 509}, {\"port\": 511}, {\"port\": 513}]}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"limit: ranges of any protocol over the limit","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 2}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 4, \"endPort\": 5}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 7, \"endPort\": 8}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 10, \"endPort\": 11}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 13, \"endPort\": 14}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 16, \"endPort\": 17}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 19, \"endPort\": 20}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 22, \"endPort\": 23}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 25, \"endPort\": 26}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 28, \"endPort\": 29}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 31, \"endPort\": 32}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 34, \"endPort\": 35}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 37, \"endPort\": 38}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 40, \"endPort\": 41}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 43, \"endPort\": 44}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 46, \"endPort\": 47}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 49, \"endPort\": 50}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 52, \"endPort\": 53}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 55, \"endPort\": 56}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 58, \"endPort\": 59}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 61, \"endPort\": 62}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 64, \"endPort\": 65}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 67, \"endPort\": 68}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 70, \"endPort\": 71}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 73, \"endPort\": 74}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 76, \"endPort\": 77}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 79, \"endPort\": 80}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 82, \"endPort\": 83}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 85, \"endPort\": 86}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 88, \"endPort\": 89}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 91, \"endPort\": 92}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 94, \"endPort\": 95}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 97, \"endPort\": 98}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 100, \"endPort\": 101}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 103, \"endPort\": 104}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 106, \"endPort\": 107}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 109, \"endPort\": 110}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 112, \"endPort\": 113}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 115, \"endPort\": 116}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 118, \"endPort\": 119}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 121, \"endPort\": 122}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 124, \"endPort\": 125}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 127, \"endPort\": 128}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 130, \"endPort\": 131}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 133, \"endPort\": 134}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 136, \"endPort\": 137}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 139, \"endPort\": 140}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 142, \"endPort\": 143}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 145, \"endPort\": 146}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 148, \"endPort\": 149}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 151, \"endPort\": 152}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 154, \"endPort\": 155}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 157, \"endPort\": 158}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 160, \"endPort\": 161}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 163, \"endPort\": 164}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 166, \"endPort\": 167}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 169, \"endPort\": 170}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 172, \"endPort\": 173}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 175, \"endPort\": 176}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 178, \"endPort\": 179}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 181, \"endPort\": 182}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 184, \"endPort\": 185}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 187, \"endPort\": 188}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 190, \"endPort\": 191}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 193, \"endPort\": 194}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 196, \"endPort\": 197}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 199, \"endPort\": 200}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 202, \"endPort\": 203}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 205, \"endPort\": 206}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 208, \"endPort\": 209}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 211, \"endPort\": 212}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 214, \"endPort\": 215}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 217, \"endPort\": 218}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 220, \"endPort\": 221}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 223, \"endPort\": 224}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 226, \"endPort\": 227}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 229, \"endPort\": 230}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 232, \"endPort\": 233}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 235, \"endPort\": 236}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 238, \"endPort\": 239}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 241, \"endPort\": 242}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 244, \"endPort\": 245}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 247, \"endPort\": 248}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 250, \"endPort\": 251}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 253, \"endPort\": 254}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 256, \"endPort\": 257}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 259, \"endPort\": 260}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 262, \"endPort\": 263}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 265, \"endPort\": 266}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 268, \"endPort\": 269}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 271, \"endPort\": 272}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 274, \"endPort\": 275}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 277, \"endPort\": 278}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 280, \"endPort\": 281}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 283, \"endPort\": 284}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 286, \"endPort\": 287}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 289, \"endPort\": 290}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 292, \"endPort\": 293}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 295, \"endPort\": 296}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 298, \"endPort\": 299}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 301, \"endPort\": 302}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 304, \"endPort\": 305}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 307, \"endPort\": 308}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 310, \"endPort\": 311}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 313, \"endPort\": 314}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 316, \"endPort\": 317}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 319, \"endPort\": 320}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 322, \"endPort\": 323}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 325, \"endPort\": 326}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 328, \"endPort\": 329}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 331, \"endPort\": 332}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 334, \"endPort\": 335}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 337, \"endPort\": 338}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 340, \"endPort\": 341}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 343, \"endPort\": 344}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 346, \"endPort\": 347}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 349, \"endPort\": 350}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 352, \"endPort\": 353}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 355, \"endPort\": 356}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 358, \"endPort\": 359}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 361, \"endPort\": 362}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 364, \"endPort\": 365}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 367, \"endPort\": 368}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 370, \"endPort\": 371}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 373, \"endPort\": 374}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 376, \"endPort\": 377}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 379, \"endPort\": 380}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 382, \"endPort\": 383}, {\"cidr\": \"192.0.2.1\", \"protocol\": \"any\", \"port\": 385, \"endPort\": 386}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"limit: fragmented exclusions","input":[{"text":"{\"allow\": [{\"cidr\": \"0.0.0.0/0\", \"except\": [\"0.0.0.1\", \"2.0.0.1\", \"4.0.0.1\", \"6.0.0.1\", \"8.0.0.1\", \"10.0.0.1\", \"12.0.0.1\", \"14.0.0.1\", \"16.0.0.1\", \"18.0.0.1\", \"20.0.0.1\", \"22.0.0.1\", \"24.0.0.1\", \"26.0.0.1\", \"28.0.0.1\", \"30.0.0.1\", \"32.0.0.1\", \"34.0.0.1\", \"36.0.0.1\", \"38.0.0.1\", \"40.0.0.1\", \"42.0.0.1\", \"44.0.0.1\", \"46.0.0.1\", \"48.0.0.1\", \"50.0.0.1\", \"52.0.0.1\", \"54.0.0.1\", \"56.0.0.1\", \"58.0.0.1\", \"60.0.0.1\", \"62.0.0.1\", \"64.0.0.1\", \"66.0.0.1\", \"68.0.0.1\", \"70.0.0.1\", \"72.0.0.1\", \"74.0.0.1\", \"76.0.0.1\", \"78.0.0.1\", \"80.0.0.1\", \"82.0.0.1\", \"84.0.0.1\", \"86.0.0.1\", \"88.0.0.1\", \"90.0.0.1\", \"92.0.0.1\", \"94.0.0.1\", \"96.0.0.1\", \"98.0.0.1\", \"100.0.0.1\", \"102.0.0.1\", \"104.0.0.1\", \"106.0.0.1\", \"108.0.0.1\", \"110.0.0.1\", \"112.0.0.1\", \"114.0.0.1\", \"116.0.0.1\", \"118.0.0.1\", \"120.0.0.1\", \"122.0.0.1\", \"124.0.0.1\", \"126.0.0.1\"]}]}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"limit: fragments covered by a wider rule","input":[{"text":"{\"allow\": [{\"cidr\": \"0.0.0.0/0\", \"except\": [\"0.0.0.1\", \"1.0.0.1\", \"2.0.0.1\", \"3.0.0.1\", \"4.0.0.1\", \"5.0.0.1\", \"6.0.0.1\", \"7.0.0.1\", \"8.0.0.1\", \"9.0.0.1\", \"10.0.0.1\", \"11.0.0.1\", \"12.0.0.1\", \"13.0.0.1\", \"14.0.0.1\", \"15.0.0.1\", \"16.0.0.1\", \"17.0.0.1\", \"18.0.0.1\", \"19.0.0.1\", \"20.0.0.1\", \"21.0.0.1\", \"22.0.0.1\", \"23.0.0.1\", \"24.0.0.1\", \"25.0.0.1\", \"26.0.0.1\", \"27.0.0.1\", \"28.0.0.1\", \"29.0.0.1\", \"30.0.0.1\", \"31.0.0.1\", \"32.0.0.1\", \"33.0.0.1\", \"34.0.0.1\", \"35.0.0.1\", \"36.0.0.1\", \"37.0.0.1\", \"38.0.0.1\", \"39.0.0.1\", \"40.0.0.1\", \"41.0.0.1\", \"42.0.0.1\", \"43.0.0.1\", \"44.0.0.1\", \"45.0.0.1\", \"46.0.0.1\", \"47.0.0.1\", \"48.0.0.1\", \"49.0.0.1\", \"50.0.0.1\", \"51.0.0.1\", \"52.0.0.1\", \"53.0.0.1\", \"54.0.0.1\", \"55.0.0.1\", \"56.0.0.1\", \"57.0.0.1\", \"58.0.0.1\", \"59.0.0.1\", \"60.0.0.1\", \"61.0.0.1\", \"62.0.0.1\", \"63.0.0.1\", \"64.0.0.1\", \"65.0.0.1\", \"66.0.0.1\", \"67.0.0.1\", \"68.0.0.1\", \"69.0.0.1\", \"70.0.0.1\", \"71.0.0.1\", \"72.0.0.1\", \"73.0.0.1\", \"74.0.0.1\", \"75.0.0.1\", \"76.0.0.1\", \"77.0.0.1\", \"78.0.0.1\", \"79.0.0.1\", \"80.0.0.1\", \"81.0.0.1\", \"82.0.0.1\", \"83.0.0.1\", \"84.0.0.1\", \"85.0.0.1\", \"86.0.0.1\", \"87.0.0.1\", \"88.0.0.1\", \"89.0.0.1\", \"90.0.0.1\", \"91.0.0.1\", \"92.0.0.1\", \"93.0.0.1\", \"94.0.0.1\", \"95.0.0.1\", \"96.0.0.1\", \"97.0.0.1\", \"98.0.0.1\", \"99.0.0.1\", \"100.0.0.1\", \"101.0.0.1\", \"102.0.0.1\", \"103.0.0.1\", \"104.0.0.1\", \"105.0.0.1\", \"106.0.0.1\", \"107.0.0.1\", \"108.0.0.1\", \"109.0.0.1\", \"110.0.0.1\", \"111.0.0.1\", \"112.0.0.1\", \"113.0.0.1\", \"114.0.0.1\", \"115.0.0.1\", \"116.0.0.1\", \"117.0.0.1\", \"118.0.0.1\", \"119.0.0.1\", \"120.0.0.1\", \"121.0.0.1\", \"122.0.0.1\", \"123.0.0.1\", \"124.0.0.1\", \"125.0.0.1\", \"126.0.0.1\", \"127.0.0.1\", \"128.0.0.1\", \"129.0.0.1\", \"130.0.0.1\", \"131.0.0.1\", \"132.0.0.1\", \"133.0.0.1\", \"134.0.0.1\", \"135.0.0.1\", \"136.0.0.1\", \"137.0.0.1\", \"138.0.0.1\", \"139.0.0.1\", \"140.0.0.1\", \"141.0.0.1\", \"142.0.0.1\", \"143.0.0.1\", \"144.0.0.1\", \"145.0.0.1\", \"146.0.0.1\", \"147.0.0.1\", \"148.0.0.1\", \"149.0.0.1\", \"150.0.0.1\", \"151.0.0.1\", \"152.0.0.1\", \"153.0.0.1\", \"154.0.0.1\", \"155.0.0.1\", \"156.0.0.1\", \"157.0.0.1\", \"158.0.0.1\", \"159.0.0.1\", \"160.0.0.1\", \"161.0.0.1\", \"162.0.0.1\", \"163.0.0.1\", \"164.0.0.1\", \"165.0.0.1\", \"166.0.0.1\", \"167.0.0.1\", \"168.0.0.1\", \"169.0.0.1\", \"170.0.0.1\", \"171.0.0.1\", \"172.0.0.1\", \"173.0.0.1\", \"174.0.0.1\", \"175.0.0.1\", \"176.0.0.1\", \"177.0.0.1\", \"178.0.0.1\", \"179.0.0.1\", \"180.0.0.1\", \"181.0.0.1\", \"182.0.0.1\", \"183.0.0.1\", \"184.0.0.1\", \"185.0.0.1\", \"186.0.0.1\", \"187.0.0.1\", \"188.0.0.1\", \"189.0.0.1\", \"190.0.0.1\", \"191.0.0.1\", \"192.0.0.1\", \"193.0.0.1\", \"194.0.0.1\", \"195.0.0.1\", \"196.0.0.1\", \"197.0.0.1\", \"198.0.0.1\", \"199.0.0.1\", \"200.0.0.1\", \"201.0.0.1\", \"202.0.0.1\", \"203.0.0.1\", \"204.0.0.1\", \"205.0.0.1\", \"206.0.0.1\", \"207.0.0.1\", \"208.0.0.1\", \"209.0.0.1\", \"210.0.0.1\", \"211.0.0.1\", \"212.0.0.1\", \"213.0.0.1\", \"214.0.0.1\", \"215.0.0.1\", \"216.0.0.1\", \"217.0.0.1\", \"218.0.0.1\", \"219.0.0.1\", \"220.0.0.1\", \"221.0.0.1\", \"222.0.0.1\", \"223.0.0.1\", \"224.0.0.1\", \"225.0.0.1\", \"226.0.0.1\", \"227.0.0.1\", \"228.0.0.1\", \"229.0.0.1\", \"230.0.0.1\", \"231.0.0.1\", \"232.0.0.1\", \"233.0.0.1\", \"234.0.0.1\", \"235.0.0.1\", \"236.0.0.1\", \"237.0.0.1\", \"238.0.0.1\", \"239.0.0.1\", \"240.0.0.1\", \"241.0.0.1\", \"242.0.0.1\", \"243.0.0.1\", \"244.0.0.1\", \"245.0.0.1\", \"246.0.0.1\", \"247.0.0.1\", \"248.0.0.1\", \"249.0.0.1\", \"250.0.0.1\", \"251.0.0.1\", \"252.0.0.1\", \"253.0.0.1\", \"254.0.0.1\", \"255.0.0.1\"]}, {\"cidr\": \"0.0.0.0/0\"}]}"}],"expected":{"allow":["0.0.0.0/0"],"deny":[]}}, + {"name":"size: exactly the limit","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"}]}"},{"text":" ","repeat":1048539}],"expected":{"allow":["192.0.2.0/24"],"deny":[]}}, + {"name":"size: one byte over the limit","input":[{"text":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\"}]}"},{"text":" ","repeat":1048540}],"expected":{"error":"egress policy file exceeds 1048576-byte limit: {path}"}}, + {"name":"size: invalid and over the limit","input":[{"hex":"ff","repeat":1048577}],"expected":{"error":"egress policy file exceeds 1048576-byte limit: {path}"}}, + {"name":"size: spaces up to the limit","input":[{"text":" ","repeat":1048576}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #0","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"23.74.67.220/255.255.255.254\"}, {\"cidr\": \"0.0.0.0/0\"}, {\"cidr\": \"10.0.1.128/30\"}, {\"cidr\": \"2001:db8::28c/118\"}], \"ports\": [{\"port\": 65535, \"endPort\": 65535}, {\"protocol\": \"udp\", \"port\": 5, \"endPort\": 7}]}, {\"to\": [{\"cidr\": \"2001:db8::a6/118\", \"except\": [\"2001:db8::394/126\", \"2001:db8::/118\", \"2001:db8::32d/121\"]}]}, {\"cidr\": \"10.0.2.0/26\", \"except\": [\"10.0.2.43\", \"10.0.2.7\", \"10.0.2.0/26\"], \"protocol\": \"tcp\", \"port\": 65535}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.231/124\", \"except\": [\"2001:db8::e0/124\", \"2001:db8::e8/126\"]}, {\"cidr\": \"80.0.0.0/0004\"}, {\"cidr\": \"10.0.2.0/24\"}, {\"cidr\": \"10.0.2.64/255.255.255.224\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 78}]}], \"deny\": [{\"to\": [{\"cidr\": \"2001:db8::200/122\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"udp\", \"port\": 8}, {\"protocol\": \"tcp\", \"port\": 4}, {\"protocol\": \"any\"}]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.2.216/126\", \"protocol\": \"any\", \"port\": 65532}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0237/127\", \"protocol\": \"udp\", \"port\": 81, \"endPort\": 24324}, {\"to\": [{\"cidr\": \"10.0.2.0/28\"}]}, {\"cidr\": \"10.0.3.0/24\", \"protocol\": \"any\", \"port\": 65532}, {\"cidr\": \"10.0.0.229/32\", \"except\": [], \"protocol\": \"tcp\", \"port\": 445}, {\"ports\": [{\"protocol\": \"tcp\", \"port\": 78, \"endPort\": 42429}, {\"protocol\": \"udp\"}]}, {\"to\": [{\"cidr\": \"2001:DB8::1B0/0124\"}, {\"cidr\": \"231.5.119.194/31\"}, {\"cidr\": \"2001:db8::200/120\"}, {\"cidr\": \"10.0.1.112/0.0.0.15\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 79, \"endPort\": 79}, {\"protocol\": \"any\"}, {\"protocol\": \"tcp\"}]}]}"}],"expected":{"allow":["0.0.0.0/0:tcp:65535","0.0.0.0/0:udp:5-7","0.0.0.0/0:udp:65535","10.0.2.0/24:udp:78","80.0.0.0/4:udp:78","2001:db8::/118:tcp:65535","2001:db8::/118:udp:5-7","2001:db8::/118:udp:65535"],"deny":["0.0.0.0/0:tcp:78-42429","0.0.0.0/0:udp","10.0.1.112/28","10.0.2.0/28","10.0.3.0/24:tcp:65532","231.5.119.194/31","::/0:tcp:78-42429","::/0:udp","2001:db8::1b0/124","2001:db8::200/120"]}}, + {"name":"random #1","input":[{"text":"{\n \"allow\": [\n {\n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 65532\n }, \n {\n \"protocol\": \"tcp\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.0/27\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 65532, \n \"endPort\": 65534\n }\n ]\n }, \n {\n \"cidr\": \"2001:DB8::3C0/124\", \n \"except\": [\n \"2001:db8::3cc/128\", \n \"2001:db8::3c0/126\"\n ], \n \"protocol\": \"icmp\"\n }, \n {\n \"ports\": [\n {\n \"port\": 80, \n \"endPort\": 83\n }\n ]\n }\n ], \n \"deny\": []\n}"}],"expected":{"allow":["0.0.0.0/0:tcp","0.0.0.0/0:udp:80-83","10.0.3.0/27:udp:65532-65534","::/0:tcp","::/0:udp:80-83","2001:db8::3c4/126:icmp","2001:db8::3c8/126:icmp","2001:db8::3cd/128:icmp","2001:db8::3ce/127:icmp"],"deny":[]}}, + {"name":"random #2","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.0.72/30\"}, {\"cidr\": \"10.0.1.220/30\"}, {\"cidr\": \"10.0.1.240/29\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\"}]}, {\"cidr\": \"2001:DB8::160/123\", \"except\": [], \"protocol\": \"any\", \"port\": 6, \"endPort\": 9}], \"deny\": []}"}],"expected":{"allow":["10.0.0.72/30:tcp","10.0.0.72/30:udp","10.0.1.220/30:tcp","10.0.1.220/30:udp","10.0.1.240/29:tcp","10.0.1.240/29:udp","2001:db8::160/123:tcp:6-9","2001:db8::160/123:udp:6-9"],"deny":[]}}, + {"name":"random #3","input":[{"text":"{\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.80/255.255.255.254\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.80\", \n\t\t\t\t\t\t\"10.0.1.80/31\", \n\t\t\t\t\t\t\"10.0.1.80/255.255.255.254\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.129/24\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.18/00027\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"0.0.0.0/2\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 442\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 7, \n\t\t\t\t\t\"endPort\": 10\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.0.64/26\", \n\t\t\t\"except\": []\n\t\t}, \n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 82, \n\t\t\t\t\t\"endPort\": 83\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 444\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":[],"deny":["0.0.0.0/0:tcp","0.0.0.0/0:udp:82-83","0.0.0.0/2","::/0:tcp","::/0:udp:82-83"]}}, + {"name":"random #4","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1c0/122\",\n\t\t\t\"except\": [\n\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:01e4/125\",\n\t\t\t\t\"2001:db8::1c9/122\",\n\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:01d8/125\"\n\t\t\t],\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 2,\n\t\t\t\"endPort\": 33322\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.64/027\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 442\n\t\t},\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 442,\n\t\t\t\t\t\"endPort\": 443\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.160/255.255.255.240\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.0.160/255.255.255.240\",\n\t\t\t\t\t\t\"10.0.0.164/30\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.61/22\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"port\": 444,\n\t\t\t\t\t\"endPort\": 446\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"endPort\": 0\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.48/28\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 445\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 80,\n\t\t\t\t\t\"endPort\": 9745\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 65533,\n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.30/031\",\n\t\t\t\"except\": [\n\t\t\t\t\"10.0.3.30/31\",\n\t\t\t\t\"10.0.3.31\"\n\t\t\t],\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 65535\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::140/122\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 81,\n\t\t\t\"endPort\": 81\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::300/120\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 65531\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::/120\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"55.48.14.65/0001\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.216/30\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.192/26\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::200/121\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"allow[3] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #5","input":[{"text":"{\"allow\": [{\"cidr\": \"128.0.0.0/2\", \"except\": [\"181.0.0.0/8\", \"185.116.28.62/3\", \"128.0.0.0/003\"], \"protocol\": \"tcp\", \"port\": 65532, \"endPort\": 65535}, {\"cidr\": \"176.0.0.0/4\", \"protocol\": \"tcp\", \"port\": 6, \"endPort\": 9}, {\"cidr\": \"10.0.3.128/26\", \"protocol\": \"any\", \"port\": 65531, \"endPort\": 65532}], \"deny\": [{\"cidr\": \"::/1\", \"except\": [], \"protocol\": \"udp\", \"port\": 9, \"endPort\": 9}, {\"to\": [{\"cidr\": \"2001:db8::152/124\"}, {\"cidr\": \"8354:9c55:5a5f:ba2c:8faf:6798:f9f4:7f00/120\"}, {\"cidr\": \"0.0.0.0/0\"}, {\"cidr\": \"2001:db8::200/122\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 444, \"endPort\": 447}]}, {\"cidr\": \"38.55.133.109/2\", \"protocol\": \"tcp\", \"port\": 7, \"endPort\": 11}]}"}],"expected":{"allow":["10.0.3.128/26:tcp:65531-65532","10.0.3.128/26:udp:65531-65532","176.0.0.0/4:tcp:6-9"],"deny":["0.0.0.0/0:tcp:444-447","0.0.0.0/2:tcp:7-11","::/1:udp:9","2001:db8::150/124:tcp:444-447","2001:db8::200/122:tcp:444-447","8354:9c55:5a5f:ba2c:8faf:6798:f9f4:7f00/120:tcp:444-447"]}}, + {"name":"random #6","input":[{"text":"{\"allow\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:01c0/122\",\"protocol\":\"any\",\"port\":8},{\"ports\":[{\"protocol\":\"any\"},{\"protocol\":\"tcp\",\"port\":81,\"endPort\":83},{\"port\":65532},{\"protocol\":\"tcp\"}]},{\"cidr\":\"2001:db8::10f/0128\"},{\"ports\":[{\"protocol\":\"tcp\",\"port\":12,\"endPort\":13},{\"protocol\":\"udp\",\"port\":445,\"endPort\":449},{\"protocol\":\"any\",\"port\":65534}]}],\"deny\":[{\"to\":[{\"cidr\":\"2001:db8::ac\",\"except\":[\"2001:db8::ac\",\"2001:db8::ac/0128\",\"2001:0db8:0000:0000:0000:0000:0000:00ac/128\"]},{\"cidr\":\"10.0.3.176/28\",\"except\":[\"10.0.3.188/30\"]},{\"cidr\":\"10.0.2.171/32\",\"except\":[\"10.0.2.171\",\"10.0.2.171\"]},{\"cidr\":\"2001:db8::3ca/000127\"}],\"ports\":[{\"protocol\":\"any\",\"port\":65531,\"endPort\":65534},{\"protocol\":\"udp\"},{\"protocol\":\"icmp\"}]},{\"to\":[{\"cidr\":\"2001:db8::200/000119\",\"except\":[]},{\"cidr\":\"10.0.2.140/26\"},{\"cidr\":\"10.0.0.0/24\"},{\"cidr\":\"2001:DB8::3F8/125\"}],\"ports\":[{\"protocol\":\"icmp\"}]}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["10.0.0.0/24:icmp","10.0.2.128/26:icmp","10.0.3.176/29:icmp","10.0.3.176/29:tcp:65531-65534","10.0.3.176/29:udp","10.0.3.184/30:icmp","10.0.3.184/30:tcp:65531-65534","10.0.3.184/30:udp","2001:db8::200/119:icmp","2001:db8::3ca/127:tcp:65531-65534","2001:db8::3ca/127:udp"]}}, + {"name":"random #7","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::/119\", \"protocol\": \"udp\", \"port\": 12}, {\"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"udp\"}, {\"protocol\": \"any\", \"port\": 78}, {\"protocol\": \"tcp\", \"port\": 78, \"endPort\": 78}]}, {\"ports\": [{\"protocol\": \"tcp\", \"port\": 443, \"endPort\": 446}, {\"protocol\": \"tcp\", \"port\": 445, \"endPort\": 447}]}, {\"cidr\": \"2001:db8::90/121\", \"protocol\": \"udp\"}, {\"to\": [{\"cidr\": \"16.81.121.51/4\"}], \"ports\": [{\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"109.16.44.171/8\", \"except\": [\"109.224.0.0/13\", \"109.250.0.0/16\", \"109.40.0.0/15\"]}], \"ports\": [{\"protocol\": \"tcp\"}]}, {}, {\"to\": [{\"cidr\": \"2001:db8::200/120\", \"except\": []}, {\"cidr\": \"10.0.1.48/28\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 444, \"endPort\": 446}, {\"port\": 443, \"endPort\": 447}, {\"protocol\": \"any\"}]}, {\"to\": [{\"cidr\": \"2001:DB8::/120\"}, {\"cidr\": \"144.0.0.0/4\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 3, \"endPort\": 7}]}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.2.32/123\"}, {\"cidr\": \"::ffff:11.251.162.71/000104\", \"except\": [\"::ffff:11.64.0.0/107\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65534}, {\"protocol\": \"any\"}, {\"port\": 65532, \"endPort\": 65535}, {\"protocol\": \"any\", \"port\": 442, \"endPort\": 446}]}, {\"to\": [{\"cidr\": \"157.85.35.190/31\"}, {\"cidr\": \"10.0.0.143/0.0.1.255\"}, {\"cidr\": \"62.165.173.29/16\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"udp\"}, {\"protocol\": \"any\", \"port\": 4, \"endPort\": 4658}]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0170/124\", \"protocol\": \"any\", \"port\": 8, \"endPort\": 8}, {\"to\": [{\"cidr\": \"10.0.1.0/26\"}, {\"cidr\": \"10.0.1.128/0.0.0.7\", \"except\": [\"10.0.1.132/31\", \"10.0.1.134/32\", \"10.0.1.128/30\"]}]}, {\"to\": [{\"cidr\": \"165.64.138.224/128.0.0.0\"}, {\"cidr\": \"2001:db8::40/123\", \"except\": [\"2001:DB8::50/00124\", \"2001:db8::40/126\", \"2001:db8::40/126\"]}, {\"cidr\": \"10.0.0.100/0.0.0.3\", \"except\": []}, {\"cidr\": \"10.0.3.192/26\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 445}, {\"protocol\": \"tcp\"}, {\"protocol\": \"udp\", \"port\": 79, \"endPort\": 58101}, {\"protocol\": \"icmp\"}]}, {\"cidr\": \"2001:db8::24e/120\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0.0.2.208/124\", \"2001:db8::2ef/122\"], \"protocol\": \"icmp\"}, {\"cidr\": \"2001:DB8::3DA/122\", \"except\": [], \"protocol\": \"tcp\", \"port\": 81}, {\"to\": [{\"cidr\": \"2001:db8::300/120\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:0390/127\", \"2001:db8::340/124\"]}, {\"cidr\": \"2001:db8::371/125\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 444}, {\"protocol\": \"any\", \"port\": 78}]}, {\"cidr\": \"10.0.2.48/28\", \"protocol\": \"tcp\"}, {}, {\"to\": [{\"cidr\": \"2001:DB8::200/122\"}, {\"cidr\": \"10.0.1.0/24\"}, {\"cidr\": \"2001:DB8::174/126\"}]}, {\"to\": [{\"cidr\": \"2001:db8::3c0/122\", \"except\": []}]}, {\"cidr\": \"8682:bcfe:0000:0000:0000:0000:0000:0000/32\", \"protocol\": \"icmp\"}, {\"to\": [{\"cidr\": \"10.0.0.95/31\"}, {\"cidr\": \"10.0.0.0/23\"}, {\"cidr\": \"10.0.2.81/30\"}]}, {\"to\": [{\"cidr\": \"2001:db8::2c1/123\"}, {\"cidr\": \"10.0.2.176/28\"}, {\"cidr\": \"10.0.3.192/28\", \"except\": []}, {\"cidr\": \"128.0.0.0/2\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"port\": 65533, \"endPort\": 65535}, {\"protocol\": \"udp\", \"port\": 6}]}, {\"cidr\": \"24f9:243f:7d7c:fd47:fa64:63cd:425f:9e00/120\", \"except\": [\"24f9:243f:7d7c:fd47:fa64:63cd:425f:9e80/121\", \"24f9:243f:7d7c:fd47:fa64:63cd:425f:9e00/122\"], \"protocol\": \"tcp\"}, {\"cidr\": \"2001:db8::2c0/122\", \"protocol\": \"tcp\", \"port\": 443, \"endPort\": 443}, {\"to\": [{\"cidr\": \"2001:db8::2e6/125\"}, {\"cidr\": \"2001:db8::3df/123\"}, {\"cidr\": \"8000:0000:0000:0000:0000:0000:0000:0000/1\"}, {\"cidr\": \"10.0.1.26/255.255.255.224\", \"except\": [\"10.0.1.4/27\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 79}, {\"port\": 8}, {\"protocol\": \"icmp\"}]}, {}, {\"to\": [{\"cidr\": \"2001:db8::382/125\", \"except\": []}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0366/00128\"}, {\"cidr\": \"52.203.163.0/24\"}, {\"cidr\": \"2001:DB8::/118\"}]}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0178/121\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 6, \"endPort\": 7}, {\"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65531}, {\"protocol\": \"udp\", \"port\": 81, \"endPort\": 81}]}, {\"cidr\": \"10.0.1.96/27\", \"except\": [\"10.0.1.102\"], \"protocol\": \"udp\", \"port\": 82, \"endPort\": 86}, {\"to\": [{\"cidr\": \"10.0.1.116/031\", \"except\": [\"10.0.1.117\", \"10.0.1.116/31\", \"10.0.1.116/32\"]}, {\"cidr\": \"2001:db8::320/000123\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"any\"}, {\"protocol\": \"icmp\"}]}, {\"ports\": [{\"protocol\": \"any\"}, {\"protocol\": \"tcp\", \"port\": 65531, \"endPort\": 65535}]}, {\"to\": [{\"cidr\": \"10.0.1.28\"}]}, {\"cidr\": \"10.0.1.0/24\", \"protocol\": \"tcp\"}, {\"ports\": [{\"protocol\": \"udp\", \"port\": 444, \"endPort\": 7998}, {\"port\": 440}, {\"protocol\": \"tcp\", \"port\": 12, \"endPort\": 15}]}, {\"to\": [{\"cidr\": \"2001:db8::280/121\"}, {\"cidr\": \"10.0.0.192/29\", \"except\": [\"10.0.0.196/30\", \"10.0.0.199/30\", \"10.0.0.194/31\"]}], \"ports\": [{\"protocol\": \"any\", \"port\": 81, \"endPort\": 83}]}, {\"to\": [{\"cidr\": \"10.0.2.24/25\", \"except\": []}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0380/123\", \"except\": [\"2001:db8::390/124\", \"2001:db8::392/127\"]}, {\"cidr\": \"::/1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"any\", \"port\": 79, \"endPort\": 80}, {\"protocol\": \"udp\", \"port\": 440}]}, {\"to\": [{\"cidr\": \"10.0.3.156/030\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 65532, \"endPort\": 65533}, {\"protocol\": \"any\", \"port\": 444, \"endPort\": 56397}, {\"protocol\": \"any\", \"port\": 65530, \"endPort\": 65535}]}, {\"cidr\": \"10.0.0.176/255.255.255.248\", \"protocol\": \"any\"}, {\"cidr\": \"2001:db8::348/125\"}, {\"cidr\": \"10.0.2.104/30\", \"protocol\": \"any\", \"port\": 444, \"endPort\": 447}, {\"cidr\": \"10.0.0.0/255.255.254.0\", \"protocol\": \"tcp\", \"port\": 65533, \"endPort\": 65535}, {\"to\": [{\"cidr\": \"10.0.0.0/22\", \"except\": [\"10.0.0.0/00023\", \"10.0.0.152/25\", \"10.0.2.0/23\"]}, {\"cidr\": \"10.0.0.194\"}, {\"cidr\": \"10.0.0.37/32\"}, {\"cidr\": \"2001:DB8::1CB\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65533, \"endPort\": 65534}, {\"protocol\": \"any\"}, {\"port\": 65534}, {\"protocol\": \"udp\", \"port\": 12}]}, {\"cidr\": \"2001:db8::2f9/127\", \"protocol\": \"any\", \"port\": 82, \"endPort\": 82}, {\"to\": [{\"cidr\": \"c2d6:46cf:7673:1d6:930:d0b5::/96\"}, {\"cidr\": \"10.0.2.0/24\"}, {\"cidr\": \"10.0.2.204/255.255.255.252\"}]}, {\"to\": [{\"cidr\": \"26.175.173.172\"}, {\"cidr\": \"e000:0000:0000:0000:0000:0000:0.0.0.0/3\"}, {\"cidr\": \"183.0.0.0/8\"}, {\"cidr\": \"10.0.1.224/27\", \"except\": [\"10.0.1.239/30\", \"10.0.1.224/00027\", \"10.0.1.246/255.255.255.254\"]}], \"ports\": [{\"protocol\": \"udp\"}]}, {\"to\": [{\"cidr\": \"10.0.1.161/0.0.0.63\"}, {\"cidr\": \"10.0.1.84/32\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 9}]}, {\"to\": [{\"cidr\": \"10.0.0.113/24\"}, {\"cidr\": \"10.0.2.30\"}, {\"cidr\": \"2001:db8::1b6/126\"}], \"ports\": [{}]}, {\"cidr\": \"10.0.2.0/23\", \"protocol\": \"udp\"}, {\"cidr\": \"2001:DB8::90/0125\", \"protocol\": \"tcp\"}, {\"to\": [{\"cidr\": \"202.209.202.54/24\"}, {\"cidr\": \"10.0.1.182/30\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\", \"port\": 81, \"endPort\": 82}]}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/22\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 78}]}, {\"cidr\": \"2001:db8::388/127\", \"except\": [\"::/0\", \"2001:0db8:0000:0000:0000:0000:0.0.3.137/128\"], \"protocol\": \"icmp\"}, {\"to\": [{\"cidr\": \"2001:db8::140/123\", \"except\": [\"2001:db8::148/0128\"]}, {\"cidr\": \"f44d:213d::/32\", \"except\": [\"f44d:213d:58b4:78f2:6752:3908:33bd:3e67/33\", \"f44d:213d:2000::/00038\", \"f44d:213d:4800::/38\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 445}]}, {\"cidr\": \"10.0.0.216/32\", \"protocol\": \"icmp\"}]}"}],"expected":{"error":"deny[1].except[0] must be contained in 2001:db8::388/127"}}, + {"name":"random #8","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"0.0.0.0/0\"\n}, \n{\n\"cidr\": \"10.0.1.40/31\", \n\"except\": [\n\"10.0.1.41/32\"\n]\n}, \n{\n\"cidr\": \"10.0.0.0/23\"\n}, \n{\n\"cidr\": \"2001:db8::380/0121\"\n}\n]\n}\n], \n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"227.184.191.101/32\"\n}, \n{\n\"cidr\": \"160.0.0.0/15.255.255.255\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 81, \n\"endPort\": 84\n}, \n{}, \n{}, \n{\n\"protocol\": \"tcp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:DB8::200/122\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\"\n}, \n{\n\"protocol\": \"any\"\n}\n]\n}\n]\n}"}],"expected":{"allow":["0.0.0.0/0","2001:db8::380/121"],"deny":["160.0.0.0/4","227.184.191.101/32","2001:db8::200/122"]}}, + {"name":"random #9","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #10","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"123.129.205.123/1\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.0/26\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 445\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"port\": 65533, \n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65535\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.64/26\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::27e/127\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::27e/127\", \n\t\t\t\t\t\t\"2001:db8::27f/128\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.24/31\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 65530\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t], \n\t\"deny\": [\n\t\t{\n\t\t\t\"cidr\": \"227.135.130.0/24\", \n\t\t\t\"protocol\": \"udp\"\n\t\t}\n\t]\n}"}],"expected":{"allow":["0.0.0.0/1:icmp","0.0.0.0/1:tcp:445","0.0.0.0/1:tcp:65533-65535","0.0.0.0/1:udp:65533-65535","10.0.0.24/31:tcp:65530","10.0.0.24/31:udp","10.0.2.64/26:tcp:65530","10.0.2.64/26:udp"],"deny":["227.135.130.0/24:udp"]}}, + {"name":"random #11","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::10e1:8a38:20d7:cfc0/64\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.244/32\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"144.0.0.0/4\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.76/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":311\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.96/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":381,\n\t\t\t\"endPort\":384\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.200/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":99,\n\t\t\t\"endPort\":99\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.180/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":27\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::314/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":35\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.232/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":321\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.100/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":293\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.232/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":53,\n\t\t\t\"endPort\":55\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3b0/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":17,\n\t\t\t\"endPort\":18\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.184/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":211,\n\t\t\t\"endPort\":215\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.52/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":1,\n\t\t\t\"endPort\":5\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::f/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":81,\n\t\t\t\"endPort\":84\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.136/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":329,\n\t\t\t\"endPort\":329\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.164/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":61\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.137/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":99,\n\t\t\t\"endPort\":101\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.112/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":313\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::308/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":213,\n\t\t\t\"endPort\":215\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.248/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":127,\n\t\t\t\"endPort\":130\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.254/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":209\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2e5/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":127\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::9b/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":299\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.180/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":377,\n\t\t\t\"endPort\":381\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.104/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":5\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.98/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":261\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.232/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":13,\n\t\t\t\"endPort\":16\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.176/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":41\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.100/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":343,\n\t\t\t\"endPort\":346\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.15/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":215\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.50/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":377,\n\t\t\t\"endPort\":381\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.174/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":205\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.200/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":317\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.176/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":351\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3a4/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":149\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::385/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":361\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.92/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":25\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.224/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":395\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.224/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":331\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.158/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":31\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.104/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":381,\n\t\t\t\"endPort\":381\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.6/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":53\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.68/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":57\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.80/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":179\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.16/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":9\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::36e/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":23\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1dd/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":247\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::389/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":377\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.20/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":355\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.80/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":373\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.150/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":123\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.172/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":65\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.188/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":131\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3cc/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":79\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2dc/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":101,\n\t\t\t\"endPort\":102\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1d2/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":341\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::15f/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":289\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::278/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":235\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.148/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":215\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.178/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":29\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.192/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":399\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.0/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":135,\n\t\t\t\"endPort\":139\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::143/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":253\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.224/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":135\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.114/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":363\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.247/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":367,\n\t\t\t\"endPort\":367\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.140/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":397\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.42/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":337\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::d2/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":31\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.176/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":241\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::38f/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":99\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.26/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":63\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.56/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":279\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::28e/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":9,\n\t\t\t\"endPort\":12\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.204/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":9\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.148/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":83\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::14a/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":133\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.60/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":379\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::206/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":3\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::21e/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":343,\n\t\t\t\"endPort\":343\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.252/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":257,\n\t\t\t\"endPort\":260\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::339/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":91\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1a/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":287\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.102/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":329,\n\t\t\t\"endPort\":329\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3fb/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":83\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.180/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":293,\n\t\t\t\"endPort\":295\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.49/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":53\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.145/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":141\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.136/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":21\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2d1/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":67,\n\t\t\t\"endPort\":70\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.190/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":339\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1a/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":267,\n\t\t\t\"endPort\":268\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.152/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":13\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::215/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":105\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.224/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":5\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.48/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":313\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.58/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":95,\n\t\t\t\"endPort\":95\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.94/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":203\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.245/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":241,\n\t\t\t\"endPort\":245\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.96/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":313\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2a8/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":31\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.118/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":85,\n\t\t\t\"endPort\":85\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.144/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":339\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.62/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":309\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::73/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":53,\n\t\t\t\"endPort\":56\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::30a/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":265,\n\t\t\t\"endPort\":269\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::d4/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":143\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.152/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":289\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.136/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":281,\n\t\t\t\"endPort\":283\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.251/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":53\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.180/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":113\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.16/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":119\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.153/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":335\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.9/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":379\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.98/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":339,\n\t\t\t\"endPort\":343\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.32/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":337\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::16c/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":129,\n\t\t\t\"endPort\":129\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::122/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":327\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.189/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":271\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.118/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":159,\n\t\t\t\"endPort\":162\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.64/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":181\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::f/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":111\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.230/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":241\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.180/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":327\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.244/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":325\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::b8/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":245\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1dd/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":385\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1fd/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":77\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.206/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":395,\n\t\t\t\"endPort\":399\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.88/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":335\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.242/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":3,\n\t\t\t\"endPort\":4\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.149/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":291\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::9c/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":267\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3f0/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":139\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.238/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":329\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::7e/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":209\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.20/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":273,\n\t\t\t\"endPort\":273\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.158/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":213\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::114/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":357\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::392/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":191\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.121/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":207,\n\t\t\t\"endPort\":211\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.96/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":361,\n\t\t\t\"endPort\":362\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::166/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":105\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.24/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":207\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.100/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":301,\n\t\t\t\"endPort\":302\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::131/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":239\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.90/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":185\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.184/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":375\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.168/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":21\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::e3/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":279,\n\t\t\t\"endPort\":282\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.80/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":69\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.8/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":267\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::12/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":107\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::376/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":309,\n\t\t\t\"endPort\":309\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.92/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":285\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.32/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":31\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::230/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":203,\n\t\t\t\"endPort\":204\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.162/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":201\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1b7/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":313\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2a3/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":247\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.228/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":105\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::b7/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":173\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.114/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":185\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.32/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":135\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.61/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":387\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1bd/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":165,\n\t\t\t\"endPort\":168\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.2/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":21\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.148/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":175\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3dc/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":227\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.186/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":161\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.46/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":115\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::d6/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":131\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2fa/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":229\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3c2/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":145,\n\t\t\t\"endPort\":148\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::274/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":209\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::251/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":75\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.170/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":137\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.12/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":329\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::14b/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":15\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.128/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":17\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3b/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":263\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.217/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":365\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.199/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":283\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::66/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":195\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::117/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":173\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.0/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":325\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::ad/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":381\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.194/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":49,\n\t\t\t\"endPort\":53\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.40/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::119/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":357\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.100/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":163\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::10c/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":327,\n\t\t\t\"endPort\":327\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.219/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":295,\n\t\t\t\"endPort\":295\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.160/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":325\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.130/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":15\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.192/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":7\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.168/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":141,\n\t\t\t\"endPort\":145\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.153/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":57,\n\t\t\t\"endPort\":57\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.108/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":381\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.248/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":85\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.184/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":43\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2ca/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2fc/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":311\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::22d/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":203,\n\t\t\t\"endPort\":203\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.230/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":59\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1a1/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":311\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.220/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":119\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.20/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":271\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.172/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":51,\n\t\t\t\"endPort\":55\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":115\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.207/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":81\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::59/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":33\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.177/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":323,\n\t\t\t\"endPort\":327\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1f9/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":339\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2ea/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":349\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::8/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":135\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.148/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":279\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.138/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":159,\n\t\t\t\"endPort\":163\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.224/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":157\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.120/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":143,\n\t\t\t\"endPort\":147\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.186/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":129,\n\t\t\t\"endPort\":131\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.26/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":193\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.32/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":43,\n\t\t\t\"endPort\":44\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::370/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":257\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::135/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":221\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.176/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":71\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3fa/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":363\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2ef/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":385\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.228/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":203\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::26e/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":37,\n\t\t\t\"endPort\":39\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.148/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":177\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.0/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":253\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.180/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":217\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::7c/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":81,\n\t\t\t\"endPort\":84\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.68/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":315,\n\t\t\t\"endPort\":318\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.26/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":283\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::298/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":27\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.220/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":299\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.140/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":293,\n\t\t\t\"endPort\":297\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.200/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":37\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.122/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":3\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.210/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":81\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.64/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":267,\n\t\t\t\"endPort\":270\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.104/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":261,\n\t\t\t\"endPort\":263\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.210/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":313\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::205/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":227\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.216/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":173,\n\t\t\t\"endPort\":176\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::235/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":59\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.208/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":129\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.95/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":7\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.152/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":9\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.35/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":41\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.80/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":69,\n\t\t\t\"endPort\":71\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1cd/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":287\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.244/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":393,\n\t\t\t\"endPort\":393\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::39a/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":171,\n\t\t\t\"endPort\":174\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::5b/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":45,\n\t\t\t\"endPort\":49\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::253/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":365\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.106/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":225\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::d/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":183,\n\t\t\t\"endPort\":186\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::b7/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":171\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::18d/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":333\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::56/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":97\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1f8/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":285\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.207/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":311\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.192/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":215\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::95/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":371,\n\t\t\t\"endPort\":371\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.21/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":329,\n\t\t\t\"endPort\":330\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.76/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":383\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::f5/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":79\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.88/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":251\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::127/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":301,\n\t\t\t\"endPort\":305\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.176/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":291\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::19f/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":83,\n\t\t\t\"endPort\":84\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.255/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":109\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.220/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":239\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.88/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":263,\n\t\t\t\"endPort\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.192/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":189\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::270/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":349\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.164/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":95\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1a1/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":109\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.26/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":357\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.40/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":45\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::bc/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":231,\n\t\t\t\"endPort\":234\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::16/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":141\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.90/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":109,\n\t\t\t\"endPort\":112\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.167/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":53\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.85/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":263\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.26/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":109\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.136/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":35\n\t\t}\n\t],\n\t\"deny\":[\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.32/255.255.255.240\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":65532,\n\t\t\t\"endPort\":65535\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.250/31\",\n\t\t\t\"except\":[],\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":78,\n\t\t\t\"endPort\":33847\n\t\t}\n\t]\n}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"random #12","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::280/122\", \"except\": [], \"protocol\": \"any\", \"port\": 80, \"endPort\": 83}, {\"cidr\": \"10.0.2.64/26\", \"protocol\": \"any\", \"port\": 65530}, {\"cidr\": \"10.0.2.0/26\", \"except\": [\"10.0.2.32/28\", \"10.0.2.4/30\"], \"protocol\": \"udp\", \"port\": 10, \"endPort\": 11}, {\"to\": [{\"cidr\": \"10.0.2.119\"}, {\"cidr\": \"2001:db8::194/127\"}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"udp\", \"port\": 4}, {\"protocol\": \"tcp\", \"port\": 65532}]}], \"deny\": [{\"cidr\": \"10.0.3.128/25\", \"except\": [\"10.0.3.128/28\"], \"protocol\": \"udp\", \"port\": 6, \"endPort\": 8}, {\"cidr\": \"42.49.72.171/1\", \"protocol\": \"tcp\", \"port\": 65533, \"endPort\": 65534}]}"}],"expected":{"allow":["10.0.2.0/30:udp:10-11","10.0.2.8/29:udp:10-11","10.0.2.16/28:udp:10-11","10.0.2.48/28:udp:10-11","10.0.2.64/26:tcp:65530","10.0.2.64/26:udp:65530","10.0.2.119/32:tcp","10.0.2.119/32:udp:4","2001:db8::194/127:tcp","2001:db8::194/127:udp:4","2001:db8::280/122:tcp:80-83","2001:db8::280/122:udp:80-83"],"deny":["0.0.0.0/1:tcp:65533-65534","10.0.3.144/28:udp:6-8","10.0.3.160/27:udp:6-8","10.0.3.192/26:udp:6-8"]}}, + {"name":"random #13","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"0.0.0.0/0\"\n}, \n{\n\"cidr\": \"10.0.0.213/27\"\n}, \n{\n\"cidr\": \"10.0.0.84/23\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 443\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65530, \n\"endPort\": 65532\n}, \n{\n\"protocol\": \"tcp\"\n}\n]\n}, \n{\n\"cidr\": \"101.171.32.96\", \n\"protocol\": \"tcp\", \n\"port\": 65531\n}, \n{\n\"cidr\": \"2001:db8::22b/123\", \n\"protocol\": \"icmp\"\n}, \n{\n\"cidr\": \"2001:db8::238/125\", \n\"protocol\": \"tcp\", \n\"port\": 8\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.93/24\"\n}, \n{\n\"cidr\": \"2001:db8::/118\", \n\"except\": [\n\"2001:db8::200/119\", \n\"2001:db8::1a3/123\", \n\"2001:0db8:0000:0000:0000:0000:0000:02a0/125\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 65533, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 444, \n\"endPort\": 445\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 12, \n\"endPort\": 16\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65533\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/118\", \n\"except\": [\n\"2001:db8::138/126\"\n]\n}\n]\n}\n], \n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:02e4/124\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 65531\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65533\n}\n]\n}, \n{\n\"cidr\": \"10.0.1.32/27\", \n\"except\": [\n\"10.0.1.60/30\", \n\"10.0.1.48/30\", \n\"10.0.1.60/30\"\n], \n\"protocol\": \"any\", \n\"port\": 7\n}\n]\n}"}],"expected":{"allow":["0.0.0.0/0:tcp","0.0.0.0/0:udp:65530-65532","10.0.0.0/24:udp:12-16","2001:db8::/118:tcp:444-445","2001:db8::/118:tcp:65533-65535","2001:db8::/118:udp:12-16","2001:db8::/120","2001:db8::100/123","2001:db8::120/124","2001:db8::130/125","2001:db8::13c/126","2001:db8::140/122","2001:db8::180/121","2001:db8::200/119"],"deny":["10.0.1.32/28:tcp:7","10.0.1.32/28:udp:7","10.0.1.52/30:tcp:7","10.0.1.52/30:udp:7","10.0.1.56/30:tcp:7","10.0.1.56/30:udp:7","2001:db8::2e0/124:udp:65531","2001:db8::2e0/124:udp:65533"]}}, + {"name":"random #14","input":[{"text":"null"}],"expected":{"error":"egress policy must be an object"}}, + {"name":"random #15","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"685a:6abb:5ae6:c85f:f2ad:5c59:8f00:0/104\"},{\"cidr\":\"10.0.3.73/30\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":65532},{\"protocol\":\"icmp\"}]}]}"}],"expected":{"allow":["10.0.3.72/30:icmp","10.0.3.72/30:tcp:65532","685a:6abb:5ae6:c85f:f2ad:5c59:8f00:0/104:icmp","685a:6abb:5ae6:c85f:f2ad:5c59:8f00:0/104:tcp:65532"],"deny":[]}}, + {"name":"random #16","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::330/124\"}, {\"cidr\": \"10.0.1.200/30\"}, {\"cidr\": \"2001:db8::260/126\"}, {\"cidr\": \"8036:e4bb:7d0a:b04d:9b26:b335:4600:0/000104\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"144.0.0.0/15.255.255.255\"}, {\"cidr\": \"10.0.0.0/24\"}, {\"cidr\": \"2001:db8::48/000127\", \"except\": []}]}, {\"cidr\": \"10.0.0.168/29\", \"protocol\": \"any\", \"port\": 2}, {\"cidr\": \"164.72.221.0/24\", \"protocol\": \"udp\", \"port\": 65532}, {\"to\": [{\"cidr\": \"10.0.1.96/0028\"}, {\"cidr\": \"10.0.0.158/00025\"}, {\"cidr\": \"2001:DB8::200/121\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:0261/124\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0263/120\", \"except\": [\"2001:db8::2e0/125\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65535, \"endPort\": 65535}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"10.0.1.74/31\"}, {\"cidr\": \"2001:db8::/118\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"port\": 65531, \"endPort\": 65531}, {\"protocol\": \"any\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"87dc:e63e:601c:436f:9dd6:226c:43df:7700/120\"}, {\"cidr\": \"2001:db8::1a0/123\"}, {\"cidr\": \"2001:db8::332/120\", \"except\": [\"2001:db8::380/00121\", \"2001:db8::3f0/124\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 8}, {\"protocol\": \"udp\"}, {\"port\": 82, \"endPort\": 24693}, {\"protocol\": \"tcp\", \"port\": 12, \"endPort\": 18}]}, {\"to\": [{\"cidr\": \"::/00\"}, {\"cidr\": \"2001:db8::201/118\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 7, \"endPort\": 12}]}]}"}],"expected":{"allow":["10.0.0.0/24","10.0.1.74/31","10.0.1.96/28:tcp","10.0.1.200/30:icmp","10.0.1.200/30:tcp","144.0.0.0/4","164.72.221.0/24:udp:65532","2001:db8::/118","8036:e4bb:7d0a:b04d:9b26:b335:4600:0/104:icmp","8036:e4bb:7d0a:b04d:9b26:b335:4600:0/104:tcp"],"deny":["::/0:tcp:7-12","2001:db8::1a0/123:tcp:13-18","2001:db8::1a0/123:tcp:82-24693","2001:db8::1a0/123:udp","2001:db8::300/121:tcp:13-18","2001:db8::300/121:tcp:82-24693","2001:db8::300/121:udp","87dc:e63e:601c:436f:9dd6:226c:43df:7700/120:tcp:13-18","87dc:e63e:601c:436f:9dd6:226c:43df:7700/120:tcp:82-24693","87dc:e63e:601c:436f:9dd6:226c:43df:7700/120:udp"]}}, + {"name":"random #17","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.2.0/25\", \"protocol\": \"any\"}, {\"cidr\": \"2001:db8::134/127\", \"except\": [\"2001:db8::134/127\", \"2001:db8::134/127\"], \"protocol\": \"tcp\", \"port\": 80, \"endPort\": 86}, {\"to\": [{\"cidr\": \"10.0.3.192/255.255.255.192\"}, {\"cidr\": \"10.0.0.0/22\", \"except\": [\"10.0.0.117/29\", \"10.0.2.24/29\", \"10.0.0.0/22\"]}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"24de:1653:d6d3:934f:b0c9:ccc9:915f:d2ae\", \"except\": []}, {\"cidr\": \"2001:db8::3c2/127\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:03c2/127\", \"2001:db8::3c3\", \"2001:db8::3c3\"]}, {\"cidr\": \"2001:db8::80/122\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 80, \"endPort\": 86}]}, {\"to\": [{\"cidr\": \"10.0.2.240/255.255.255.240\"}]}, {\"cidr\": \"180.214.63.179/12\", \"except\": [\"180.208.0.0/12\", \"180.209.29.38/20\", \"180.221.192.0/255.255.224.0\"], \"protocol\": \"tcp\", \"port\": 1}, {\"to\": [{\"cidr\": \"10.0.3.138/23\"}, {\"cidr\": \"10.0.1.168/30\", \"except\": []}, {\"cidr\": \"10.0.2.136/0031\"}, {\"cidr\": \"10.0.1.128/25\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 445}, {\"protocol\": \"udp\", \"port\": 65534}]}, {\"to\": [{\"cidr\": \"2001:db8::/119\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:002a/127\", \"2001:DB8::113/119\", \"2001:db8::15a/127\"]}, {\"cidr\": \"10.0.2.0/23\", \"except\": []}, {\"cidr\": \"ac71:2d35:69a9:330:c439:61ad:5200:0/104\", \"except\": [\"ac71:2d35:69a9:330:c439:61ad:5200:0/105\", \"AC71:2D35:69A9:330:C439:61AD:5200:0/105\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 441, \"endPort\": 443}, {\"protocol\": \"tcp\", \"port\": 441}, {\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0040/122\"}, {\"cidr\": \"fae1:2117:c59b:f7db:52d2:4868::/96\"}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0320/123\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"tcp\"}, {\"protocol\": \"udp\", \"port\": 12, \"endPort\": 9561}]}, {\"to\": [{\"cidr\": \"10.0.1.204/31\"}, {\"cidr\": \"10.0.3.108/023\"}]}, {\"to\": [{\"cidr\": \"10.0.2.0/24\", \"except\": [\"10.0.2.15/25\"]}, {\"cidr\": \"153.47.0.0/16\", \"except\": [\"153.47.150.5/019\", \"153.47.0.0/00016\"]}, {\"cidr\": \"10.0.2.224/29\", \"except\": []}, {\"cidr\": \"e63b:3ea2:2d1:66c::/64\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 2, \"endPort\": 3}]}]}"}],"expected":{"allow":["10.0.1.128/25:tcp:445","10.0.1.128/25:udp:65534","10.0.2.0/23:icmp","10.0.2.0/23:tcp:441-443","10.0.2.0/23:tcp:445","10.0.2.0/23:udp:65534","10.0.2.0/25","10.0.2.240/28","2001:db8::80/122:udp:80-86","24de:1653:d6d3:934f:b0c9:ccc9:915f:d2ae/128:udp:80-86","ac71:2d35:69a9:330:c439:61ad:5280:0/105:icmp","ac71:2d35:69a9:330:c439:61ad:5280:0/105:tcp:441-443"],"deny":["10.0.1.204/31","10.0.2.0/23","2001:db8::40/122:tcp","2001:db8::40/122:udp","2001:db8::320/123:tcp","2001:db8::320/123:udp","e63b:3ea2:2d1:66c::/64:udp:2-3","fae1:2117:c59b:f7db:52d2:4868::/96:tcp","fae1:2117:c59b:f7db:52d2:4868::/96:udp"]}}, + {"name":"random #18","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.0.173/32\"}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:00f8/125\", \"except\": []}, {\"cidr\": \"50.208.0.0/12\"}, {\"cidr\": \"6a98:8f87:c846:8913:adf5:b318:3f00:0/104\"}], \"ports\": [{\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"::/0\"}, {\"cidr\": \"10.0.0.145/26\"}, {\"cidr\": \"10.0.0.43/031\", \"except\": []}], \"ports\": [{\"port\": 79, \"endPort\": 59551}, {\"protocol\": \"tcp\"}]}, {\"cidr\": \"2001:db8::e8/120\", \"protocol\": \"any\", \"port\": 8, \"endPort\": 11}, {\"cidr\": \"2001:db8::/118\"}, {\"to\": [{\"cidr\": \"2001:db8::/00118\"}, {\"cidr\": \"10.0.0.218/255.255.255.224\", \"except\": [\"10.0.0.216/29\", \"10.0.0.209/32\", \"10.0.0.192/29\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 4}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 65530}]}, {\"to\": [{\"cidr\": \"10.0.1.64/26\"}, {\"cidr\": \"2001:db8::300/120\"}]}], \"deny\": [{\"cidr\": \"10.0.2.0/0024\", \"except\": [], \"protocol\": \"tcp\"}, {\"to\": [{\"cidr\": \"10.0.3.0/24\"}, {\"cidr\": \"10.0.3.63/27\"}, {\"cidr\": \"10.0.0.192/27\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 444}]}, {\"to\": [{\"cidr\": \"10.0.0.192/26\", \"except\": []}, {\"cidr\": \"10.0.2.0/026\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 65530}]}, {\"to\": [{\"cidr\": \"10.0.3.172/30\", \"except\": [\"10.0.3.175\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0140/122\"}, {\"cidr\": \"10.0.2.11/026\", \"except\": []}], \"ports\": [{\"protocol\": \"udp\", \"port\": 65533, \"endPort\": 65535}]}, {}, {\"ports\": [{\"port\": 2}]}]}"}],"expected":{"allow":["10.0.0.42/31:tcp","10.0.0.42/31:udp:79-59551","10.0.0.128/26:tcp","10.0.0.128/26:udp:79-59551","10.0.0.200/29:tcp:4","10.0.0.200/29:tcp:65530","10.0.0.200/29:udp","10.0.0.208/32:tcp:4","10.0.0.208/32:tcp:65530","10.0.0.208/32:udp","10.0.0.210/31:tcp:4","10.0.0.210/31:tcp:65530","10.0.0.210/31:udp","10.0.0.212/30:tcp:4","10.0.0.212/30:tcp:65530","10.0.0.212/30:udp","10.0.1.64/26","50.208.0.0/12:tcp","::/0:tcp","::/0:udp:79-59551","2001:db8::/118"],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"random #19","input":[{"text":"{\n\"allow\":[\n{\n\"to\":[\n{\n\"cidr\":\"124.71.0.0/16\"\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"10.0.3.20/31\"\n},\n{\n\"cidr\":\"10.0.0.0/24\"\n},\n{\n\"cidr\":\"2001:db8::3aa/127\",\n\"except\":[\n\"2001:db8::3aa/127\",\n\"2001:db8::3aa/127\",\n\"200"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #20","input":[{"text":"{\n\"allow\": [\n{\n\"cidr\": \"2001:db8::2ea/120\", \n\"except\": [\n\"2001:db8::268/127\"\n], \n\"protocol\": \"icmp\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::/118\"\n}, \n{\n\"cidr\": \"2001:db8::70/122\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 442\n}\n]\n}, \n{\n\"cidr\": \"10.0.2.235/32\", \n\"protocol\": \"any\", \n\"port\": 3, \n\"endPort\": 9\n}\n], \n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0010/126\"\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/00119\"\n}, \n{\n\"cidr\": \"2001:db8::/120\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 82, \n\"endPort\": 86\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65532\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 82\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/255.255.252.0\", \n\"except\": [\n\"10.0.0.0/22\", \n\"10.0.2.128/26\"\n]\n}, \n{\n\"cidr\": \"10.0.2.228/0029\"\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01c0/123\"\n}, \n{\n\"cidr\": \"10.0.3.128/26\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 6\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65534, \n\"endPort\": 65535\n}, \n{}, \n{\n\"protocol\": \"udp\", \n\"port\": 78\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.24/30\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"160.112.0.0/12\", \n\"except\": [\n\"160.112.0.0/12\"\n]\n}, \n{\n\"cidr\": \"10.0.1.16/29\"\n}, \n{\n\"cidr\": \"2001:db8::213/121\"\n}, \n{\n\"cidr\": \"10.0.1.0/27\", \n\"except\": [\n\"10.0.1.0/27\"\n]\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:DB8::308/125\"\n}, \n{\n\"cidr\": \"10.0.3.128/26\"\n}, \n{\n\"cidr\": \"2001:DB8::C0/125\"\n}, \n{\n\"cidr\": \"23c6:569d:a646:d784:5f35:7ca6:8452:43ec/096\"\n}\n], \n\"ports\": [\n{\n\"port\": 445, \n\"endPort\": 14651\n}, \n{\n\"protocol\": \"any\", \n\"port\": 5\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.184/32\", \n\"except\": [\n\"10.0.0.184\"\n]\n}, \n{\n\"cidr\": \"78.166.151.141\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 79\n}, \n{\n\"port\": 441\n}\n]\n}\n]\n}"}],"expected":{"allow":["10.0.2.235/32:tcp:3-9","10.0.2.235/32:udp:3-9","2001:db8::/118:tcp:442","2001:db8::/118:udp:442","2001:db8::200/122:icmp","2001:db8::240/123:icmp","2001:db8::260/125:icmp","2001:db8::26a/127:icmp","2001:db8::26c/126:icmp","2001:db8::270/124:icmp","2001:db8::280/121:icmp"],"deny":["10.0.1.16/29","10.0.2.224/29","10.0.3.24/30","10.0.3.128/26","78.166.151.141/32:tcp:441","78.166.151.141/32:udp:79","78.166.151.141/32:udp:441","2001:db8::/119:tcp","2001:db8::/119:udp:82","2001:db8::/119:udp:65532","2001:db8::c0/125:icmp","2001:db8::c0/125:udp:5","2001:db8::c0/125:udp:445-14651","2001:db8::1c0/123","2001:db8::200/121","2001:db8::308/125:icmp","2001:db8::308/125:tcp:5","2001:db8::308/125:tcp:445-14651","2001:db8::308/125:udp:5","2001:db8::308/125:udp:445-14651","23c6:569d:a646:d784:5f35:7ca6::/96:icmp","23c6:569d:a646:d784:5f35:7ca6::/96:tcp:5","23c6:569d:a646:d784:5f35:7ca6::/96:tcp:445-14651","23c6:569d:a646:d784:5f35:7ca6::/96:udp:5","23c6:569d:a646:d784:5f35:7ca6::/96:udp:445-14651"]}}, + {"name":"random #21","input":[{"text":"{\n \"allow\": [],\n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::365/120\",\n \"except\": [\n \"2001:DB8::370/124\",\n \"2001:db8::38a/123\"\n ]\n },\n {\n \"cidr\": \"10.0.0.44/30\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"tcp\",\n \"port\": 444\n },\n {\n \"protocol\": \"tcp\"\n },\n {\n \"protocol\": \"udp\",\n \"port\": 82\n },\n {\n \"protocol\": \"udp\"\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"64.0.0.0/192.0.0.0\"\n },\n {\n \"cidr\": \"10.0.1.60/30\",\n \"except\": [\n \"10.0.1.60/30\",\n \"10.0.1.60/30\"\n ]\n },\n {\n \"cidr\": \"212.207.126.176/31\"\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.208/28\"\n },\n {\n \"cidr\": \"10.0.0.0/24\"\n },\n {\n \"cidr\": \"::ffff:66.229.241.167/128\"\n },\n {\n \"cidr\": \"2001:db8::1c0/124\"\n }\n ]\n }\n ]\n}"}],"expected":{"allow":[],"deny":["10.0.0.0/24","64.0.0.0/2","212.207.126.176/31","::ffff:66.229.241.167/128","2001:db8::1c0/124","2001:db8::300/122:tcp","2001:db8::300/122:udp","2001:db8::340/123:tcp","2001:db8::340/123:udp","2001:db8::360/124:tcp","2001:db8::360/124:udp","2001:db8::3a0/123:tcp","2001:db8::3a0/123:udp","2001:db8::3c0/122:tcp","2001:db8::3c0/122:udp"]}}, + {"name":"random #22","input":[{"text":"null"}],"expected":{"error":"egress policy must be an object"}}, + {"name":"random #23","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.56/125\", \"protocol\": \"any\", \"port\": 65534}, {\"cidr\": \"10.0.0.0/24\", \"protocol\": \"udp\", \"port\": 11, \"endPort\": 13}]}"}],"expected":{"allow":["10.0.0.0/24:udp:11-13","2001:db8::38/125:tcp:65534","2001:db8::38/125:udp:65534"],"deny":[]}}, + {"name":"random #24","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::/119\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0.0.1.128/125\"]}, {\"cidr\": \"93.140.2.127/32\", \"except\": [\"93.140.2.127\", \"93.140.2.127/255.255.255.255\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 444, \"endPort\": 444}]}, {\"to\": [{\"cidr\": \"28.21.64.44/31\"}], \"ports\": [{\"port\": 82, \"endPort\": 84}, {\"protocol\": \"any\", \"port\": 443}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"3320:7deb:4796:b264:9f7:369d:8a21:aca4/00128\"}, {\"cidr\": \"10.0.2.0/24\"}, {\"cidr\": \"210.240.171.178/8\"}, {\"cidr\": \"10.0.0.130\", \"except\": [\"10.0.0.130\", \"10.0.0.130/32\", \"10.0.0.130/32\"]}]}, {\"to\": [{\"cidr\": \"10.0.3.192/0.0.0.63\", \"except\": [\"10.0.3.220/31\"]}, {\"cidr\": \"2001:db8::9d/126\"}, {\"cidr\": \"2001:db8::/118\", \"except\": []}, {\"cidr\": \"10.0.2.156/29\", \"except\": [\"10.0.2.152/00031\", \"10.0.2.153/29\", \"10.0.2.152/29\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 12}], \"cidr\": \"192.0.2.0/24\"}, {\"to\": [{\"cidr\": \"2001:db8::2c0/126\", \"except\": [\"2001:db8::2c0/000128\", \"2001:0db8:0000:0000:0000:0000:0000:02c2\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 445}, {\"protocol\": \"icmp\"}]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0100/120\", \"protocol\": \"any\", \"port\": 5, \"endPort\": 10}]}"}],"expected":{"error":"deny[3] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #25","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.192/28\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::/118\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.104/31\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.39/27\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 9, \n\t\t\t\t\t\"endPort\": 13\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"port\": 8, \n\t\t\t\t\t\"endPort\": 11\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 78\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65531, \n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/0022\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.160/255.255.255.254\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.86/23\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::2e8/127\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 79, \n\t\t\t\t\t\"endPort\": 83\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 65531, \n\t\t\t\t\t\"endPort\": 65532\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"2001:db8::16a/119\", \n\t\t\t\"except\": [\n\t\t\t\t\"2001:db8::8c/126\"\n\t\t\t], \n\t\t\t\"protocol\": \"any\", \n\t\t\t\"port\": 444\n\t\t}\n\t], \n\t\"deny\": [\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 443\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 7, \n\t\t\t\t\t\"endPort\": 11\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.64/31\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::100/123\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"::ac56:e339:cff7:b80e/064\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 78, \n\t\t\t\t\t\"endPort\": 7012\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"::/0\", \n\t\t\t\"protocol\": \"icmp\"\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.0.0/22:tcp:79-83","10.0.0.0/22:udp:65531-65532","10.0.0.32/27:tcp:8-13","10.0.0.32/27:tcp:65531-65535","10.0.0.32/27:udp:8-11","10.0.0.32/27:udp:78","10.0.0.32/27:udp:65533-65535","10.0.1.192/28:tcp:8-13","10.0.1.192/28:tcp:65531-65535","10.0.1.192/28:udp:8-11","10.0.1.192/28:udp:78","10.0.1.192/28:udp:65533-65535","10.0.2.104/31:tcp:8-13","10.0.2.104/31:tcp:65531-65535","10.0.2.104/31:udp:8-11","10.0.2.104/31:udp:78","10.0.2.104/31:udp:65533-65535","2001:db8::/118:tcp:8-13","2001:db8::/118:tcp:65531-65535","2001:db8::/118:udp:8-11","2001:db8::/118:udp:78","2001:db8::/118:udp:65531-65535","2001:db8::/121:tcp:444","2001:db8::/121:udp:444","2001:db8::80/125:tcp:444","2001:db8::80/125:udp:444","2001:db8::88/126:tcp:444","2001:db8::88/126:udp:444","2001:db8::90/124:tcp:444","2001:db8::90/124:udp:444","2001:db8::a0/123:tcp:444","2001:db8::a0/123:udp:444","2001:db8::c0/122:tcp:444","2001:db8::c0/122:udp:444","2001:db8::100/120:tcp:444","2001:db8::100/120:udp:444","2001:db8::2e8/127:tcp:79-83"],"deny":["0.0.0.0/0:tcp","0.0.0.0/0:udp:7-11","0.0.0.0/0:udp:443","10.0.3.64/31:udp","::/0:icmp","::/0:tcp","::/0:udp:7-11","::/0:udp:443","::/64:udp","2001:db8::100/123:udp"]}}, + {"name":"random #26","input":[{"text":"{\n\t\"allow\":[],\n\t\"deny\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.0/24\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.3.216/29\",\n\t\t\t\t\t\t\"10.0.3.0/25\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::218/121\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.144/28\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.224/31\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":81\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":3,\n\t\t\t\t\t\"endPort\":9\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":442,\n\t\t\t\t\t\"endPort\":447\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::3c0/122\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.0/24\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.2.16/29\",\n\t\t\t\t\t\t\"10.0.2.128/25\",\n\t\t\t\t\t\t\"10.0.2.128/0.0.0.31\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.0/23\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65530\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65535,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.253/255.255.255.224\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"255.0.0.0/8\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"6a94:2ed0:8277:bd3e:2b9e:36b4:cc8b:53c3/64\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\",\n\t\t\t\t\t\"port\":1\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.1.176/28\",\n\t\t\t\t\t\t\"10.0.2.0/25\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[4].ports[0].port is not supported with icmp"}}, + {"name":"random #27","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::3be/0125\", \"except\": [\"2001:db8::3b8/125\", \"2001:db8::3b8/126\"]}, {\"cidr\": \"10.0.3.128/25\"}, {\"cidr\": \"2001:db8::190/124\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:0190/126\", \"2001:db8::194/126\"]}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"udp\", \"port\": 441}, {\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"161.45.215.27/8\"}, {\"cidr\": \"::/3\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65531}, {\"protocol\": \"icmp\"}]}, {\"cidr\": \"10.0.2.176/28\", \"protocol\": \"tcp\"}, {\"cidr\": \"10.0.3.151/0.0.0.255\", \"except\": [\"10.0.3.11/30\", \"10.0.3.192/00030\"], \"protocol\": \"any\", \"port\": 81}, {\"to\": [{\"cidr\": \"10.0.2.0/27\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65535}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\"}, {\"protocol\": \"udp\", \"port\": 444}]}, {\"to\": [{\"cidr\": \"2001:db8::/118\", \"except\": [\"2001:db8::380/121\", \"2001:db8::100/120\", \"2001:db8::2a8/0126\"]}, {\"cidr\": \"10.0.0.0/0024\"}, {\"cidr\": \"::ffff:71.24.10.244\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 444}, {\"protocol\": \"tcp\"}, {\"protocol\": \"udp\", \"port\": 7, \"endPort\": 13}, {\"protocol\": \"any\", \"port\": 65531, \"endPort\": 65535}]}, {\"cidr\": \"10.0.3.208/28\", \"except\": [\"10.0.3.208/31\", \"10.0.3.208/28\"], \"protocol\": \"icmp\", \"port\": 65530}, {\"ports\": [{\"protocol\": \"any\", \"port\": 443, \"endPort\": 449}]}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.1.0/255.255.255.0\"}, {\"cidr\": \"176.0.0.0/4\"}, {\"cidr\": \"2001:db8::/123\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 441, \"endPort\": 444}, {\"protocol\": \"udp\", \"port\": 11}, {\"protocol\": \"udp\", \"port\": 1, \"endPort\": 2}, {\"protocol\": \"udp\"}]}, {\"cidr\": \"2001:db8::37a/127\", \"except\": [\"2001:DB8::37A/128\"], \"protocol\": \"tcp\"}, {\"cidr\": \"10.0.2.32/23\", \"protocol\": \"any\"}, {\"to\": [{\"cidr\": \"2001:db8::/118\", \"except\": []}]}, {\"cidr\": \"10.0.3.128/27\", \"protocol\": \"udp\"}, {\"to\": [{\"cidr\": \"2001:db8::/118\", \"except\": []}, {\"cidr\": \"10.0.3.64/27\", \"except\": [\"10.0.3.78/255.255.255.248\", \"10.0.3.80/28\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:022e/121\"}], \"ports\": [{\"port\": 442}, {\"protocol\": \"udp\", \"port\": 12, \"endPort\": 15}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"2001:db8::f3/125\", \"except\": [\"2001:db8::f0/125\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 78, \"endPort\": 78}, {\"protocol\": \"tcp\", \"port\": 81}]}, {\"to\": [{\"cidr\": \"2001:db8::2f9/128\"}, {\"cidr\": \"10.0.2.64/00026\", \"except\": [\"10.0.2.119/029\", \"10.0.2.64/00026\"]}, {\"cidr\": \"10.0.3.253/29\"}]}]}"}],"expected":{"error":"allow[6].port is not supported with icmp"}}, + {"name":"random #28","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"54.200.177.0/24\"}]},{\"to\":[{\"cidr\":\"10.0.3.48/30\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":9},{\"protocol\":\"udp\"}]},{\"cidr\":\"bd9:afaa:6fa3:fdaa:7e4d:87e6:a1b7:4118/127\",\"protocol\":\"any\",\"port\":441,\"endPort\":445}],\"deny\":[{\"to\":[{\"cidr\":\"2001:db8::3c0/123\"}],\"ports\":[{\"port\":1}]},{\"to\":[{\"cidr\":\"7cd2:8f88:1ffb:c6fb:8729:3774:7c18:e658/3\"}],\"ports\":[{\"protocol\":\"any\",\"port\":10,\"endPort\":13},{\"protocol\":\"any\"}]},{\"to\":[{\"cidr\":\"2"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #29","input":[{"text":"{\"deny\":[{\"cidr\":\"10.0.2.136/30\",\"protocol\":\"udp\"},{\"to\":[{\"cidr\":\"10.0.0.0/22\"},{\"cidr\":\"2001:db8::2b4/120\",\"except\":[\"2001:db8::2d8/127\",\"2001:0db8:0000:0000:0000:0000:0000:0230/124\"]}],\"ports\":[{\"protocol\":\"any\"},{\"protocol\":\"udp\",\"port\":10,\"endPort\":14}]},{\"cidr\":\"2001:db8::148/126\"},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0300/122\",\"except\":[\"2001:db8::31b/000128\"],\"protocol\":\"udp\",\"port\":9},{\"to\":[{\"cidr\":\"2001:db8::390/125\"},{\"cidr\":\"10.0.1.32/28\",\"except\":[]},{\"cidr\":\"10.0.3.27/25\",\"except\":[]},{\"cidr\":\"10.0.1.144/30\",\"except\":[\"10.0.1.145\"]}],\"ports\":[{\"protocol\":\"icmp\"},{\"protocol\":\"tcp\",\"port\":65535,\"endPort\":65535},{\"protocol\":\"icmp\"}]},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:021e/124\"}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"icmp\"}]}]}"}],"expected":{"allow":[],"deny":["10.0.0.0/22","2001:db8::148/126","2001:db8::200/123","2001:db8::220/124","2001:db8::240/122","2001:db8::280/122","2001:db8::2c0/124","2001:db8::2d0/125","2001:db8::2da/127","2001:db8::2dc/126","2001:db8::2e0/123","2001:db8::300/124:udp:9","2001:db8::310/125:udp:9","2001:db8::318/127:udp:9","2001:db8::31a/128:udp:9","2001:db8::31c/126:udp:9","2001:db8::320/123:udp:9","2001:db8::390/125:icmp","2001:db8::390/125:tcp:65535"]}}, + {"name":"random #30","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"2001:db8::2a0/0126\", \"except\": []}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 2}, {\"protocol\": \"any\"}, {\"protocol\": \"tcp\", \"port\": 443}, {\"protocol\": \"tcp\", \"port\": 5, \"endPort\": 8}]}, {\"cidr\": \"2001:db8::22f/125\", \"except\": [\"2001:db8::22c/126\", \"2001:0db8:0000:0000:0000:0000:0000:022a/127\"], \"protocol\": \"icmp\"}, {\"to\": [{\"cidr\": \"65.120.242.163/32\"}, {\"cidr\": \"10.0.1.0/0.0.0.63\", \"except\": [\"10.0.1.48/29\"]}], \"ports\": [{\"protocol\": \"any\", \"port\": 65533}, {\"protocol\": \"any\", \"port\": 443, \"endPort\": 444}, {}]}, {\"cidr\": \"2001:db8::340/123\", \"protocol\": \"tcp\", \"port\": 4}, {\"cidr\": \"2001:db8::80/123\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:0080/124\", \"2001:db8::9b/128\"], \"protocol\": \"any\", \"port\": 441, \"endPort\": 446}, {\"cidr\": \"2001:db8::200/120\", \"except\": [\"2001:db8::2c1/127\"], \"protocol\": \"any\", \"port\": 65531, \"endPort\": 65535}, {\"to\": [{\"cidr\": \"10.0.3.192/027\"}], \"ports\": [{\"protocol\": \"any\"}, {\"protocol\": \"tcp\", \"port\": 65532, \"endPort\": 65535}]}, {\"cidr\": \"b188:0db1:55a4:30a1:eae3:4b5f:23f7:c193/120\", \"protocol\": \"udp\"}]}"}],"expected":{"allow":[],"deny":["10.0.1.0/27","10.0.1.32/28","10.0.1.56/29","10.0.3.192/27","65.120.242.163/32","2001:db8::90/125:tcp:441-446","2001:db8::90/125:udp:441-446","2001:db8::98/127:tcp:441-446","2001:db8::98/127:udp:441-446","2001:db8::9a/128:tcp:441-446","2001:db8::9a/128:udp:441-446","2001:db8::9c/126:tcp:441-446","2001:db8::9c/126:udp:441-446","2001:db8::200/121:tcp:65531-65535","2001:db8::200/121:udp:65531-65535","2001:db8::228/127:icmp","2001:db8::280/122:tcp:65531-65535","2001:db8::280/122:udp:65531-65535","2001:db8::2a0/126","2001:db8::2c2/127:tcp:65531-65535","2001:db8::2c2/127:udp:65531-65535","2001:db8::2c4/126:tcp:65531-65535","2001:db8::2c4/126:udp:65531-65535","2001:db8::2c8/125:tcp:65531-65535","2001:db8::2c8/125:udp:65531-65535","2001:db8::2d0/124:tcp:65531-65535","2001:db8::2d0/124:udp:65531-65535","2001:db8::2e0/123:tcp:65531-65535","2001:db8::2e0/123:udp:65531-65535","2001:db8::340/123:tcp:4","b188:db1:55a4:30a1:eae3:4b5f:23f7:c100/120:udp"]}}, + {"name":"random #31","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.3.16/28\", \"protocol\": \"tcp\", \"port\": 79}]}"}],"expected":{"allow":["10.0.3.16/28:tcp:79"],"deny":[]}}, + {"name":"random #32","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3e2/123\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 442\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.235/121\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.228/255.255.255.254\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.0.228\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::190/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::3e0/123\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.64/026\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"0fe7:2fe6:f2e7:d03c:0000:0000:0000:0000/64\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 12\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 65531\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 81\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"port\": 79,\n\t\t\t\t\t\"endPort\": 83\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 441\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 79\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.222/23\",\n\t\t\t\"protocol\": \"icmp\"\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.62/23\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.32/0.0.0.15\",\n\t\t\t\"protocol\": \"icmp\"\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.8/30\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 65530\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::6c/126\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 65534\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.119/32\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 65532\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 79,\n\t\t\t\t\t\"endPort\": 79\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::358/127\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.0/26\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"29.95.35.240/31\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"port\": 4,\n\t\t\t\t\t\"endPort\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 65532,\n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::220/123\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 78\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.30/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 9\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.128/26\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.62/23\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.3.185/24\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::240/122\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/26\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"::ffff:154.15.6.0/120\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::40/122\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"0.36.0.0/16\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 65531\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 82,\n\t\t\t\t\t\"endPort\": 83\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.58/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 167,\n\t\t\t\"endPort\": 168\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::b7/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 373,\n\t\t\t\"endPort\": 374\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::204/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 217\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.252/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 373,\n\t\t\t\"endPort\": 374\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.50/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 345,\n\t\t\t\"endPort\": 345\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::14d/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 251\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.84/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 29\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.192/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 229\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.32/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 217\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2b6/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 319\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.252/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 71,\n\t\t\t\"endPort\": 72\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.164/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 363\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.232/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 233\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.248/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 41,\n\t\t\t\"endPort\": 43\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.139/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 259\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.236/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 217\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3fb/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 91,\n\t\t\t\"endPort\": 95\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::21e/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 333\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.40/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 215\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::7e/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 75\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.176/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 85,\n\t\t\t\"endPort\": 88\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1be/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 211,\n\t\t\t\"endPort\": 211\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.186/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 311\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2df/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 209\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2ca/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 5\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.216/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 89,\n\t\t\t\"endPort\": 90\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.104/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 11\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.144/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 145\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.89/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 259\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::103/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 197\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::87/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 323\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.208/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 215,\n\t\t\t\"endPort\": 218\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.92/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 351\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.96/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 69\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.30/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 29\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.232/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 239,\n\t\t\t\"endPort\": 243\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.99/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 287\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::304/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 221,\n\t\t\t\"endPort\": 224\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.170/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 325,\n\t\t\t\"endPort\": 326\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.208/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 25,\n\t\t\t\"endPort\": 29\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::321/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 87,\n\t\t\t\"endPort\": 88\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.60/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 25\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::ec/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 99\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.176/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 91\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.5/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 205\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.42/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 311\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.96/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 219\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.166/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 159\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.220/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 353\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::ff/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 149,\n\t\t\t\"endPort\": 151\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.104/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 17\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.212/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 141,\n\t\t\t\"endPort\": 143\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.164/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 161,\n\t\t\t\"endPort\": 161\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::137/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 159\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.124/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 55\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.157/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 143\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.20/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 107\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.230/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 1\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.216/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 67\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.252/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 157\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.178/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 293\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.196/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 327\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.202/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 55\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.40/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 27,\n\t\t\t\"endPort\": 27\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.48/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 13\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.114/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 249,\n\t\t\t\"endPort\": 252\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.212/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 165,\n\t\t\t\"endPort\": 166\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.247/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 221\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.112/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 221\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.224/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 271\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::384/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 319\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.68/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 153,\n\t\t\t\"endPort\": 154\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::327/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 29\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3ec/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 349\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.74/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 83\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::e2/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 109\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.227/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 147,\n\t\t\t\"endPort\": 149\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1e6/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 173\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::a/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 199,\n\t\t\t\"endPort\": 202\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2f/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 297,\n\t\t\t\"endPort\": 300\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.59/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 15\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.64/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 337,\n\t\t\t\"endPort\": 337\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::32e/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 169\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.82/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 277,\n\t\t\t\"endPort\": 281\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3e4/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 139,\n\t\t\t\"endPort\": 142\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.204/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 251,\n\t\t\t\"endPort\": 254\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.8/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 309\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.242/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 253\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::339/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 217,\n\t\t\t\"endPort\": 218\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.108/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 305\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.118/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 305\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.219/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 349\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.188/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 115\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.53/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 195\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::326/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 297,\n\t\t\t\"endPort\": 299\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.64/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 359\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::310/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 379\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.192/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 255\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.138/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 371\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.204/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 37\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.120/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 273\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.146/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 305\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.240/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 33\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.36/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 205\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.229/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 175,\n\t\t\t\"endPort\": 176\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::24b/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 265\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::298/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 199\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.241/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 173,\n\t\t\t\"endPort\": 174\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.176/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 243,\n\t\t\t\"endPort\": 244\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.172/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 47\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.140/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 225\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.134/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 283\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1a3/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 219\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.117/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 343\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.144/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 383,\n\t\t\t\"endPort\": 385\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.113/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 345,\n\t\t\t\"endPort\": 347\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.84/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 313\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.146/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 213\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.84/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 395,\n\t\t\t\"endPort\": 398\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.56/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 135,\n\t\t\t\"endPort\": 135\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::47/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 309\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1a1/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 147\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.154/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 399\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2c8/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 371\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::44/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 231\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.54/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 127\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.34/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 185,\n\t\t\t\"endPort\": 187\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::145/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 265\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::131/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 109,\n\t\t\t\"endPort\": 109\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.152/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 129\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.160/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 179\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2c2/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 377\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.248/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 173\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.4/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 63\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.37/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 119\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.230/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 287\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.228/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 385\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.136/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 135\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.93/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 189,\n\t\t\t\"endPort\": 191\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.27/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 183,\n\t\t\t\"endPort\": 183\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::37f/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 337\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::28f/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 259,\n\t\t\t\"endPort\": 259\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.152/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 317,\n\t\t\t\"endPort\": 321\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.196/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 363\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::32b/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 121\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::293/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 391,\n\t\t\t\"endPort\": 393\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3b7/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 399\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1ff/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 217\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.52/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 109,\n\t\t\t\"endPort\": 110\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::39/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 307\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1f5/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 283\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::31/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 233\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.70/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 35,\n\t\t\t\"endPort\": 38\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::326/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 345\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.81/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 247,\n\t\t\t\"endPort\": 249\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::167/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 275\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.240/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 307\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.176/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 181\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.205/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 71,\n\t\t\t\"endPort\": 75\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1b9/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 135\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.180/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 189\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.68/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 101\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.52/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 119\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.96/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 217\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3e4/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 255\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::6f/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 393\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.70/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 315,\n\t\t\t\"endPort\": 317\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.26/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 107,\n\t\t\t\"endPort\": 109\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.115/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 333,\n\t\t\t\"endPort\": 337\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.240/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 1\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3b5/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 81\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.208/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 335\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.234/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 175\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.148/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 233,\n\t\t\t\"endPort\": 236\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::311/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 271,\n\t\t\t\"endPort\": 275\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.160/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 347\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::17a/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 315,\n\t\t\t\"endPort\": 316\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.124/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 75\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.2/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 277\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::333/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 313,\n\t\t\t\"endPort\": 317\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3cc/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 105,\n\t\t\t\"endPort\": 106\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.138/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 107,\n\t\t\t\"endPort\": 111\n\t\t}\n\t]\n}"}],"expected":{"allow":["0.0.0.0/0:tcp:79-83","0.0.0.0/0:tcp:441","0.0.0.0/0:udp:79-83","0.0.0.0/0:udp:441","10.0.0.8/30:tcp:65530","10.0.0.8/30:udp","10.0.0.32/28:icmp","10.0.0.229/32:icmp","10.0.1.64/26:tcp:65531","10.0.1.64/26:udp:12","10.0.1.64/26:udp:65531","10.0.2.0/23","::/0:tcp:79-83","::/0:tcp:441","::/0:udp:79-83","::/0:udp:441","fe7:2fe6:f2e7:d03c::/64:tcp:65531","fe7:2fe6:f2e7:d03c::/64:udp:12","fe7:2fe6:f2e7:d03c::/64:udp:65531","2001:db8::80/121:icmp","2001:db8::190/124:icmp","2001:db8::3e0/123:icmp","2001:db8::3e0/123:udp:442"],"deny":["0.36.0.0/16","10.0.0.0/26","10.0.0.64/30:tcp:359","10.0.0.84/30:tcp:29","10.0.0.84/30:udp:29","10.0.0.84/32:tcp:395-398","10.0.0.84/32:udp:395-398","10.0.0.89/32:tcp:259","10.0.0.96/31:tcp:217","10.0.0.113/32:udp:345-347","10.0.0.114/32:tcp:249-252","10.0.0.117/32:tcp:343","10.0.0.117/32:udp:343","10.0.0.124/30:udp:55","10.0.0.134/31:tcp:283","10.0.0.138/32:tcp:107-111","10.0.0.138/32:udp:107-111","10.0.0.146/32:tcp:305","10.0.0.164/31:tcp:363","10.0.0.164/31:udp:363","10.0.0.170/32:tcp:325-326","10.0.0.170/32:udp:325-326","10.0.0.176/30:tcp:243-244","10.0.0.176/30:udp:243-244","10.0.0.176/31:tcp:181","10.0.0.180/31:tcp:189","10.0.0.180/31:udp:189","10.0.0.204/31:udp:251-254","10.0.0.205/32:udp:71-75","10.0.0.208/31:tcp:335","10.0.0.208/31:udp:335","10.0.0.228/32:tcp:385","10.0.0.228/32:udp:385","10.0.0.229/32:udp:175-176","10.0.0.240/30:tcp:307","10.0.0.240/30:udp:307","10.0.0.248/30:tcp:41-43","10.0.0.248/30:udp:41-43","10.0.0.252/30:tcp:373-374","10.0.1.28/30:tcp:9","10.0.1.37/32:udp:119","10.0.1.40/30:tcp:215","10.0.1.40/30:udp:215","10.0.1.40/31:udp:27","10.0.1.42/32:udp:311","10.0.1.52/30:tcp:109-110","10.0.1.52/30:udp:109-110","10.0.1.53/32:udp:195","10.0.1.56/30:tcp:135","10.0.1.56/30:udp:135","10.0.1.59/32:udp:15","10.0.1.70/31:udp:315-317","10.0.1.74/31:tcp:83","10.0.1.81/32:udp:247-249","10.0.1.92/30:tcp:351","10.0.1.92/30:udp:351","10.0.1.93/32:tcp:189-191","10.0.1.108/30:udp:305","10.0.1.118/31:tcp:305","10.0.1.119/32:tcp:65532","10.0.1.119/32:udp:79","10.0.1.124/31:tcp:75","10.0.1.138/32:tcp:371","10.0.1.140/31:udp:225","10.0.1.144/31:udp:383-385","10.0.1.154/32:udp:399","10.0.1.160/30:udp:179","10.0.1.160/31:tcp:347","10.0.1.164/30:tcp:161","10.0.1.164/30:udp:161","10.0.1.166/32:tcp:159","10.0.1.166/32:udp:159","10.0.1.176/30:tcp:85-88","10.0.1.178/31:tcp:293","10.0.1.186/32:tcp:311","10.0.1.186/32:udp:311","10.0.1.196/32:udp:363","10.0.1.202/32:tcp:55","10.0.1.208/30:udp:25-29","10.0.1.212/30:udp:141-143","10.0.1.212/32:udp:165-166","10.0.1.220/30:tcp:353","10.0.1.230/31:tcp:287","10.0.1.230/31:udp:1","10.0.1.230/31:udp:287","10.0.1.232/30:tcp:239-243","10.0.1.232/31:tcp:233","10.0.1.232/31:udp:233","10.0.1.234/31:tcp:175","10.0.1.234/31:udp:175","10.0.1.236/31:tcp:217","10.0.1.242/31:tcp:253","10.0.1.252/31:tcp:157","10.0.1.252/31:udp:157","10.0.2.0/24","10.0.3.0/26:tcp","10.0.3.0/26:udp:2","10.0.3.0/26:udp:4-10","10.0.3.20/30:udp:107","10.0.3.27/32:udp:183","10.0.3.30/31:udp:29","10.0.3.68/31:tcp:153-154","10.0.3.82/31:tcp:277-281","10.0.3.84/31:udp:313","10.0.3.96/30:tcp:219","10.0.3.96/30:udp:219","10.0.3.96/31:tcp:69","10.0.3.99/32:tcp:287","10.0.3.104/30:udp:17","10.0.3.115/32:tcp:333-337","10.0.3.120/30:udp:273","10.0.3.128/26","10.0.3.192/30:tcp:229","10.0.3.192/30:tcp:255","10.0.3.192/30:udp:229","10.0.3.196/30:tcp:327","10.0.3.208/30:tcp:215-218","10.0.3.216/30:udp:67","10.0.3.224/31:tcp:271","10.0.3.224/31:udp:271","10.0.3.227/32:tcp:147-149","10.0.3.227/32:udp:147-149","10.0.3.240/30:tcp:1","10.0.3.241/32:udp:173-174","29.95.35.240/31:tcp","29.95.35.240/31:udp:2","29.95.35.240/31:udp:4-10","::ffff:154.15.6.0/120","2001:db8::a/128:udp:199-202","2001:db8::2f/128:tcp:297-300","2001:db8::2f/128:udp:297-300","2001:db8::31/128:tcp:233","2001:db8::31/128:udp:233","2001:db8::39/128:udp:307","2001:db8::40/122","2001:db8::87/128:tcp:323","2001:db8::87/128:udp:323","2001:db8::b7/128:tcp:373-374","2001:db8::b7/128:udp:373-374","2001:db8::e2/128:tcp:109","2001:db8::e2/128:udp:109","2001:db8::ec/128:tcp:99","2001:db8::ec/128:udp:99","2001:db8::ff/128:udp:149-151","2001:db8::103/128:tcp:197","2001:db8::131/128:tcp:109","2001:db8::131/128:udp:109","2001:db8::137/128:udp:159","2001:db8::145/128:tcp:265","2001:db8::14d/128:tcp:251","2001:db8::14d/128:udp:251","2001:db8::167/128:udp:275","2001:db8::17a/128:udp:315-316","2001:db8::1a1/128:tcp:147","2001:db8::1a3/128:tcp:219","2001:db8::1a3/128:udp:219","2001:db8::1b9/128:tcp:135","2001:db8::1be/128:tcp:211","2001:db8::1e6/128:udp:173","2001:db8::1f5/128:udp:283","2001:db8::1ff/128:tcp:217","2001:db8::1ff/128:udp:217","2001:db8::204/128:tcp:217","2001:db8::21e/128:tcp:333","2001:db8::220/123:udp:78","2001:db8::240/122","2001:db8::28f/128:tcp:259","2001:db8::293/128:tcp:391-393","2001:db8::293/128:udp:391-393","2001:db8::298/128:udp:199","2001:db8::2b6/128:udp:319","2001:db8::2c2/128:tcp:377","2001:db8::2c2/128:udp:377","2001:db8::2c8/128:tcp:371","2001:db8::2ca/128:tcp:5","2001:db8::2df/128:tcp:209","2001:db8::2df/128:udp:209","2001:db8::304/128:tcp:221-224","2001:db8::304/128:udp:221-224","2001:db8::310/128:tcp:379","2001:db8::311/128:udp:271-275","2001:db8::321/128:tcp:87-88","2001:db8::321/128:udp:87-88","2001:db8::326/128:tcp:297-299","2001:db8::326/128:tcp:345","2001:db8::326/128:udp:345","2001:db8::327/128:tcp:29","2001:db8::327/128:udp:29","2001:db8::32b/128:tcp:121","2001:db8::32b/128:udp:121","2001:db8::32e/128:udp:169","2001:db8::333/128:tcp:313-317","2001:db8::339/128:tcp:217-218","2001:db8::358/127:tcp","2001:db8::358/127:udp:2","2001:db8::358/127:udp:4-10","2001:db8::37f/128:udp:337","2001:db8::384/128:tcp:319","2001:db8::3b5/128:tcp:81","2001:db8::3b5/128:udp:81","2001:db8::3b7/128:udp:399","2001:db8::3cc/128:tcp:105-106","2001:db8::3e4/128:tcp:139-142","2001:db8::3e4/128:udp:139-142","2001:db8::3e4/128:udp:255","2001:db8::3ec/128:tcp:349","2001:db8::3fb/128:tcp:91-95"]}}, + {"name":"random #33","input":[{"text":"{\n \"allow\":[],\n \"deny\":[\n {\n \"cidr\":\"10.0.2.8/30\",\n \"protocol\":\"udp\",\n \"port\":255\n },\n {\n \"cidr\":\"10.0.1.84/30\",\n \"protocol\":\"udp\",\n \"port\":299,\n \"endPort\":299\n },\n {\n \"cidr\":\"10.0.3.192/30\",\n \"protocol\":\"tcp\",\n \"port\":81\n },\n {\n \"cidr\":\"2001:db8::110/128\",\n \"protocol\":\"udp\",\n \"port\":289\n },\n {\n \"cidr\":\"10.0.3.14/32\",\n \"protocol\":\"any\",\n \"port\":345,\n \"endPort\":346\n },\n {\n \"cidr\":\"2001:db8::1da/128\",\n \"protocol\":\"tcp\",\n \"port\":285,\n \"endPort\":286\n },\n {\n \"cidr\":\"10.0.3.9/32\",\n \"protocol\":\"udp\",\n \"port\":299\n },\n {\n \"cidr\":\"10.0.2.58/31\",\n \"protocol\":\"udp\",\n \"port\":61\n },\n {\n \"cidr\":\"10.0.3.224/30\",\n \"protocol\":\"any\",\n \"port\":353\n },\n {\n \"cidr\":\"10.0.0.136/31\",\n \"protocol\":\"udp\",\n \"port\":345,\n \"endPort\":346\n },\n {\n \"cidr\":\"10.0.1.43/32\",\n \"protocol\":\"udp\",\n \"port\":357\n },\n {\n \"cidr\":\"10.0.1.188/30\",\n \"protocol\":\"udp\",\n \"port\":233\n },\n {\n \"cidr\":\"10.0.2.72/30\",\n \"protocol\":\"any\",\n \"port\":135,\n \"endPort\":138\n },\n {\n \"cidr\":\"2001:db8::1fb/128\",\n \"protocol\":\"udp\",\n \"port\":21\n },\n {\n \"cidr\":\"2001:db8::3c2/128\",\n \"protocol\":\"udp\",\n \"port\":347,\n \"endPort\":351\n },\n {\n \"cidr\":\"10.0.0.0/30\",\n \"protocol\":\"any\",\n \"port\":215,\n \"endPort\":215\n },\n {\n \"cidr\":\"10.0.3.252/31\",\n \"protocol\":\"any\",\n \"port\":287\n },\n {\n \"cidr\":\"2001:db8::2ca/128\",\n \"protocol\":\"udp\",\n \"port\":105,\n \"endPort\":106\n },\n {\n \"cidr\":\"2001:db8::3de/128\",\n \"protocol\":\"udp\",\n \"port\":385\n },\n {\n \"cidr\":\"10.0.2.54/31\",\n \"protocol\":\"any\",\n \"port\":233,\n \"endPort\":234\n },\n {\n \"cidr\":\"10.0.3.212/32\",\n \"protocol\":\"any\",\n \"port\":341\n },\n {\n \"cidr\":\"10.0.2.146/32\",\n \"protocol\":\"any\",\n \"port\":143\n },\n {\n \"cidr\":\"10.0.2.184/30\",\n \"protocol\":\"any\",\n \"port\":129\n },\n {\n \"cidr\":\"2001:db8::b1/128\",\n \"protocol\":\"tcp\",\n \"port\":19\n },\n {\n \"cidr\":\"2001:db8::329/128\",\n \"protocol\":\"udp\",\n \"port\":331,\n \"endPort\":333\n },\n {\n \"cidr\":\"10.0.1.92/31\",\n \"protocol\":\"udp\",\n \"port\":165,\n \"endPort\":165\n },\n {\n \"cidr\":\"10.0.1.60/30\",\n \"protocol\":\"any\",\n \"port\":145\n },\n {\n \"cidr\":\"2001:db8::48/128\",\n \"protocol\":\"tcp\",\n \"port\":253\n },\n {\n \"cidr\":\"10.0.2.52/30\",\n \"protocol\":\"any\",\n \"port\":187,\n \"endPort\":188\n },\n {\n \"cidr\":\"10.0.1.162/31\",\n \"protocol\":\"any\",\n \"port\":143\n },\n {\n \"cidr\":\"10.0.0.240/31\",\n \"protocol\":\"tcp\",\n \"port\":83,\n \"endPort\":86\n },\n {\n \"cidr\":\"2001:db8::be/128\",\n \"protocol\":\"any\",\n \"port\":153\n },\n {\n \"cidr\":\"10.0.3.68/30\",\n \"protocol\":\"tcp\",\n \"port\":207,\n \"endPort\":207\n },\n {\n \"cidr\":\"10.0.0.8/30\",\n \"protocol\":\"tcp\",\n \"port\":207\n },\n {\n \"cidr\":\"10.0.0.24/30\",\n \"protocol\":\"tcp\",\n \"port\":251\n },\n {\n \"cidr\":\"2001:db8::d3/128\",\n \"protocol\":\"udp\",\n \"port\":211\n },\n {\n \"cidr\":\"10.0.1.110/31\",\n \"protocol\":\"tcp\",\n \"port\":3\n },\n {\n \"cidr\":\"10.0.3.24/32\",\n \"protocol\":\"tcp\",\n \"port\":27,\n \"endPort\":27\n },\n {\n \"cidr\":\"2001:db8::1fb/128\",\n \"protocol\":\"tcp\",\n \"port\":39,\n \"endPort\":42\n },\n {\n \"cidr\":\"10.0.0.100/30\",\n \"protocol\":\"any\",\n \"port\":277\n },\n {\n \"cidr\":\"2001:db8::371/128\",\n \"protocol\":\"any\",\n \"port\":143\n },\n {\n \"cidr\":\"10.0.0.205/32\",\n \"protocol\":\"any\",\n \"port\":385\n },\n {\n \"cidr\":\"2001:db8::1b0/128\",\n \"protocol\":\"any\",\n \"port\":109\n },\n {\n \"cidr\":\"2001:db8::1ec/128\",\n \"protocol\":\"tcp\",\n \"port\":163\n },\n {\n \"cidr\":\"10.0.2.146/31\",\n \"protocol\":\"any\",\n \"port\":223\n },\n {\n \"cidr\":\"2001:db8::36/128\",\n \"protocol\":\"any\",\n \"port\":107,\n \"endPort\":108\n },\n {\n \"cidr\":\"10.0.2.252/32\",\n \"protocol\":\"tcp\",\n \"port\":219,\n \"endPort\":223\n },\n {\n \"cidr\":\"2001:db8::11d/128\",\n \"protocol\":\"udp\",\n \"port\":127,\n \"endPort\":128\n },\n {\n \"cidr\":\"2001:db8::280/128\",\n \"protocol\":\"any\",\n \"port\":217\n },\n {\n \"cidr\":\"2001:db8::1fe/128\",\n \"protocol\":\"any\",\n \"port\":263\n },\n {\n \"cidr\":\"2001:db8::bb/128\",\n \"protocol\":\"any\",\n \"port\":253\n },\n {\n \"cidr\":\"2001:db8::40/128\",\n \"protocol\":\"udp\",\n \"port\":341,\n \"endPort\":344\n },\n {\n \"cidr\":\"10.0.1.244/30\",\n \"protocol\":\"udp\",\n \"port\":39\n },\n {\n \"cidr\":\"10.0.0.242/31\",\n \"protocol\":\"udp\",\n \"port\":281\n },\n {\n \"cidr\":\"2001:db8::292/128\",\n \"protocol\":\"udp\",\n \"port\":27\n },\n {\n \"cidr\":\"10.0.0.252/30\",\n \"protocol\":\"any\",\n \"port\":1,\n \"endPort\":3\n },\n {\n \"cidr\":\"2001:db8::29f/128\",\n \"protocol\":\"tcp\",\n \"port\":303\n },\n {\n \"cidr\":\"10.0.0.116/31\",\n \"protocol\":\"any\",\n \"port\":255\n },\n {\n \"cidr\":\"10.0.0.27/32\",\n \"protocol\":\"tcp\",\n \"port\":271,\n \"endPort\":273\n },\n {\n \"cidr\":\"2001:db8::3a7/128\",\n \"protocol\":\"any\",\n \"port\":225,\n \"endPort\":226\n },\n {\n \"cidr\":\"10.0.2.2/32\",\n \"protocol\":\"udp\",\n \"port\":137\n },\n {\n \"cidr\":\"10.0.3.226/31\",\n \"protocol\":\"any\",\n \"port\":121\n },\n {\n \"cidr\":\"10.0.1.88/31\",\n \"protocol\":\"any\",\n \"port\":81,\n \"endPort\":85\n },\n {\n \"cidr\":\"2001:db8::e5/128\",\n \"protocol\":\"any\",\n \"port\":99\n },\n {\n \"cidr\":\"10.0.2.129/32\",\n \"protocol\":\"udp\",\n \"port\":85,\n \"endPort\":89\n },\n {\n \"cidr\":\"10.0.0.204/30\",\n \"protocol\":\"udp\",\n \"port\":87\n },\n {\n \"cidr\":\"2001:db8::194/128\",\n \"protocol\":\"tcp\",\n \"port\":361,\n \"endPort\":361\n },\n {\n \"cidr\":\"10.0.3.236/31\",\n \"protocol\":\"tcp\",\n \"port\":375,\n \"endPort\":378\n },\n {\n \"cidr\":\"10.0.3.60/31\",\n \"protocol\":\"udp\",\n \"port\":263,\n \"endPort\":264\n },\n {\n \"cidr\":\"2001:db8::2b0/128\",\n \"protocol\":\"udp\",\n \"port\":289\n },\n {\n \"cidr\":\"10.0.3.18/31\",\n \"protocol\":\"udp\",\n \"port\":143\n },\n {\n \"cidr\":\"10.0.3.30/31\",\n \"protocol\":\"udp\",\n \"port\":103,\n \"endPort\":105\n },\n {\n \"cidr\":\"10.0.1.40/30\",\n \"protocol\":\"tcp\",\n \"port\":265\n },\n {\n \"cidr\":\"2001:db8::1bb/128\",\n \"protocol\":\"tcp\",\n \"port\":49,\n \"endPort\":51\n },\n {\n \"cidr\":\"10.0.1.144/31\",\n \"protocol\":\"udp\",\n \"port\":323\n },\n {\n \"cidr\":\"10.0.3.48/30\",\n \"protocol\":\"any\",\n \"port\":321,\n \"endPort\":324\n },\n {\n \"cidr\":\"10.0.2.96/31\",\n \"protocol\":\"any\",\n \"port\":43\n },\n {\n \"cidr\":\"10.0.3.72/31\",\n \"protocol\":\"any\",\n \"port\":133\n },\n {\n \"cidr\":\"10.0.1.124/30\",\n \"protocol\":\"any\",\n \"port\":139\n },\n {\n \"cidr\":\"2001:db8::1f8/128\",\n \"protocol\":\"any\",\n \"port\":121\n }\n ]\n}"}],"expected":{"allow":[],"deny":["10.0.0.0/30:tcp:215","10.0.0.0/30:udp:215","10.0.0.8/30:tcp:207","10.0.0.24/30:tcp:251","10.0.0.27/32:tcp:271-273","10.0.0.100/30:tcp:277","10.0.0.100/30:udp:277","10.0.0.116/31:tcp:255","10.0.0.116/31:udp:255","10.0.0.136/31:udp:345-346","10.0.0.204/30:udp:87","10.0.0.205/32:tcp:385","10.0.0.205/32:udp:385","10.0.0.240/31:tcp:83-86","10.0.0.242/31:udp:281","10.0.0.252/30:tcp:1-3","10.0.0.252/30:udp:1-3","10.0.1.40/30:tcp:265","10.0.1.43/32:udp:357","10.0.1.60/30:tcp:145","10.0.1.60/30:udp:145","10.0.1.84/30:udp:299","10.0.1.88/31:tcp:81-85","10.0.1.88/31:udp:81-85","10.0.1.92/31:udp:165","10.0.1.110/31:tcp:3","10.0.1.124/30:tcp:139","10.0.1.124/30:udp:139","10.0.1.144/31:udp:323","10.0.1.162/31:tcp:143","10.0.1.162/31:udp:143","10.0.1.188/30:udp:233","10.0.1.244/30:udp:39","10.0.2.2/32:udp:137","10.0.2.8/30:udp:255","10.0.2.52/30:tcp:187-188","10.0.2.52/30:udp:187-188","10.0.2.54/31:tcp:233-234","10.0.2.54/31:udp:233-234","10.0.2.58/31:udp:61","10.0.2.72/30:tcp:135-138","10.0.2.72/30:udp:135-138","10.0.2.96/31:tcp:43","10.0.2.96/31:udp:43","10.0.2.129/32:udp:85-89","10.0.2.146/31:tcp:223","10.0.2.146/31:udp:223","10.0.2.146/32:tcp:143","10.0.2.146/32:udp:143","10.0.2.184/30:tcp:129","10.0.2.184/30:udp:129","10.0.2.252/32:tcp:219-223","10.0.3.9/32:udp:299","10.0.3.14/32:tcp:345-346","10.0.3.14/32:udp:345-346","10.0.3.18/31:udp:143","10.0.3.24/32:tcp:27","10.0.3.30/31:udp:103-105","10.0.3.48/30:tcp:321-324","10.0.3.48/30:udp:321-324","10.0.3.60/31:udp:263-264","10.0.3.68/30:tcp:207","10.0.3.72/31:tcp:133","10.0.3.72/31:udp:133","10.0.3.192/30:tcp:81","10.0.3.212/32:tcp:341","10.0.3.212/32:udp:341","10.0.3.224/30:tcp:353","10.0.3.224/30:udp:353","10.0.3.226/31:tcp:121","10.0.3.226/31:udp:121","10.0.3.236/31:tcp:375-378","10.0.3.252/31:tcp:287","10.0.3.252/31:udp:287","2001:db8::36/128:tcp:107-108","2001:db8::36/128:udp:107-108","2001:db8::40/128:udp:341-344","2001:db8::48/128:tcp:253","2001:db8::b1/128:tcp:19","2001:db8::bb/128:tcp:253","2001:db8::bb/128:udp:253","2001:db8::be/128:tcp:153","2001:db8::be/128:udp:153","2001:db8::d3/128:udp:211","2001:db8::e5/128:tcp:99","2001:db8::e5/128:udp:99","2001:db8::110/128:udp:289","2001:db8::11d/128:udp:127-128","2001:db8::194/128:tcp:361","2001:db8::1b0/128:tcp:109","2001:db8::1b0/128:udp:109","2001:db8::1bb/128:tcp:49-51","2001:db8::1da/128:tcp:285-286","2001:db8::1ec/128:tcp:163","2001:db8::1f8/128:tcp:121","2001:db8::1f8/128:udp:121","2001:db8::1fb/128:tcp:39-42","2001:db8::1fb/128:udp:21","2001:db8::1fe/128:tcp:263","2001:db8::1fe/128:udp:263","2001:db8::280/128:tcp:217","2001:db8::280/128:udp:217","2001:db8::292/128:udp:27","2001:db8::29f/128:tcp:303","2001:db8::2b0/128:udp:289","2001:db8::2ca/128:udp:105-106","2001:db8::329/128:udp:331-333","2001:db8::371/128:tcp:143","2001:db8::371/128:udp:143","2001:db8::3a7/128:tcp:225-226","2001:db8::3a7/128:udp:225-226","2001:db8::3c2/128:udp:347-351","2001:db8::3de/128:udp:385"]}}, + {"name":"random #34","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.0/29\"\n }, \n {\n \"cidr\": \"10.0.1.64/28\", \n \"except\": []\n }, \n {\n \"cidr\": \"10.0.1.80/0.0.0.15\"\n }, \n {\n \"cidr\": \"::ffff:3.0.0.0/104\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\", \n \"port\": 65534, \n \"endPort\": 65535\n }\n ]\n }, \n {\n \"cidr\": \"36.53.0.0/16\", \n \"protocol\": \"udp\", \n \"port\": 440, \n \"endPort\": 444\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::2fb/118\"\n }, \n {\n \"cidr\": \"0000:0000:0000:0000:0000:0000:0000:0000/32\"\n }\n ]\n }, \n {\n \"cidr\": \"10.0.2.202/30\", \n \"except\": [], \n \"protocol\": \"tcp\", \n \"port\": 440, \n \"endPort\": 440\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0308/126\", \n \"except\": [\n \"2001:db8::30a/127\", \n \"2001:db8::308/0127\", \n \"2001:db8::30a/127\"\n ]\n }, \n {\n \"cidr\": \"10.0.0.200/30\"\n }, \n {\n \"cidr\": \"214.105.75.196\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\", \n \"port\": 440\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"cidr\": \"::ffff:80.9.219.205/00120\", \n \"protocol\": \"udp\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"::/1\"\n }, \n {\n \"cidr\": \"10.0.1.156/25\", \n \"except\": []\n }, \n {\n \"cidr\": \"10.0.2.80/29\"\n }, \n {\n \"cidr\": \"10.0.0.14\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 65532\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 82\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 81\n }, \n {\n \"protocol\": \"any\", \n \"port\": 443\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.2.0/25\"\n }, \n {\n \"cidr\": \"::ffff:0.0.0.0/96\", \n \"except\": [\n \"::ffff:126.173.126.93/102\", \n \"::ffff:45.206.246.157/100\", \n \"::FFFF:18.163.1.89/96\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 65535, \n \"endPort\": 65535\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 5\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/118\", \n \"except\": [\n \"2001:db8::23d/122\", \n \"2001:0db8:0000:0000:0000:0000:0000:0100/122\"\n ]\n }, \n {\n \"cidr\": \"2001:db8::d1\", \n \"except\": [\n \"2001:db8::d1\", \n \"2001:0db8:0000:0000:0000:0000:0.0.0.209/128\"\n ]\n }, \n {\n \"cidr\": \"2001:db8::1e9\"\n }\n ]\n }, \n {\n \"cidr\": \"10.0.3.217/31\", \n \"except\": [\n \"10.0.3.216/31\", \n \"10.0.3.216/31\"\n ], \n \"protocol\": \"udp\", \n \"port\": 1\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"udp\"\n }, \n {\n \"protocol\": \"any\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/118\"\n }, \n {\n \"cidr\": \"10.0.3.64/26\", \n \"except\": [\n \"10.0.3.116\", \n \"10.0.3.80/28\"\n ]\n }, \n {\n \"cidr\": \"10.0.0.218\"\n }, \n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0370/0124\", \n \"except\": [\n \"2001:db8::379\", \n \"2001:0db8:0000:0000:0000:0000:0000:0374\", \n \"2001:db8::377\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\"\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 79, \n \"endPort\": 84\n }\n ]\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 444\n }, \n {\n \"protocol\": \"any\", \n \"port\": 4\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.0/23\"\n }, \n {\n \"cidr\": \"2001:db8::20/123\"\n }, \n {\n \"cidr\": \"10.0.1.224\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 3\n }\n ]\n }, \n {\n \"cidr\": \"10.0.1.128/255.255.255.128\", \n \"protocol\": \"udp\", \n \"port\": 3, \n \"endPort\": 9\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"any\"\n }\n ]\n }, \n {\n \"cidr\": \"10.0.2.0/255.255.255.240\", \n \"except\": [\n \"10.0.2.0/31\", \n \"10.0.2.1/30\"\n ], \n \"protocol\": \"udp\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::200/122\", \n \"except\": [\n \"2001:db8::204/124\"\n ]\n }, \n {\n \"cidr\": \"2001:db8::28b/123\", \n \"except\": [\n \"2001:db8::294/127\"\n ]\n }, \n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0200/119\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\"\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 65534, \n \"endPort\": 65535\n }, \n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"tcp\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"::ffff:0.0.0.0/96\"\n }, \n {\n \"cidr\": \"::ffff:155.0.0.0/104\", \n \"except\": [\n \"::ffff:155.104.0.0/109\"\n ]\n }, \n {\n \"cidr\": \"2001:db8::/119\"\n }, \n {\n \"cidr\": \"10.0.2.96/27\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\"\n }, \n {\n \"port\": 9\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.138/25\", \n \"except\": []\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\"\n }, \n {\n \"protocol\": \"any\", \n \"port\": 4, \n \"endPort\": 7\n }\n ]\n }, \n {\n \"cidr\": \"10.0.0.214/00029\", \n \"except\": [], \n \"protocol\": \"udp\", \n \"port\": 79, \n \"endPort\": 80\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 441\n }\n ]\n }, \n {}, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.182/31\"\n }, \n {\n \"cidr\": \"10.0.1.219\", \n \"except\": [\n \"10.0.1.219/32\"\n ]\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"0.0.0.0/192.0.0.0\"\n }, \n {\n \"cidr\": \"10.0.2.166/31\"\n }, \n {\n \"cidr\": \"10.0.0.224/28\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }, \n {\n \"port\": 78\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 65534\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::2f3/125\"\n }, \n {\n \"cidr\": \"c57b:887a:be83:b59e:19f3:5ef2:b9c4:beea/0\", \n \"except\": []\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"7e6e:b6b7:7853:f3be:1c89:4a2d::/00096\"\n }, \n {\n \"cidr\": \"2001:db8::1e3/125\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 442, \n \"endPort\": 445\n }, \n {\n \"protocol\": \"any\", \n \"port\": 65535\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 82\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.1.193/22\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 65532\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 3, \n \"endPort\": 6\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 10, \n \"endPort\": 13\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 445\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"61.204.245.90\"\n }\n ]\n }, \n {\n \"cidr\": \"0.0.0.0/0\", \n \"except\": [\n \"112.0.0.0/0004\"\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::340/125\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 65532, \n \"endPort\": 65535\n }\n ]\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"cidr\": \"2001:db8::1c9/126\", \n \"protocol\": \"udp\", \n \"ports\": [\n {\n \"port\": 9, \n \"endPort\": 8\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01e8/125\"\n }, \n {\n \"cidr\": \"155.207.30.93/4\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\", \n \"port\": 4, \n \"endPort\": 6\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 3, \n \"endPort\": 3\n }\n ]\n }, \n {\n \"cidr\": \"b5c4:8c4e:b6d:259b:9db8:49fd:6000:0/104\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.2.9/28\"\n }, \n {\n \"cidr\": \"2001:db8::38/125\"\n }, \n {\n \"cidr\": \"4.91.195.19/4\"\n }, \n {\n \"cidr\": \"93.225.0.0/16\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\", \n \"port\": 79, \n \"endPort\": 81\n }, \n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 81, \n \"endPort\": 83\n }, \n {\n \"protocol\": \"any\", \n \"port\": 80, \n \"endPort\": 81\n }\n ]\n }, \n {}, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::3cb/128\", \n \"except\": [\n \"2001:db8::3cb/128\", \n \"2001:db8::3cb/128\"\n ]\n }\n ]\n }, \n {\n \"cidr\": \"2001:db8::29e/124\", \n \"protocol\": \"udp\", \n \"port\": 441\n }, \n {\n \"cidr\": \"2001:db8::136/127\", \n \"protocol\": \"tcp\", \n \"port\": 80, \n \"endPort\": 55814\n }, \n {\n \"cidr\": \"192.0.0.0/2\", \n \"protocol\": \"tcp\", \n \"port\": 441, \n \"endPort\": 441\n }, \n {\n \"cidr\": \"2001:db8::342/123\", \n \"protocol\": \"tcp\", \n \"port\": 12, \n \"endPort\": 39236\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0180/124\", \n \"except\": [\n \"2001:db8::180/124\"\n ]\n }, \n {\n \"cidr\": \"10.0.1.166/0.0.0.3\"\n }, \n {\n \"cidr\": \"10.0.0.192/26\"\n }, \n {\n \"cidr\": \"10.0.0.0/25\"\n }\n ], \n \"ports\": [\n {\n \"port\": 441, \n \"endPort\": 445\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 5\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.2.142/32\"\n }\n ]\n }, \n {}, \n {\n \"cidr\": \"2001:db8::1c0/123\", \n \"protocol\": \"udp\", \n \"port\": 65531\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.36/26\", \n \"except\": [\n \"10.0.3.0/26\", \n \"10.0.3.49/0031\"\n ]\n }, \n {\n \"cidr\": \"52.240.0.0/012\", \n \"except\": [\n \"52.246.128.0/18\", \n \"52.255.164.50/255.248.0.0\"\n ]\n }, \n {\n \"cidr\": \"10.0.0.24/30\", \n \"except\": [\n \"10.0.0.26/31\", \n \"10.0.0.25\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\", \n \"port\": 443, \n \"endPort\": 444\n }, \n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 80, \n \"endPort\": 83\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 65535\n }\n ]\n }\n ], \n \"deny\": [\n {\n \"cidr\": \"10.0.2.161/29\", \n \"protocol\": \"any\"\n }, \n {\n \"cidr\": \"10.0.0.193/22\", \n \"protocol\": \"any\", \n \"port\": 2\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.2.0/25\", \n \"except\": []\n }, \n {\n \"cidr\": \"2001:db8::/123\", \n \"except\": [\n \"2001:db8::/123\", \n \"2001:0db8:0000:0000:0000:0000:0000:0010/128\", \n \"2001:DB8::/125\"\n ]\n }, \n {\n \"cidr\": \"195.9.149.242/1\"\n }, \n {\n \"cidr\": \"10.0.1.4/255.255.255.0\"\n }\n ]\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"udp\"\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 444\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 440\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:DB8::27A/118\"\n }\n ]\n }, \n {\n \"cidr\": \"10.0.3.0/255.255.255.128\", \n \"protocol\": \"tcp\", \n \"port\": 441, \n \"endPort\": 446\n }\n ]\n}"}],"expected":{"error":"allow[32] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #35","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.3.88/00031\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 80}, {\"protocol\": \"udp\", \"port\": 5}, {\"protocol\": \"udp\", \"port\": 82}]}]}"}],"expected":{"allow":["10.0.3.88/31:udp:5","10.0.3.88/31:udp:80","10.0.3.88/31:udp:82"],"deny":[]}}, + {"name":"random #36","input":[{"text":"{\"allow\": [], \"allow\":[{\"cidr\":\"10.0.3.128/26\",\"protocol\":\"udp\",\"port\":443,\"to\":\"192.0.2.0/24\"}],\"deny\":[{\"cidr\":\"2001:db8::110/124\",\"protocol\":\"udp\",\"port\":65532,\"endPort\":65535}]}"}],"expected":{"error":"duplicate JSON property: allow"}}, + {"name":"random #37","input":[{"text":"{\"allow\": []}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #38","input":[{"text":"{\"allow\":[],\"deny\":[{\"to\":[{\"cidr\":\"2001:db8::241/125\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":65533,\"endPort\":65535}]}]}"}],"expected":{"allow":[],"deny":["2001:db8::240/125:udp:65533-65535"]}}, + {"name":"random #39","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"4.153.169.229\", \n\t\t\t\"except\": [\n\t\t\t\t\"4.153.169.229\", \n\t\t\t\t\"4.153.169.229\", \n\t\t\t\t\"4.153.169.229/32\"\n\t\t\t], \n\t\t\t\"protocol\": \"udp\", \n\t\t\t\"port\": 442\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.0.42/31\", \n\t\t\t\"protocol\": \"any\"\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0240/122\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::240/00122\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"4ab4:b5ae:9775:79d8:9c97:c1b:2800:0/104\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"1cd8:be1d:e837:b777:d9ef:38bf:79ba:4135/3\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"port\": 8\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65533\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.3.92/30\"\n\t\t}\n\t], \n\t\"deny\": [\n\t\t{\n\t\t\t\"cidr\": \"::/0\", \n\t\t\t\"protocol\": \"tcp\"\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/023\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 82, \n\t\t\t\t\t\"endPort\": 84\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65530\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.0/25\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.3.58/31\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.0/024\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.150/32\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.2.173\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 80\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.128/027\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.138/31\", \n\t\t\t\t\t\t\"10.0.1.128/30\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.140/025\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.2.200/0030\", \n\t\t\t\t\t\t\"x\", \n\t\t\t\t\t\t\"10.0.2.224/28\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"c8ab:a263:d9bf:fd87:669d:6cf:f6b5:c57f\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"d838:ab7c:347f:f0a9:35f5:7377::/96\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"d838:ab7c:347f:f0a9:35f5:7377:f000:0/100\", \n\t\t\t\t\t\t\"d838:ab7c:347f:f0a9:35f5:7377:b400:0/102\", \n\t\t\t\t\t\t\"d838:ab7c:347f:f0a9:35f5:7377:d44a:d645/96\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 65532, \n\t\t\t\t\t\"endPort\": 65532\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 3\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 65533, \n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"196.160.0.0/16\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"239.97.233.82/31\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 440, \n\t\t\t\t\t\"endPort\": 441\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:DB8::BC/123\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::a0/124\", \n\t\t\t\t\t\t\"2001:db8::a0/124\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 442\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.3.240/28\", \n\t\t\t\"except\": [\n\t\t\t\t\"10.0.3.252/255.255.255.254\", \n\t\t\t\t\"10.0.3.240/29\", \n\t\t\t\t\"10.0.3.253/31\"\n\t\t\t], \n\t\t\t\"protocol\": \"any\", \n\t\t\t\"port\": 78\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[3].to[1].except[1] is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"random #40","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"10.0.0.151/255.255.255.128\",\"except\":[\"10.0.0.217\",\"10.0.0.218/31\"]},{\"cidr\":\"10.0.1.30/23\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":65534},{\"protocol\":\"icmp\"}]},{\"to\":[{\"cidr\":\"10.0.0.144/28\",\"except\":[\"10.0.0.155/30\",\"10.0.0.144/255.255.255.252\",\"10.0.0.151\"]},{\"cidr\":\"2001:db8::184/00128\"},{\"cidr\":\"10.0.2.72/29\"}]},{\"cidr\":\"2001:DB8::133/128\",\"protocol\":\"tcp\",\"port\":81,\"endPort\":82},{\"to\":[{\"cidr\":\"10.0.1.32/28\",\"except\":[]},{\"cidr\":\"8da1:715f:6660:81e8:b5f0:2f97:72.165.49.118/127\"}],\"ports\":[{\"protocol\":\"icmp\"},{\"protocol\":\"icmp\"},{\"protocol\":\"any\",\"port\":444},{\"protocol\":\"any\",\"port\":65535}]},{\"to\":[{\"cidr\":\"10.0.1.128/25\",\"except\":[\"10.0.1.210/30\",\"10.0.1.171\",\"10.0.1.128/29\"]},{\"cidr\":\"128.0.0.0/2\",\"except\":[]},{\"cidr\":\"2001:db8::213\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":80,\"endPort\":81},{\"protocol\":\"tcp\"},{\"protocol\":\"any\",\"port\":65530,\"endPort\":65533},{\"protocol\":\"icmp\"}]},{\"to\":[{\"cidr\":\"f65c:9cb7:8f6c:865e:c86c:2c08:9f76:8613\",\"except\":[\"f65c:9cb7:8f6c:865e:c86c:2c08:9f76:8613/128\",\"f65c:9cb7:8f6c:865e:c86c:2c08:9f76:8613\",\"f65c:9cb7:8f6c:865e:c86c:2c08:9f76:8613/128\"]},{\"cidr\":\"10.0.0.0/23\"},{\"cidr\":\"10.0.3.120/255.255.255.254\"}],\"ports\":[{\"port\":2,\"endPort\":2},{\"protocol\":\"tcp\",\"port\":80,\"endPort\":85}]},{\"to\":[{\"cidr\":\"2001:db8::281\"}],\"ports\":[{\"protocol\":\"any\"},{\"protocol\":\"tcp\",\"port\":440},{\"protocol\":\"udp\",\"port\":82,\"endPort\":88},{\"protocol\":\"any\",\"port\":9}]},{\"ports\":[{\"protocol\":\"udp\",\"port\":445,\"endPort\":447},{\"protocol\":\"tcp\",\"port\":65530},{\"protocol\":\"any\",\"port\":443,\"endPort\":446},{\"protocol\":\"udp\"}]},{\"to\":[{\"cidr\":\"220.83.212.0/255.255.255.0\"},{\"cidr\":\"10.0.2.113\",\"except\":[]},{\"cidr\":\"2001:db8::a0/123\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":442,\"endPort\":448},{\"protocol\":\"udp\",\"port\":65531}]},{\"cidr\":\"193.11.7.194/16\",\"except\":[],\"protocol\":\"udp\"},{\"to\":[{\"cidr\":\"10.0.3.10/0.0.0.15\"}],\"ports\":[{\"protocol\":\"any\",\"port\":441},{\"protocol\":\"any\",\"port\":79}]},{\"to\":[{\"cidr\":\"2001:db8::170/124\"},{\"cidr\":\"10.0.2.4/31\",\"except\":[]},{\"cidr\":\"bb4a:e7e7:489f:5c3f:4bf6:7958::/96\",\"except\":[\"bb4a:e7e7:489f:5c3f:4bf6:7958:e000:0/00100\",\"bb4a:e7e7:489f:5c3f:4bf6:7958:1f22:e0c9/101\",\"bb4a:e7e7:489f:5c3f:4bf6:7958::/97\"]}],\"ports\":[{\"protocol\":\"any\",\"port\":444,\"endPort\":44209},{\"protocol\":\"tcp\"},{\"protocol\":\"udp\",\"port\":7},{\"protocol\":\"tcp\"}]},{\"to\":[{\"cidr\":\"10.0.2.128/255.255.255.192\",\"except\":[]}],\"ports\":[{\"protocol\":\"udp\"}]},{\"cidr\":\"41.112.0.0/012\",\"protocol\":\"tcp\",\"port\":65534},{\"to\":[{\"cidr\":\"10.0.3.128/27\"},{\"cidr\":\"10.0.1.0/24\"}],\"ports\":[{\"protocol\":\"any\",\"port\":3}]},{\"cidr\":\"136.115.227.64/12\",\"protocol\":\"any\",\"port\":445},{\"to\":[{\"cidr\":\"10.0.3.0/24\"},{\"cidr\":\"10.0.2.127/29\",\"except\":[\"10.0.2.124/31\"]},{\"cidr\":\"2001:db8::20/124\"},{\"cidr\":\"2001:db8::378/00118\"}],\"ports\":[{\"protocol\":\"any\",\"port\":445,\"endPort\":445},{\"protocol\":\"udp\"},{\"protocol\":\"tcp\",\"port\":79,\"endPort\":79},{\"protocol\":\"tcp\"}]},{\"ports\":[{\"protocol\":\"any\"}]},{\"cidr\":\"10.0.0.0/22\"},{\"to\":[{\"cidr\":\"10.0.2.34/31\"},{\"cidr\":\"2001:db8::300/121\",\"except\":[\"2001:db8::330/124\"]},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0191/124\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":65531},{\"protocol\":\"tcp\"},{\"protocol\":\"udp\"},{\"protocol\":\"icmp\"}]},{\"cidr\":\"::ffff:0.0.0.0/96\",\"protocol\":\"udp\",\"port\":1}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.3.156/30\"},{\"cidr\":\"10.0.0.34/022\",\"except\":[\"10.0.2.184/29\",\"10.0.0.0/22\"]},{\"cidr\":\"1a9b:a28c:cfe1:bb0c:1176:2acd:141c:1a4c/0\"},{\"cidr\":\"218.41.53.10/1\"}],\"ports\":[{\"protocol\":\"any\",\"port\":4,\"endPort\":6},{\"protocol\":\"tcp\",\"port\":79,\"endPort\":82}]},{\"to\":[{\"cidr\":\"64.0.0.0/4\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":5},{\"protocol\":\"tcp\",\"port\":65530,\"endPort\":65534}]}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["10.0.3.156/30:tcp:4-6","10.0.3.156/30:tcp:79-82","10.0.3.156/30:udp:4-6","64.0.0.0/4:tcp:5","64.0.0.0/4:tcp:65530-65534","128.0.0.0/1:tcp:4-6","128.0.0.0/1:tcp:79-82","128.0.0.0/1:udp:4-6","::/0:tcp:4-6","::/0:tcp:79-82","::/0:udp:4-6"]}}, + {"name":"random #41","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:00d0/124\", \"protocol\": \"any\"}, {\"cidr\": \"2001:DB8::200/119\", \"except\": [\"2001:db8::228/122\", \"2001:db8::280/123\", \"2001:db8::2f7/124\"], \"protocol\": \"icmp\"}, {\"ports\": [{\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"55.17.21.41\"}, {\"cidr\": \"2001:db8::1eb/128\"}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.0/120\", \"except\": [\"2001:db8::/0123\"]}], \"ports\": [{\"protocol\": \"any\", \"port\": 6, \"endPort\": 7}]}, {\"cidr\": \"2001:db8::33/128\", \"except\": [\"2001:db8::33/128\", \"2001:0db8:0000:0000:0000:0000:0000:0033/128\", \"2001:db8::33/128\"]}, {\"to\": [{\"cidr\": \"89.32.0.0/12\", \"except\": [\"89.47.0.0/255.255.0.0\", \"89.40.0.0/13\", \"89.44.176.86/17\"]}, {\"cidr\": \"2001:db8::31\"}, {\"cidr\": \"10.0.1.240/28\", \"except\": [\"10.0.1.240/00028\", \"10.0.1.244/30\"]}]}, {\"to\": [{\"cidr\": \"330e:2de6:2235:e665:776:31ea:2c2f:2f33/32\"}, {\"cidr\": \"10.0.3.28/30\"}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/118\"}, {\"cidr\": \"10.0.1.111/27\"}], \"ports\": [{\"protocol\": \"any\"}, {\"protocol\": \"udp\", \"port\": 82}]}, {\"ports\": [{\"protocol\": \"tcp\", \"port\": 65535, \"endPort\": 65535}]}], \"deny\": [{\"cidr\": \"f575:27c8:679f:126a:9f18:a624:6100:0/104\", \"protocol\": \"udp\", \"port\": 78}]}"}],"expected":{"allow":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:65535","10.0.1.96/27","10.0.3.28/30","55.17.21.41/32:tcp:6-7","55.17.21.41/32:udp:6-7","89.32.0.0/13","::/0:icmp","::/0:tcp:65535","2001:db8::/118","330e:2de6::/32"],"deny":["f575:27c8:679f:126a:9f18:a624:6100:0/104:udp:78"]}}, + {"name":"random #42","input":[{"text":"{\"allow\": [], \"deny\": [{\"to\": [{\"cidr\": \"10.0.1.182/28\"}, {\"cidr\": \"96.0.0.0/4\", \"except\": [\"104.0.0.0/3.255.255.255\"]}], \"ports\": [{\"protocol\": \"any\", \"port\": 78}, {\"protocol\": \"tcp\", \"port\": 440, \"endPort\": 446}]}, {\"to\": [{\"cidr\": \"10.0.0.0/23\", \"except\": [\"10.0.0.121/31\", \"10.0.1.0/24\", \"10.0.1.138/0.0.0.3\"]}, {\"cidr\": \"67c3:582b:340c:4f65:8c5f:a321:b14a:9c11/3\", \"except\": []}, {\"cidr\": \"10.0.0.95/26\"}]}, {\"to\": [{\"cidr\": \"10.0.2.0/23\"}, {\"cidr\": \"2001:db8::40/122\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65533}]}, {\"ports\": [{\"port\": 80, \"endPort\": 86}, {\"protocol\": \"udp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"10.0.0.171/31\", \"except\": [\"10.0.0.170/255.255.255.255\", \"10.0.0.170/31\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 81, \"endPort\": 11029}, {\"port\": 8}]}, {\"cidr\": \"10.0.3.184/29\"}, {\"cidr\": \"134.43.197.42\", \"except\": [], \"protocol\": \"tcp\"}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0204/119\"}], \"ports\": [{\"protocol\": \"any\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\", \"port\": 65532, \"endPort\": 65534}, {\"protocol\": \"tcp\", \"port\": 80}]}, {\"to\": [{\"cidr\": \"2001:db8::32c/127\"}], \"ports\": [{}]}, {\"to\": [{\"cidr\": \"45ac:a868:8cb7:6c66:71a6:3f42:f232:caaf/00\"}, {\"cidr\": \"10.0.2.234/31\", \"except\": [\"10.0.2.234/31\"]}, {\"cidr\": \"123.177.32.249/0\"}, {\"cidr\": \"10.0.3.130/31\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 442, \"endPort\": 442}, {\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\", \"port\": 441}, {\"protocol\": \"udp\", \"port\": 65532, \"endPort\": 65533}]}, {\"cidr\": \"10.0.0.160/255.255.255.224\", \"except\": [\"10.0.0.160/27\", \"10.0.0.185/28\", \"10.0.0.175/32\"], \"protocol\": \"any\", \"port\": 78}, {\"to\": [{\"cidr\": \"2001:DB8::2FF/126\"}, {\"cidr\": \"10.0.0.194/22\", \"except\": [\"10.0.3.160/27\", \"10.0.2.87/0.0.0.255\"]}, {\"cidr\": \"10.0.3.249/0.0.0.31\"}, {\"cidr\": \"2001:DB8::E0/124\"}]}, {\"to\": [{\"cidr\": \"10.0.1.197/26\", \"except\": [\"10.0.1.206/31\"]}, {\"cidr\": \"203.127.143.163/24\", \"except\": [\"203.127.143.128/255.255.255.240\"]}, {\"cidr\": \"10.0.3.0/24\", \"except\": [\"10.0.3.204/255.255.255.252\"]}, {\"cidr\": \"10.0.0.36/30\"}], \"ports\": [{\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"10.0.1.0/255.255.255.128\"}, {\"cidr\": \"2001:db8::2cb\", \"except\": [\"2001:db8::2cb/128\"]}, {\"cidr\": \"10.0.2.0/00023\"}]}, {\"to\": [{\"cidr\": \"2001:DB8::100/00122\"}, {\"cidr\": \"150.192.0.0/12\"}, {\"cidr\": \"196.51.205.115/32\"}]}, {\"to\": [{\"cidr\": \"10.0.0.47/32\"}, {\"cidr\": \"2001:db8::3f3/123\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 12, \"endPort\": 14}]}, {\"to\": [{\"cidr\": \"5e0e:a4b5:9a3:e15f:7e3a:40ab::/96\", \"except\": []}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 441, \"endPort\": 443}]}, {\"cidr\": \"10.0.2.160/27\"}, {\"cidr\": \"::/1\", \"except\": [\"714b:5417:2081:c1d3:9658:b44e:d965:12d9/7\", \"3500::/8\", \"30f4:7a01:e11d:5a71:8afe:24e0:4a7c:e793/4\"], \"protocol\": \"any\", \"port\": 65531, \"endPort\": 65533}, {\"cidr\": \"10.0.2.0/23\", \"protocol\": \"any\", \"port\": 65533}, {\"cidr\": \"2001:DB8::341/122\", \"except\": [], \"protocol\": \"udp\", \"port\": 65535, \"endPort\": 65535}, {\"cidr\": \"88.36.77.186/32\"}, {\"cidr\": \"10.0.1.206/25\", \"except\": [\"10.0.1.252/30\", \"10.0.1.128/25\", \"10.0.1.155/25\"], \"protocol\": \"tcp\", \"port\": 81, \"endPort\": 87}, {\"to\": [{\"cidr\": \"c000:0000:0000:0000:0000:0000:0.0.0.0/3\", \"except\": [\"de04:2db1:973:dbab:a9e5:1c54:f5ad:fd1a/7\", \"d000::/5\"]}, {\"cidr\": \"128.0.0.0/1\", \"except\": [\"128.0.0.0/2\"]}]}, {}, {\"cidr\": \"11.249.150.59/0\", \"except\": [\"32.0.0.0/3\"], \"protocol\": \"icmp\"}, {\"to\": [{\"cidr\": \"2001:db8::306/121\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0.0.3.37/122\", \"2001:db8::36f/126\"]}, {\"cidr\": \"10.0.3.192/30\", \"except\": [\"10.0.3.194/31\", \"10.0.3.192/30\"]}, {\"cidr\": \"2001:DB8::22C/120\"}, {\"cidr\": \"10.0.3.178/31\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 441, \"endPort\": 445}, {\"protocol\": \"any\", \"port\": 79}, {\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\", \"port\": 78, \"endPort\": 79}]}, {\"cidr\": \"2001:db8::298/126\", \"except\": [\"2001:db8::29a/00127\", \"2001:db8::299\", \"2001:db8::29a/127\"], \"protocol\": \"icmp\"}, {\"to\": [{\"cidr\": \"31.255.69.181/00016\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"tcp\"}, {\"protocol\": \"any\", \"port\": 81, \"endPort\": 86}]}, {\"to\": [{\"cidr\": \"10.0.0.166/23\"}, {\"cidr\": \"0.0.0.0/0\", \"except\": []}, {\"cidr\": \"10.0.3.0/26\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 441, \"endPort\": 441}, {\"protocol\": \"tcp\", \"port\": 12}]}, {}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/119\"}, {\"cidr\": \"10.0.2.143\"}, {\"cidr\": \"106.225.35.0/24\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"udp\", \"port\": 65533}]}, {\"to\": [{\"cidr\": \"2001:DB8::/119\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 440}, {\"protocol\": \"udp\", \"port\": 65532, \"endPort\": 65532}, {\"protocol\": \"udp\", \"port\": 444}]}, {\"to\": [{\"cidr\": \"2001:db8::100/120\"}], \"ports\": [{}, {\"protocol\": \"udp\", \"port\": 444, \"endPort\": 446}, {\"port\": 444, \"endPort\": 450}]}, {\"to\": [{\"cidr\": \"2001:db8::330/124\"}, {\"cidr\": \"8000::/1\", \"except\": []}, {\"cidr\": \"ac33:dfbd:988e:f239:c96e:22e6:b800:0/104\"}, {\"cidr\": \"39.0.0.0/8\", \"except\": []}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\", \"port\": 4}, {\"protocol\": \"udp\"}]}, {}, {\"to\": [{\"cidr\": \"216.83.25.209/0.0.0.1\", \"except\": [\"216.83.25.208/31\", \"216.83.25.208/32\"]}, {\"cidr\": \"227.241.226.0/255.255.255.0\"}, {\"cidr\": \"10.0.2.144/28\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 443}, {\"protocol\": \"udp\", \"port\": 80}, {\"protocol\": \"tcp\", \"port\": 440}, {\"protocol\": \"tcp\", \"port\": 10, \"endPort\": 10}]}, {\"ports\": [{\"protocol\": \"tcp\", \"port\": 78}]}, {\"cidr\": \"::/1\", \"protocol\": \"any\"}, {\"to\": [{\"cidr\": \"e4b3:4d5:2f34:6097:3667:aceb:e833:47ee/128\"}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.0/0120\"}], \"ports\": [{\"port\": 440, \"endPort\": 445}, {\"protocol\": \"any\", \"port\": 65530, \"endPort\": 65531}, {\"protocol\": \"tcp\", \"port\": 2, \"endPort\": 2736}]}, {\"to\": [{\"cidr\": \"2001:db8::2a4/127\"}, {\"cidr\": \"2001:db8::300/121\"}, {\"cidr\": \"10.0.3.13/30\"}, {\"cidr\": \"10.0.0.89/22\"}]}, {\"to\": [{\"cidr\": \"2001:db8::188/121\", \"except\": [\"2001:db8::190/125\"]}, {\"cidr\": \"10.0.3.23/26\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 79, \"endPort\": 84}, {\"protocol\": \"tcp\", \"port\": 444}, {\"port\": 65533}, {\"protocol\": \"tcp\", \"port\": 8, \"endPort\": 63690}]}, {\"cidr\": \"10.0.0.0/23\", \"protocol\": \"tcp\", \"port\": 443}, {\"cidr\": \"10.0.3.0/24\", \"except\": [\"10.0.3.240/29\", \"10.0.3.167/00029\"], \"protocol\": \"any\"}, {\"to\": [{\"cidr\": \"10.0.2.0/23\"}], \"ports\": [{\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"10.0.2.0/024\"}, {\"cidr\": \"10.0.0.148/28\"}]}, {\"to\": [{\"cidr\": \"0.0.0.0/01\", \"except\": [\"17.124.189.197/8\", \"33.0.0.0/8\"]}, {\"cidr\": \"0.0.0.0/0.0.0.0\", \"except\": [\"158.144.137.248/248.0.0.0\", \"128.0.0.0/01\"]}, {\"cidr\": \"2001:db8::/118\", \"except\": [\"2001:db8::313/121\"]}, {\"cidr\": \"10.0.0.0/22\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 82}, {\"protocol\": \"tcp\", \"port\": 1}]}, {\"cidr\": \"2001:db8::120/123\", \"protocol\": \"udp\", \"port\": 78, \"endPort\": 82}, {}, {\"cidr\": \"2001:db8::/123\", \"protocol\": \"tcp\"}, {\"cidr\": \"10.0.2.104/32\", \"protocol\": \"tcp\", \"port\": 3}, {\"cidr\": \"64.0.0.0/2\", \"except\": [\"89.10.80.183/224.0.0.0\", \"80.0.0.0/8\", \"116.128.0.0/0.127.255.255\"], \"protocol\": \"tcp\"}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"random #43","input":[{"text":"{\n \"deny\": []\n}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #44","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"10.0.1.174/30\"},{\"cidr\":\"10.0.0.132/30\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":NaN}]},{\"cidr\":\"2001:db8::/118\"},{\"to\":[{\"cidr\":\"10.0.1.0/25\",\"except\":[\"10.0.1.41/0026\",\"10.0.1.57/27\",\"10.0.1.32/255.255.255.224\"]},{\"cidr\":\"10.0.1.144/28\",\"except\":[]},{\"cidr\":\"10.0.2.144/00028\"}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"tcp\",\"port\":443,\"endPort\":448},{\"protocol\":\"udp\",\"port\":9},{\"protocol\":\"udp\",\"port\":445}]},{\"to\":[{\"cidr\":\"30.44.62.94/2\"}]},{\"to\":[{\"cidr\":\"2001:db8::1f4/126\"}]},{\"to\":[{\"cidr\":\"af82:e6a3:b081:4741:9c2f:e26:e600:0/104\",\"except\":[\"af82:e6a3:b081:4741:9c2f:e26:e634:6973/000112\"]},{\"cidr\":\"128.0.0.0/127.255.255.255\",\"except\":[]},{\"cidr\":\"2001:db8::30/000120\"}],\"ports\":[{\"protocol\":\"any\",\"port\":80,\"endPort\":85},{\"protocol\":\"any\",\"port\":65530},{\"protocol\":\"tcp\",\"port\":79,\"endPort\":82},{\"protocol\":\"any\",\"port\":9}]}],\"deny\":[]}"}],"expected":{"error":"allow[0].ports[0].port must be an integer"}}, + {"name":"random #45","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"64.0.0.0/2\"\n},\n{\n\"cidr\": \"10.0.1.72/29\",\n\"except\": [\n\"10.0.1.72/29\",\n\"10.0.1.79/31\",\n\"10.0.0.0/33\"\n]\n}\n],\n\"ports\": [\n{\n\"protocol\": \"tcp\"\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:02e6/122\",\n\"except\": []\n},\n{\n\"cidr\": \"10.0.3.0/24\"\n},\n{\n\"cidr\": \"10.0.0.40/30\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\"\n},\n{\n\"protocol\": \"tcp\",\n\"port\": 444\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.0/26\",\n\"except\": []\n},\n{\n\"cidr\": \"2001:db8::255/119\",\n\"except\": [\n\"2001:db8::28d/121\",\n\"2001:db8::280/123\",\n\"2001:db8::308/127\"\n]\n},\n{\n\"cidr\": \"::ffff:53.183.196.0/00120\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\"\n}\n]\n},\n{\n\"cidr\": \"10.0.0.110/22\",\n\"except\": [\n\"10.0.1.128/25\",\n\"10.0.2.0/026\"\n],\n\"protocol\": \"any\",\n\"port\": 444\n},\n{\n\"ports\": [\n{\n\"protocol\": \"tcp\",\n\"port\": 4,\n\"endPort\": 9\n},\n{\n\"protocol\": \"any\",\n\"port\": 9\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.200/029\"\n},\n{\n\"cidr\": \"72.57.105.0/24\",\n\"except\": [\n\"72.57.105.164/28\",\n\"72.57.105.95/32\"\n]\n},\n{\n\"cidr\": \"10.0.1.192/24\"\n},\n{\n\"cidr\": \"10.0.0.0/23\"\n}\n],\n\"ports\": [\n{},\n{\n\"port\": 65530,\n\"endPort\": 65530\n}\n]\n},\n{\n\"cidr\": \"2001:db8::300/123\",\n\"protocol\": \"icmp\"\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.133/32\",\n\"except\": [\n\"10.0.1.133/32\",\n\"10.0.1.133\"\n]\n},\n{\n\"cidr\": \"2001:db8::350/126\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"icmp\"\n},\n{\n\"protocol\": \"tcp\"\n},\n{\n\"protocol\": \"any\"\n},\n{\n\"protocol\": \"any\",\n\"port\": 12,\n\"endPort\": 17\n}\n]\n}\n],\n\"deny\": [\n{\n\"cidr\": \"2001:db8::be/125\",\n\"except\": [\n\"2001:db8::bf/128\",\n\"2001:DB8::BA/00127\",\n\"2001:db8::bb/128\"\n],\n\"protocol\": \"udp\",\n\"port\": 65535,\n\"endPort\": 65535\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/23\",\n\"except\": [\n\"10.0.1.182/24\",\n\"10.0.1.108/31\"\n]\n},\n{\n\"cidr\": \"10.0.3.34/31\"\n},\n{\n\"cidr\": \"198.24.190.118/4\",\n\"except\": [\n\"196.0.0.0/8\",\n\"192.0.0.0/5\"\n]\n}\n]\n}\n]\n}"}],"expected":{"error":"allow[0].to[1].except[2] is not a valid IPv4 or IPv6 CIDR: 10.0.0.0/33"}}, + {"name":"random #46","input":[{"text":"{\n \"allow\":[\n {\n \"to\":[\n {\n \"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.2.185/127\",\n \"except\":[\n \"2001:db8::2b8\",\n \"2001:db8::2b8/127\"\n ]\n },\n {\n \"cidr\":\"2001:DB8::397/122\"\n },\n {\n \"cidr\":\"10.0.0.0/32\"\n },\n {\n \"cidr\":\"10.0.0.40/024\"\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.0.59/255.255.255.252\"\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"ff4c:12ce:acd1:6d60:3e99:9fe3:2b35:356/127\"\n },\n {\n \"cidr\":\"4c48:aa07:7d40:380b:1a6:193b::/96\"\n },\n {\n \"cidr\":\"80.0.0.0/0008\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"icmp\"\n },\n {\n \"protocol\":\"udp\"\n },\n {\n \"protocol\":\"any\",\n \"port\":442,\n \"endPort\":446\n },\n {\n \"protocol\":\"tcp\",\n \"port\":65535,\n \"endPort\":65535\n }\n ]\n },\n {\n \"cidr\":\"10.0.0.192/255.255.255.192\",\n \"except\":[\n \"10.0.0.198/29\",\n \"10.0.0.192/31\",\n \"10.0.0.195/27\"\n ],\n \"protocol\":\"tcp\"\n }\n ],\n \"deny\":[\n {\n \"cidr\":\"892d:173c:5886:2788::/64\",\n \"protocol\":\"tcp\",\n \"port\":440,\n \"endPort\":440\n },\n {\n \"to\":[\n {\n \"cidr\":\"2001:db8::200/120\"\n },\n {\n \"cidr\":\"10.0.2.172/28\"\n },\n {\n \"cidr\":\"0.0.0.0/0.0.0.0\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"any\"\n },\n {\n \"protocol\":\"icmp\"\n },\n {\n \"protocol\":\"udp\",\n \"port\":1,\n \"endPort\":4\n }\n ]\n },\n {\n \"cidr\":\"10.0.3.183/029\",\n \"protocol\":\"any\",\n \"port\":65531\n },\n {\n \"to\":[\n {\n \"cidr\":\"89.226.0.0/16\",\n \"except\":[\n \"89.226.234.128/17\"\n ]\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"tcp\",\n \"port\":6\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.2.166/22\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"tcp\",\n \"port\":80\n },\n {\n \"protocol\":\"tcp\",\n \"port\":65531,\n \"endPort\":65535\n },\n {\n \"protocol\":\"tcp\",\n \"port\":11\n },\n {\n \"protocol\":\"udp\",\n \"port\":4\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"130.187.202.139\"\n },\n {\n \"cidr\":\"2001:db8::100/121\",\n \"except\":[\n \"2001:db8::148/125\",\n \"2001:db8::16b\"\n ]\n },\n {\n \"cidr\":\"2001:DB8::2AE/127\"\n },\n {\n \"cidr\":\"2001:db8::/118\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"udp\",\n \"port\":78,\n \"endPort\":83\n },\n {\n \"protocol\":\"udp\"\n },\n {\n \"protocol\":\"tcp\"\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["10.0.0.0/24","80.0.0.0/8:icmp","80.0.0.0/8:tcp:442-446","80.0.0.0/8:tcp:65535","80.0.0.0/8:udp","2001:db8::380/122","4c48:aa07:7d40:380b:1a6:193b::/96:icmp","4c48:aa07:7d40:380b:1a6:193b::/96:tcp:442-446","4c48:aa07:7d40:380b:1a6:193b::/96:tcp:65535","4c48:aa07:7d40:380b:1a6:193b::/96:udp","ff4c:12ce:acd1:6d60:3e99:9fe3:2b35:356/127:icmp","ff4c:12ce:acd1:6d60:3e99:9fe3:2b35:356/127:tcp:442-446","ff4c:12ce:acd1:6d60:3e99:9fe3:2b35:356/127:tcp:65535","ff4c:12ce:acd1:6d60:3e99:9fe3:2b35:356/127:udp"],"deny":["0.0.0.0/0","2001:db8::/118:tcp","2001:db8::/118:udp","2001:db8::200/120","892d:173c:5886:2788::/64:tcp:440"]}}, + {"name":"random #47","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"50d1:d9cf:76e9:8abb:4360:ac7f:563e:c278\"\n}, \n{\n\"cidr\": \"10.0.0.206/0.0.0.63\"\n}\n], \n\"port\": true\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.7/24\"\n}, \n{\n\"cidr\": \"2001:DB8::2C0/122\", \n\"except\": []\n}\n], \n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"any\"\n}\n]\n}, \n{\n\"cidr\": \"10.0.1.64/30\", \n\"protocol\": \"tcp\", \n\"port\": 79, \n\"endPort\": 84\n}, \n{\n\"cidr\": \"10.0.2.225\", \n\"protocol\": \"tcp\", \n\"port\": 80, \n\"endPort\": 81\n}\n], \n\"deny\": []\n}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #48","input":[{"text":"{\"deny\": [{\"cidr\": \"10.0.1.22/31\", \"protocol\": \"tcp\"}, {\"to\": [{\"cidr\": \"10.0.0.128/25\"}]}, {\"to\": [{\"cidr\": \"10.0.2.128/255.255.255.128\"}, {\"cidr\": \"::ffff:226.231.246.82/127\"}, {\"cidr\": \"10.0.0.0/0026\", \"except\": []}, {\"cidr\": \"0.0.0.0/0\"}]}, {\"to\": [{\"cidr\": \"2001:db8::2fe/119\", \"except\": [\"2001:db8::300/123\", \"2001:db8::2a0/126\"]}, {\"cidr\": \"2001:db8::313/119\"}, {\"cidr\": \"10.0.3.112/29\", \"except\": [\"10.0.3.112/00030\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 445, \"endPort\": 450}, {\"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65533}]}, {\"cidr\": \"2001:db8::200/119\", \"except\": [], \"protocol\": \"udp\", \"port\": 65534}, {\"to\": [{\"cidr\": \"10.0.1.192/29\"}]}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0","::ffff:226.231.246.82/127","2001:db8::200/119:tcp:65530-65533","2001:db8::200/119:udp:445-450","2001:db8::200/119:udp:65534"]}}, + {"name":"random #49","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #50","input":[{"text":"{\n \"allow\":[\n {\n \"to\":[\n {\n \"cidr\":\"10.0.2.51/22\",\n \"except\":[]\n },\n {\n \"cidr\":\"2001:db8::/118\"\n }\n ],\n \"ports\":[\n {\n \"port\":80.0\n },\n {\n \"protocol\":\"tcp\",\n \"port\":65535\n },\n {\n \"protocol\":\"udp\",\n \"port\":6,\n \"endPort\":7\n }\n ]\n },\n {\n \"cidr\":\"10.0.2.104/30\",\n \"protocol\":\"any\",\n \"port\":109\n },\n {\n \"cidr\":\"2001:db8::2cb/128\",\n \"protocol\":\"udp\",\n \"port\":373,\n \"endPort\":375\n },\n {\n \"cidr\":\"10.0.1.162/32\",\n \"protocol\":\"tcp\",\n \"port\":67,\n \"endPort\":70\n },\n {\n \"cidr\":\"10.0.2.220/30\",\n \"protocol\":\"udp\",\n \"port\":179,\n \"endPort\":181\n },\n {\n \"cidr\":\"10.0.3.130/31\",\n \"protocol\":\"any\",\n \"port\":137\n },\n {\n \"cidr\":\"10.0.0.153/32\",\n \"protocol\":\"any\",\n \"port\":275\n },\n {\n \"cidr\":\"10.0.3.130/31\",\n \"protocol\":\"any\",\n \"port\":261\n },\n {\n \"cidr\":\"2001:db8::10b/128\",\n \"protocol\":\"tcp\",\n \"port\":349,\n \"endPort\":349\n },\n {\n \"cidr\":\"10.0.2.12/30\",\n \"protocol\":\"any\",\n \"port\":209,\n \"endPort\":213\n },\n {\n \"cidr\":\"10.0.2.48/30\",\n \"protocol\":\"udp\",\n \"port\":125\n },\n {\n \"cidr\":\"10.0.1.16/30\",\n \"protocol\":\"udp\",\n \"port\":173\n },\n {\n \"cidr\":\"2001:db8::2a0/128\",\n \"protocol\":\"udp\",\n \"port\":153\n },\n {\n \"cidr\":\"10.0.3.50/31\",\n \"protocol\":\"any\",\n \"port\":35\n },\n {\n \"cidr\":\"10.0.0.52/32\",\n \"protocol\":\"tcp\",\n \"port\":51,\n \"endPort\":55\n },\n {\n \"cidr\":\"10.0.1.218/32\",\n \"protocol\":\"tcp\",\n \"port\":197\n },\n {\n \"cidr\":\"10.0.1.38/31\",\n \"protocol\":\"any\",\n \"port\":215,\n \"endPort\":219\n },\n {\n \"cidr\":\"10.0.0.160/30\",\n \"protocol\":\"any\",\n \"port\":121,\n \"endPort\":125\n },\n {\n \"cidr\":\"2001:db8::3f7/128\",\n \"protocol\":\"tcp\",\n \"port\":141,\n \"endPort\":143\n },\n {\n \"cidr\":\"10.0.0.162/32\",\n \"protocol\":\"any\",\n \"port\":199\n },\n {\n \"cidr\":\"10.0.3.114/31\",\n \"protocol\":\"tcp\",\n \"port\":147\n },\n {\n \"cidr\":\"10.0.2.30/32\",\n \"protocol\":\"any\",\n \"port\":69,\n \"endPort\":69\n },\n {\n \"cidr\":\"10.0.3.206/31\",\n \"protocol\":\"any\",\n \"port\":17\n },\n {\n \"cidr\":\"10.0.2.108/30\",\n \"protocol\":\"any\",\n \"port\":153\n },\n {\n \"cidr\":\"10.0.3.66/31\",\n \"protocol\":\"any\",\n \"port\":19\n },\n {\n \"cidr\":\"10.0.0.114/31\",\n \"protocol\":\"udp\",\n \"port\":37,\n \"endPort\":39\n },\n {\n \"cidr\":\"2001:db8::1/128\",\n \"protocol\":\"udp\",\n \"port\":7\n },\n {\n \"cidr\":\"10.0.3.132/30\",\n \"protocol\":\"udp\",\n \"port\":149\n },\n {\n \"cidr\":\"10.0.0.17/32\",\n \"protocol\":\"tcp\",\n \"port\":177\n },\n {\n \"cidr\":\"10.0.0.176/31\",\n \"protocol\":\"any\",\n \"port\":205,\n \"endPort\":206\n },\n {\n \"cidr\":\"2001:db8::11/128\",\n \"protocol\":\"udp\",\n \"port\":85,\n \"endPort\":88\n },\n {\n \"cidr\":\"2001:db8::294/128\",\n \"protocol\":\"udp\",\n \"port\":285,\n \"endPort\":289\n },\n {\n \"cidr\":\"10.0.0.228/31\",\n \"protocol\":\"udp\",\n \"port\":333\n },\n {\n \"cidr\":\"10.0.0.236/31\",\n \"protocol\":\"tcp\",\n \"port\":275\n },\n {\n \"cidr\":\"10.0.2.144/31\",\n \"protocol\":\"tcp\",\n \"port\":169\n },\n {\n \"cidr\":\"10.0.2.0/30\",\n \"protocol\":\"any\",\n \"port\":143,\n \"endPort\":144\n },\n {\n \"cidr\":\"10.0.1.52/30\",\n \"protocol\":\"tcp\",\n \"port\":153\n },\n {\n \"cidr\":\"10.0.1.172/30\",\n \"protocol\":\"any\",\n \"port\":325,\n \"endPort\":329\n },\n {\n \"cidr\":\"2001:db8::e3/128\",\n \"protocol\":\"tcp\",\n \"port\":273\n },\n {\n \"cidr\":\"10.0.2.210/31\",\n \"protocol\":\"any\",\n \"port\":389\n },\n {\n \"cidr\":\"10.0.3.126/32\",\n \"protocol\":\"any\",\n \"port\":247\n },\n {\n \"cidr\":\"10.0.2.56/30\",\n \"protocol\":\"udp\",\n \"port\":123,\n \"endPort\":127\n },\n {\n \"cidr\":\"10.0.1.228/30\",\n \"protocol\":\"udp\",\n \"port\":149\n },\n {\n \"cidr\":\"10.0.0.250/31\",\n \"protocol\":\"tcp\",\n \"port\":119\n },\n {\n \"cidr\":\"10.0.0.80/31\",\n \"protocol\":\"udp\",\n \"port\":349\n },\n {\n \"cidr\":\"10.0.3.30/32\",\n \"protocol\":\"any\",\n \"port\":39\n },\n {\n \"cidr\":\"10.0.2.26/32\",\n \"protocol\":\"udp\",\n \"port\":293\n },\n {\n \"cidr\":\"10.0.3.80/31\",\n \"protocol\":\"udp\",\n \"port\":33\n },\n {\n \"cidr\":\"10.0.2.79/32\",\n \"protocol\":\"any\",\n \"port\":375,\n \"endPort\":377\n },\n {\n \"cidr\":\"10.0.2.40/30\",\n \"protocol\":\"any\",\n \"port\":341\n },\n {\n \"cidr\":\"2001:db8::162/128\",\n \"protocol\":\"udp\",\n \"port\":249\n },\n {\n \"cidr\":\"10.0.3.62/31\",\n \"protocol\":\"udp\",\n \"port\":275,\n \"endPort\":276\n },\n {\n \"cidr\":\"10.0.2.148/32\",\n \"protocol\":\"any\",\n \"port\":355\n },\n {\n \"cidr\":\"10.0.1.133/32\",\n \"protocol\":\"udp\",\n \"port\":181,\n \"endPort\":185\n },\n {\n \"cidr\":\"10.0.2.214/32\",\n \"protocol\":\"any\",\n \"port\":287,\n \"endPort\":290\n },\n {\n \"cidr\":\"10.0.1.172/30\",\n \"protocol\":\"any\",\n \"port\":283,\n \"endPort\":287\n },\n {\n \"cidr\":\"2001:db8::2bd/128\",\n \"protocol\":\"tcp\",\n \"port\":261\n },\n {\n \"cidr\":\"10.0.2.253/32\",\n \"protocol\":\"tcp\",\n \"port\":141\n },\n {\n \"cidr\":\"10.0.2.241/32\",\n \"protocol\":\"udp\",\n \"port\":195\n },\n {\n \"cidr\":\"10.0.0.217/32\",\n \"protocol\":\"any\",\n \"port\":109\n },\n {\n \"cidr\":\"10.0.1.248/31\",\n \"protocol\":\"any\",\n \"port\":121,\n \"endPort\":121\n },\n {\n \"cidr\":\"2001:db8::26b/128\",\n \"protocol\":\"tcp\",\n \"port\":31\n },\n {\n \"cidr\":\"2001:db8::264/128\",\n \"protocol\":\"tcp\",\n \"port\":65\n },\n {\n \"cidr\":\"10.0.2.132/32\",\n \"protocol\":\"udp\",\n \"port\":17\n },\n {\n \"cidr\":\"10.0.2.8/30\",\n \"protocol\":\"any\",\n \"port\":53\n },\n {\n \"cidr\":\"2001:db8::1e9/128\",\n \"protocol\":\"any\",\n \"port\":331,\n \"endPort\":332\n },\n {\n \"cidr\":\"10.0.2.96/31\",\n \"protocol\":\"any\",\n \"port\":105\n },\n {\n \"cidr\":\"2001:db8::204/128\",\n \"protocol\":\"any\",\n \"port\":345,\n \"endPort\":347\n },\n {\n \"cidr\":\"10.0.0.0/30\",\n \"protocol\":\"udp\",\n \"port\":119\n },\n {\n \"cidr\":\"2001:db8::1a/128\",\n \"protocol\":\"any\",\n \"port\":371\n },\n {\n \"cidr\":\"10.0.2.232/30\",\n \"protocol\":\"udp\",\n \"port\":151\n },\n {\n \"cidr\":\"10.0.3.216/31\",\n \"protocol\":\"tcp\",\n \"port\":339\n },\n {\n \"cidr\":\"2001:db8::398/128\",\n \"protocol\":\"tcp\",\n \"port\":7\n },\n {\n \"cidr\":\"10.0.0.150/31\",\n \"protocol\":\"any\",\n \"port\":107\n },\n {\n \"cidr\":\"10.0.1.88/30\",\n \"protocol\":\"any\",\n \"port\":311\n },\n {\n \"cidr\":\"10.0.0.172/30\",\n \"protocol\":\"any\",\n \"port\":149\n },\n {\n \"cidr\":\"2001:db8::3f0/128\",\n \"protocol\":\"any\",\n \"port\":35\n },\n {\n \"cidr\":\"10.0.0.30/32\",\n \"protocol\":\"tcp\",\n \"port\":167\n },\n {\n \"cidr\":\"2001:db8::e7/128\",\n \"protocol\":\"udp\",\n \"port\":307\n },\n {\n \"cidr\":\"10.0.1.48/31\",\n \"protocol\":\"any\",\n \"port\":285\n },\n {\n \"cidr\":\"10.0.3.2/32\",\n \"protocol\":\"tcp\",\n \"port\":187\n },\n {\n \"cidr\":\"10.0.1.152/30\",\n \"protocol\":\"tcp\",\n \"port\":317\n },\n {\n \"cidr\":\"10.0.0.100/30\",\n \"protocol\":\"tcp\",\n \"port\":223\n },\n {\n \"cidr\":\"10.0.3.176/30\",\n \"protocol\":\"tcp\",\n \"port\":127\n },\n {\n \"cidr\":\"2001:db8::f1/128\",\n \"protocol\":\"tcp\",\n \"port\":383\n },\n {\n \"cidr\":\"2001:db8::336/128\",\n \"protocol\":\"tcp\",\n \"port\":327\n },\n {\n \"cidr\":\"10.0.2.46/31\",\n \"protocol\":\"udp\",\n \"port\":291\n },\n {\n \"cidr\":\"10.0.2.124/30\",\n \"protocol\":\"udp\",\n \"port\":277,\n \"endPort\":280\n },\n {\n \"cidr\":\"10.0.1.114/31\",\n \"protocol\":\"tcp\",\n \"port\":193\n },\n {\n \"cidr\":\"2001:db8::ca/128\",\n \"protocol\":\"tcp\",\n \"port\":115\n },\n {\n \"cidr\":\"10.0.2.66/31\",\n \"protocol\":\"any\",\n \"port\":369\n },\n {\n \"cidr\":\"10.0.3.254/32\",\n \"protocol\":\"tcp\",\n \"port\":193\n },\n {\n \"cidr\":\"2001:db8::33c/128\",\n \"protocol\":\"udp\",\n \"port\":193,\n \"endPort\":195\n },\n {\n \"cidr\":\"2001:db8::3b0/128\",\n \"protocol\":\"any\",\n \"port\":111,\n \"endPort\":112\n },\n {\n \"cidr\":\"10.0.2.8/30\",\n \"protocol\":\"tcp\",\n \"port\":385\n },\n {\n \"cidr\":\"2001:db8::1cd/128\",\n \"protocol\":\"tcp\",\n \"port\":111\n },\n {\n \"cidr\":\"2001:db8::3b6/128\",\n \"protocol\":\"udp\",\n \"port\":169\n },\n {\n \"cidr\":\"10.0.0.150/31\",\n \"protocol\":\"any\",\n \"port\":265\n },\n {\n \"cidr\":\"2001:db8::a8/128\",\n \"protocol\":\"tcp\",\n \"port\":35\n },\n {\n \"cidr\":\"10.0.3.40/30\",\n \"protocol\":\"tcp\",\n \"port\":327\n },\n {\n \"cidr\":\"2001:db8::43/128\",\n \"protocol\":\"udp\",\n \"port\":89,\n \"endPort\":91\n },\n {\n \"cidr\":\"10.0.0.28/30\",\n \"protocol\":\"any\",\n \"port\":49,\n \"endPort\":50\n },\n {\n \"cidr\":\"2001:db8::1ee/128\",\n \"protocol\":\"any\",\n \"port\":315,\n \"endPort\":316\n },\n {\n \"cidr\":\"10.0.2.8/30\",\n \"protocol\":\"udp\",\n \"port\":347\n },\n {\n \"cidr\":\"2001:db8::22d/128\",\n \"protocol\":\"any\",\n \"port\":333,\n \"endPort\":333\n },\n {\n \"cidr\":\"10.0.0.220/32\",\n \"protocol\":\"tcp\",\n \"port\":255,\n \"endPort\":255\n },\n {\n \"cidr\":\"2001:db8::307/128\",\n \"protocol\":\"udp\",\n \"port\":89,\n \"endPort\":92\n },\n {\n \"cidr\":\"10.0.2.220/32\",\n \"protocol\":\"any\",\n \"port\":109\n },\n {\n \"cidr\":\"10.0.2.18/31\",\n \"protocol\":\"tcp\",\n \"port\":339,\n \"endPort\":342\n },\n {\n \"cidr\":\"10.0.0.164/32\",\n \"protocol\":\"udp\",\n \"port\":211,\n \"endPort\":215\n },\n {\n \"cidr\":\"2001:db8::357/128\",\n \"protocol\":\"tcp\",\n \"port\":91\n },\n {\n \"cidr\":\"2001:db8::20f/128\",\n \"protocol\":\"udp\",\n \"port\":327,\n \"endPort\":329\n },\n {\n \"cidr\":\"10.0.2.168/30\",\n \"protocol\":\"udp\",\n \"port\":243\n },\n {\n \"cidr\":\"2001:db8::37e/128\",\n \"protocol\":\"udp\",\n \"port\":315\n },\n {\n \"cidr\":\"2001:db8::305/128\",\n \"protocol\":\"any\",\n \"port\":385,\n \"endPort\":388\n },\n {\n \"cidr\":\"10.0.3.213/32\",\n \"protocol\":\"tcp\",\n \"port\":341\n },\n {\n \"cidr\":\"2001:db8::375/128\",\n \"protocol\":\"tcp\",\n \"port\":271\n },\n {\n \"cidr\":\"10.0.2.122/31\",\n \"protocol\":\"udp\",\n \"port\":201,\n \"endPort\":202\n },\n {\n \"cidr\":\"10.0.1.76/30\",\n \"protocol\":\"tcp\",\n \"port\":79,\n \"endPort\":83\n },\n {\n \"cidr\":\"10.0.1.32/30\",\n \"protocol\":\"udp\",\n \"port\":301\n },\n {\n \"cidr\":\"10.0.3.72/32\",\n \"protocol\":\"udp\",\n \"port\":369\n },\n {\n \"cidr\":\"2001:db8::7/128\",\n \"protocol\":\"tcp\",\n \"port\":361,\n \"endPort\":362\n },\n {\n \"cidr\":\"10.0.2.216/32\",\n \"protocol\":\"udp\",\n \"port\":391,\n \"endPort\":391\n },\n {\n \"cidr\":\"2001:db8::19e/128\",\n \"protocol\":\"tcp\",\n \"port\":149\n },\n {\n \"cidr\":\"10.0.2.222/32\",\n \"protocol\":\"udp\",\n \"port\":95\n },\n {\n \"cidr\":\"10.0.0.220/30\",\n \"protocol\":\"udp\",\n \"port\":311\n },\n {\n \"cidr\":\"2001:db8::15b/128\",\n \"protocol\":\"any\",\n \"port\":257\n },\n {\n \"cidr\":\"10.0.0.232/30\",\n \"protocol\":\"udp\",\n \"port\":299,\n \"endPort\":301\n },\n {\n \"cidr\":\"10.0.0.22/31\",\n \"protocol\":\"udp\",\n \"port\":315\n },\n {\n \"cidr\":\"10.0.3.179/32\",\n \"protocol\":\"tcp\",\n \"port\":161\n },\n {\n \"cidr\":\"10.0.2.18/32\",\n \"protocol\":\"udp\",\n \"port\":31\n },\n {\n \"cidr\":\"10.0.1.216/31\",\n \"protocol\":\"any\",\n \"port\":333\n },\n {\n \"cidr\":\"10.0.2.120/31\",\n \"protocol\":\"tcp\",\n \"port\":245\n },\n {\n \"cidr\":\"10.0.0.224/32\",\n \"protocol\":\"any\",\n \"port\":167,\n \"endPort\":167\n },\n {\n \"cidr\":\"10.0.0.12/30\",\n \"protocol\":\"tcp\",\n \"port\":19\n },\n {\n \"cidr\":\"10.0.3.236/31\",\n \"protocol\":\"udp\",\n \"port\":321,\n \"endPort\":321\n },\n {\n \"cidr\":\"10.0.0.113/32\",\n \"protocol\":\"tcp\",\n \"port\":125,\n \"endPort\":128\n },\n {\n \"cidr\":\"10.0.2.176/30\",\n \"protocol\":\"tcp\",\n \"port\":339\n },\n {\n \"cidr\":\"10.0.1.48/30\",\n \"protocol\":\"any\",\n \"port\":303,\n \"endPort\":306\n },\n {\n \"cidr\":\"2001:db8::15f/128\",\n \"protocol\":\"udp\",\n \"port\":327\n },\n {\n \"cidr\":\"10.0.1.140/30\",\n \"protocol\":\"any\",\n \"port\":55\n },\n {\n \"cidr\":\"10.0.1.36/31\",\n \"protocol\":\"udp\",\n \"port\":69\n },\n {\n \"cidr\":\"10.0.2.10/31\",\n \"protocol\":\"any\",\n \"port\":171,\n \"endPort\":175\n },\n {\n \"cidr\":\"2001:db8::34a/128\",\n \"protocol\":\"udp\",\n \"port\":67\n },\n {\n \"cidr\":\"10.0.3.144/31\",\n \"protocol\":\"tcp\",\n \"port\":89\n },\n {\n \"cidr\":\"10.0.2.16/30\",\n \"protocol\":\"udp\",\n \"port\":333\n },\n {\n \"cidr\":\"10.0.2.87/32\",\n \"protocol\":\"tcp\",\n \"port\":171\n },\n {\n \"cidr\":\"2001:db8::2b6/128\",\n \"protocol\":\"udp\",\n \"port\":299\n },\n {\n \"cidr\":\"10.0.1.56/30\",\n \"protocol\":\"tcp\",\n \"port\":257,\n \"endPort\":257\n },\n {\n \"cidr\":\"10.0.2.28/31\",\n \"protocol\":\"udp\",\n \"port\":107\n },\n {\n \"cidr\":\"10.0.1.234/31\",\n \"protocol\":\"udp\",\n \"port\":193\n },\n {\n \"cidr\":\"10.0.2.48/31\",\n \"protocol\":\"udp\",\n \"port\":171\n },\n {\n \"cidr\":\"10.0.0.152/30\",\n \"protocol\":\"any\",\n \"port\":345\n },\n {\n \"cidr\":\"2001:db8::bd/128\",\n \"protocol\":\"any\",\n \"port\":113,\n \"endPort\":115\n },\n {\n \"cidr\":\"2001:db8::7/128\",\n \"protocol\":\"tcp\",\n \"port\":281\n },\n {\n \"cidr\":\"10.0.3.150/31\",\n \"protocol\":\"udp\",\n \"port\":387\n },\n {\n \"cidr\":\"10.0.2.71/32\",\n \"protocol\":\"any\",\n \"port\":93\n },\n {\n \"cidr\":\"2001:db8::10b/128\",\n \"protocol\":\"udp\",\n \"port\":205\n },\n {\n \"cidr\":\"10.0.3.46/32\",\n \"protocol\":\"tcp\",\n \"port\":199\n },\n {\n \"cidr\":\"10.0.2.47/32\",\n \"protocol\":\"udp\",\n \"port\":335\n },\n {\n \"cidr\":\"10.0.1.128/31\",\n \"protocol\":\"udp\",\n \"port\":177\n },\n {\n \"cidr\":\"2001:db8::18c/128\",\n \"protocol\":\"udp\",\n \"port\":395\n },\n {\n \"cidr\":\"10.0.0.100/31\",\n \"protocol\":\"tcp\",\n \"port\":221\n },\n {\n \"cidr\":\"2001:db8::3a2/128\",\n \"protocol\":\"any\",\n \"port\":31\n },\n {\n \"cidr\":\"2001:db8::19d/128\",\n \"protocol\":\"tcp\",\n \"port\":111\n }\n ],\n \"deny\":[\n {\n \"cidr\":\"21a5:3abf:6df4:43b0:e677:e60d:41aa:3f07/0\",\n \"except\":[\n \"::/0\",\n \"ea7:dc24:70a5:8bc8:ca34:e8b0:a11b:290d/3\",\n \"b800::/5\"\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"2001:db8::1e4/126\"\n },\n {\n \"cidr\":\"247.139.55.75/8\",\n \"except\":[\n \"247.40.160.139/12\",\n \"247.46.212.221/15\",\n \"247.72.0.0/255.254.0.0\"\n ]\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.2.236\"\n },\n {\n \"cidr\":\"208.0.0.0/4\"\n },\n {\n \"cidr\":\"2001:db8::3a6\",\n \"except\":[\n \"2001:db8::3a6\"\n ]\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"any\",\n \"port\":12,\n \"endPort\":18\n },\n {\n \"protocol\":\"any\",\n \"port\":1\n },\n {\n \"protocol\":\"tcp\",\n \"port\":12\n },\n {\n \"protocol\":\"udp\",\n \"port\":79,\n \"endPort\":81\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.1.96/0027\"\n },\n {\n \"cidr\":\"98.182.67.50/255.255.0.0\"\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"a53d:74b4:4180:7239:fbf9:f191:9b79:d95/3\"\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.3.216/25\"\n },\n {\n \"cidr\":\"10.0.3.128/25\",\n \"except\":[]\n },\n {\n \"cidr\":\"10.0.1.245/32\"\n }\n ]\n }\n ]\n}"}],"expected":{"error":"allow[0].ports[0].port must be an integer"}}, + {"name":"random #51","input":[{"text":"{\"deny\":[{\"cidr\":\"42a1:6d8:d9d2:5197:6234:cc97:9646:acb3/3\",\"protocol\":\"any\"},{\"to\":[{\"cidr\":\"::ffff:55.35.254.0/120\"}],\"ports\":[{\"port\":443,\"endPort\":443},{\"protocol\":\"udp\",\"port\":65531}]},{\"to\":[{\"cidr\":\"10.0.2.0/24\",\"except\":[]},{\"cidr\":\"10.0.3.0/29\"},{\"cidr\":\"192.0.0.0/2\",\"except\":[\"198.0.0.0/07\",\"207.0.0.0/8\",\"240.0.0.0/240.0.0.0\"]}]},{\"to\":[{\"cidr\":\"10.0.2.103/27\"},{\"cidr\":\"25.139.174.39\"}],\"ports\":[{},{\"protocol\":\"tcp\",\"port\":80,\"endPort\":45339},{\"protocol\":\"udp\",\"port\":9,\"endPort\":15}]}]}"}],"expected":{"allow":[],"deny":["10.0.2.0/24","10.0.3.0/29","25.139.174.39/32","192.0.0.0/6","196.0.0.0/7","200.0.0.0/6","204.0.0.0/7","206.0.0.0/8","208.0.0.0/4","224.0.0.0/4","::ffff:55.35.254.0/120:tcp:443","::ffff:55.35.254.0/120:udp:443","::ffff:55.35.254.0/120:udp:65531","4000::/3"]}}, + {"name":"random #52","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.3.32/029\"}, {\"cidr\": \"2001:db8::2f0/126\", \"except\": [\"2001:db8::2f1\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 79, \"endPort\": 81}]}, {\"to\": [{\"cidr\": \"2001:db8::3a\", \"except\": [\"2001:db8::3a\"]}]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0200/119\", \"protocol\": \"tcp\", \"port\": 79, \"endPort\": 83}, {\"to\": [{\"cidr\": \"2001:db8::360/123\", \"except\": [\"10.0.1.72/255.255.255.248\"]}, {\"cidr\": \"10.0.2.36/31\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 442}, {\"protocol\": \"udp\", \"port\": 7, \"endPort\": 9}, {\"protocol\": \"udp\"}, {\"protocol\": \"udp\", \"port\": 65535}]}, {\"cidr\": \"10.0.2.208/0.0.0.15\", \"protocol\": \"tcp\", \"port\": 445}, {\"cidr\": \"2001:DB8::197/126\", \"except\": []}]}"}],"expected":{"error":"deny[3].to[0].except[0] must be contained in 2001:db8::360/123"}}, + {"name":"random #53","input":[{"text":"{\n \"allow\": [], \n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::3f4/126\"\n }, \n {\n \"cidr\": \"10.0.3.161/25\"\n }, \n {\n \"cidr\": \"10.0.0.0/24\"\n }, \n {\n \"cidr\": \"235.145.88.199/31\"\n }\n ], \n \"ports\": [\n {\n \"port\": 12\n }\n ]\n }\n ]\n}"}],"expected":{"allow":[],"deny":["10.0.0.0/24:tcp:12","10.0.0.0/24:udp:12","10.0.3.128/25:tcp:12","10.0.3.128/25:udp:12","235.145.88.198/31:tcp:12","235.145.88.198/31:udp:12","2001:db8::3f4/126:tcp:12","2001:db8::3f4/126:udp:12"]}}, + {"name":"random #54","input":[{"text":"{\n\t\"allow\": [], \n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::2c0/125\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.160/28\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 65534\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 79\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":[],"deny":["10.0.1.160/28:tcp:79","10.0.1.160/28:tcp:65534","2001:db8::2c0/125:tcp:79","2001:db8::2c0/125:tcp:65534"]}}, + {"name":"random #55","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #56","input":[{"text":"{\n\"allow\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0274\"\n}\n]\n}"}],"expected":{"allow":["2001:db8::274/128"],"deny":[]}}, + {"name":"random #57","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"2001:DB8::25C/118\",\"except\":[\"2001:db8::a8/125\",\"2001:db8::40/124\"]},{\"cidr\":\"10.0.2.32/27\"}],\"ports\":[{\"protocol\":\"udp\"}]},{\"to\":[{\"cidr\":\"c4f4:fdd6:6758:eeec:b3da:478a:45bd:c75c/127\"},{\"cidr\":\"10.0.2.128/24\",\"except\":[\"10.0.2.40/255.255.255.248\"]},{\"cidr\":\"10.0.0.148/031\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":65534},{\"protocol\":\"any\"},{\"protocol\":\"any\",\"port\":65532,\"endPort\":65535},{\"protocol\":\"any\",\"port\":444}]},{\"ports\":[{\"protocol\":\"any\",\"port\":442},{\"protocol\":\"tcp\",\"port\":12}]}],\"deny\":[{\"to\":[{\"cidr\":\"237.0.0.0/12\"},{\"cidr\":\"2001:db8::300/122\"}],\"ports\":[{\"protocol\":\"any\",\"port\":445},{\"protocol\":\"tcp\",\"port\":65532}]},{\"to\":[{\"cidr\":\"192.0.0.0/15.255.255.255\"},{\"cidr\":\"e12e:bf95:4038:93c5:51a7:b068:99f7:ed11/0\"},{\"cidr\":\"10.0.0.0/23\"},{\"cidr\":\"10.0.0.0/23\"}],\"ports\":[{\"port\":442},{\"protocol\":\"any\",\"port\":65535,\"endPort\":65535}]},{\"to\":[{\"cidr\":\"10.0.0.8/255.255.255.192\"},{\"cidr\":\"2001:db8::247/119\"},{\"cidr\":\"2001:db8::294/126\"}],\"ports\":[{\"protocol\":\"tcp\"}]},{\"to\":[{\"cidr\":\"10.0.1.84/255.255.255.254\"},{\"cidr\":\"86.197.155.27/1\",\"except\":[\"87.128.0.0/9\"]},{\"cidr\":\"10.0.2.0/23\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":10,\"endPort\":46931},{\"protocol\":\"tcp\"},{\"protocol\":\"udp\",\"port\":7}]},{\"cidr\":\"10.0.3.96/30\",\"except\":[],\"protocol\":\"udp\",\"port\":6,\"endPort\":64587},{\"cidr\":\"10.0.2.152/29\",\"protocol\":\"any\",\"port\":65531,\"endPort\":65533}]}"}],"expected":{"allow":["0.0.0.0/0:tcp:12","0.0.0.0/0:tcp:442","0.0.0.0/0:udp:442","10.0.0.148/31","10.0.2.0/24:udp","10.0.2.0/27","10.0.2.32/29","10.0.2.48/28","10.0.2.64/26","10.0.2.128/25","::/0:tcp:12","::/0:tcp:442","::/0:udp:442","2001:db8::/122:udp","2001:db8::50/124:udp","2001:db8::60/123:udp","2001:db8::80/123:udp","2001:db8::a0/125:udp","2001:db8::b0/124:udp","2001:db8::c0/122:udp","2001:db8::100/120:udp","2001:db8::200/119:udp","c4f4:fdd6:6758:eeec:b3da:478a:45bd:c75c/127"],"deny":["0.0.0.0/2:tcp","0.0.0.0/2:udp:7","10.0.0.0/23:udp:442","10.0.0.0/23:udp:65535","10.0.2.152/29:udp:65531-65533","10.0.3.96/30:udp:6-64587","64.0.0.0/4:tcp","64.0.0.0/4:udp:7","80.0.0.0/6:tcp","80.0.0.0/6:udp:7","84.0.0.0/7:tcp","84.0.0.0/7:udp:7","86.0.0.0/8:tcp","86.0.0.0/8:udp:7","87.0.0.0/9:tcp","87.0.0.0/9:udp:7","88.0.0.0/5:tcp","88.0.0.0/5:udp:7","96.0.0.0/3:tcp","96.0.0.0/3:udp:7","192.0.0.0/4:tcp:442","192.0.0.0/4:tcp:65535","192.0.0.0/4:udp:442","192.0.0.0/4:udp:65535","237.0.0.0/12:tcp:445","237.0.0.0/12:tcp:65532","237.0.0.0/12:udp:445","::/0:tcp:442","::/0:tcp:65535","::/0:udp:442","::/0:udp:65535","2001:db8::200/119:tcp","2001:db8::300/122:udp:445"]}}, + {"name":"random #58","input":[{"text":"{\n \"allow\": [\n {\n \"cidr\": \"10.0.1.128/0027\", \n \"except\": [\n \"10.0.1.137/0032\", \n \"10.0.1.135/32\", \n \"10.0.1.128/27\"\n ], \n \"protocol\": \"udp\", \n \"port\": 9, \n \"endPort\": 52106\n }, \n {\n \"cidr\": \"10.0.0.0/22\", \n \"protocol\": \"tcp\", \n \"port\": 441, \n \"endPort\": 445\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 80\n }, \n {\n \"protocol\": \"any\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"4e93:db03:5723:b1a1:40e1:2729:1b4f:8e7c/128\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 3, \n \"endPort\": 7\n }, \n {\n \"port\": 440\n }, \n {\n \"protocol\": \"tcp\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"679f:d470:c677:dea4:ba81:d73f:c6ab:255a/127\", \n \"except\": [\n \"679f:d470:c677:dea4:ba81:d73f:c6ab:255a/127\", \n \"679f:d470:c677:dea4:ba81:d73f:198.171.37.90/127\", \n \"679f:d470:c677:dea4:ba81:d73f:c6ab:255a/128\"\n ]\n }, \n {\n \"cidr\": \"2001:db8::/00121\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"udp\"\n }\n ]\n }, \n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0200/121\", \n \"protocol\": \"tcp\"\n }, \n {\n \"cidr\": \"128.0.0.0/1\", \n \"except\": [\n \"128.0.0.0/0002\"\n ], \n \"protocol\": \"tcp\", \n \"port\": 80, \n \"endPort\": 83\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:DB8::200/00119\"\n }, \n {\n \"cidr\": \"10.0.3.110/27\"\n }\n ], \n \"ports\": [\n {\n \"port\": 12, \n \"endPort\": 12\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 65531, \n \"endPort\": 65531\n }\n ]\n }\n ], \n \"deny\": [\n {\n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 441, \n \"endPort\": 447\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 2\n }\n ]\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 440\n }, \n {\n \"protocol\": \"any\", \n \"port\": 65534, \n \"endPort\": 65535\n }, \n {\n \"protocol\": \"any\", \n \"port\": 8, \n \"endPort\": 14\n }\n ]\n }, \n {\n \"cidr\": \"10.0.0.64/00026\", \n \"except\": [\n \"10.0.0.96/27\"\n ], \n \"protocol\": \"tcp\", \n \"port\": 82\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.37/27\"\n }, \n {\n \"cidr\": \"10.0.1.128/25\"\n }, \n {\n \"cidr\": \"65af:8ce5:3468:bb28::/64\"\n }, \n {\n \"cidr\": \"10.0.0.195\", \n \"except\": [\n \"10.0.0.195\"\n ]\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["0.0.0.0/0:tcp:2","0.0.0.0/0:tcp:8-14","0.0.0.0/0:tcp:440","0.0.0.0/0:tcp:65534-65535","0.0.0.0/0:udp:8-14","0.0.0.0/0:udp:441-447","0.0.0.0/0:udp:65534-65535","10.0.0.64/27:tcp:82","10.0.1.128/25","10.0.3.32/27","::/0:tcp:2","::/0:tcp:8-14","::/0:tcp:440","::/0:tcp:65534-65535","::/0:udp:8-14","::/0:udp:441-447","::/0:udp:65534-65535","65af:8ce5:3468:bb28::/64"]}}, + {"name":"random #59","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.0.0/118\",\"except\":[\"2001:db8::40/122\",\"2001:db8::28a/119\"]},{\"cidr\":\"20a3:2087:9c8b:505:d7d0:fa3c::/96\",\"except\":[\"20a3:2087:9c8b:0505:d7d0:fa3c:8800:0000/0101\"]}],\"ports\":[{}]},{\"to\":[{\"cidr\":\"10.0.2.152/22\",\"except\":[\"10.0.1.0/24\"]},{\"cidr\":\"10.0.1.19/23\",\"except\":[\"10.0.0.108/255.255.255.192\",\"10.0.1.96/27\"]}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"tcp\"},{\"protocol\":\"tcp\",\"port\":65533,\"endPort\":65534},{\"protocol\":\"tcp\"}]},{\"to\":[{\"cidr\":\"1ef:5762:a2c3:7878:d7f:c7ae:c117:2500/120\",\"except\":[]},{\"cidr\":\"2001:db8::/118\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":65530},{\"protocol\":\"any\",\"port\":65534},{\"protocol\":\"udp\",\"port\":440,\"endPort\":444},{\"protocol\":\"udp\",\"port\":443}]},{\"cidr\":\"2001:db8::231/128\",\"protocol\":\"udp\",\"port\":209},{\"cidr\":\"10.0.2.238/32\",\"protocol\":\"tcp\",\"port\":389},{\"cidr\":\"10.0.2.244/30\",\"protocol\":\"any\",\"port\":53},{\"cidr\":\"10.0.1.92/30\",\"protocol\":\"any\",\"port\":163},{\"cidr\":\"10.0.1.218/31\",\"protocol\":\"any\",\"port\":67},{\"cidr\":\"2001:db8::318/128\",\"protocol\":\"udp\",\"port\":235},{\"cidr\":\"2001:db8::171/128\",\"protocol\":\"any\",\"port\":297},{\"cidr\":\"2001:db8::25e/128\",\"protocol\":\"tcp\",\"port\":47,\"endPort\":49},{\"cidr\":\"10.0.3.144/31\",\"protocol\":\"udp\",\"port\":295,\"endPort\":297},{\"cidr\":\"10.0.2.172/31\",\"protocol\":\"any\",\"port\":119},{\"cidr\":\"2001:db8::1c4/128\",\"protocol\":\"any\",\"port\":109,\"endPort\":112},{\"cidr\":\"2001:db8::22d/128\",\"protocol\":\"tcp\",\"port\":141},{\"cidr\":\"10.0.0.184/30\",\"protocol\":\"udp\",\"port\":265,\"endPort\":266},{\"cidr\":\"10.0.2.24/30\",\"protocol\":\"tcp\",\"port\":89,\"endPort\":90},{\"cidr\":\"10.0.1.162/31\",\"protocol\":\"any\",\"port\":33,\"endPort\":35},{\"cidr\":\"10.0.3.53/32\",\"protocol\":\"udp\",\"port\":341},{\"cidr\":\"2001:db8::ec/128\",\"protocol\":\"udp\",\"port\":241},{\"cidr\":\"10.0.2.96/30\",\"protocol\":\"any\",\"port\":37,\"endPort\":37},{\"cidr\":\"2001:db8::26e/128\",\"protocol\":\"any\",\"port\":397},{\"cidr\":\"2001:db8::1c8/128\",\"protocol\":\"tcp\",\"port\":69},{\"cidr\":\"2001:db8::16e/128\",\"protocol\":\"any\",\"port\":59,\"endPort\":59},{\"cidr\":\"10.0.0.20/31\",\"protocol\":\"any\",\"port\":219},{\"cidr\":\"2001:db8::2bb/128\",\"protocol\":\"udp\",\"port\":9},{\"cidr\":\"10.0.0.146/31\",\"protocol\":\"tcp\",\"port\":125},{\"cidr\":\"2001:db8::96/128\",\"protocol\":\"any\",\"port\":203},{\"cidr\":\"10.0.3.160/30\",\"protocol\":\"tcp\",\"port\":283},{\"cidr\":\"10.0.1.208/30\",\"protocol\":\"udp\",\"port\":113},{\"cidr\":\"10.0.3.80/31\",\"protocol\":\"udp\",\"port\":21},{\"cidr\":\"10.0.0.145/32\",\"protocol\":\"tcp\",\"port\":339},{\"cidr\":\"10.0.1.47/32\",\"protocol\":\"tcp\",\"port\":9,\"endPort\":13},{\"cidr\":\"10.0.1.80/31\",\"protocol\":\"udp\",\"port\":143,\"endPort\":147},{\"cidr\":\"2001:db8::350/128\",\"protocol\":\"tcp\",\"port\":271},{\"cidr\":\"10.0.1.140/31\",\"protocol\":\"any\",\"port\":257,\"endPort\":257},{\"cidr\":\"10.0.3.58/32\",\"protocol\":\"udp\",\"port\":233},{\"cidr\":\"10.0.3.10/31\",\"protocol\":\"udp\",\"port\":333},{\"cidr\":\"10.0.3.190/31\",\"protocol\":\"tcp\",\"port\":277},{\"cidr\":\"10.0.3.188/31\",\"protocol\":\"any\",\"port\":79},{\"cidr\":\"10.0.0.78/32\",\"protocol\":\"tcp\",\"port\":147},{\"cidr\":\"10.0.2.216/30\",\"protocol\":\"tcp\",\"port\":91},{\"cidr\":\"2001:db8::1b0/128\",\"protocol\":\"any\",\"port\":155,\"endPort\":157},{\"cidr\":\"10.0.1.92/30\",\"protocol\":\"tcp\",\"port\":211},{\"cidr\":\"10.0.3.198/32\",\"protocol\":\"any\",\"port\":57,\"endPort\":58},{\"cidr\":\"2001:db8::d5/128\",\"protocol\":\"udp\",\"port\":301},{\"cidr\":\"10.0.2.84/30\",\"protocol\":\"tcp\",\"port\":395},{\"cidr\":\"10.0.2.162/31\",\"protocol\":\"tcp\",\"port\":201},{\"cidr\":\"10.0.0.252/31\",\"protocol\":\"any\",\"port\":201},{\"cidr\":\"2001:db8::68/128\",\"protocol\":\"tcp\",\"port\":301,\"endPort\":305},{\"cidr\":\"10.0.3.150/31\",\"protocol\":\"tcp\",\"port\":81},{\"cidr\":\"10.0.0.162/32\",\"protocol\":\"udp\",\"port\":135,\"endPort\":135},{\"cidr\":\"10.0.0.44/30\",\"protocol\":\"tcp\",\"port\":133},{\"cidr\":\"2001:db8::3f2/128\",\"protocol\":\"udp\",\"port\":339},{\"cidr\":\"10.0.0.254/32\",\"protocol\":\"any\",\"port\":121},{\"cidr\":\"10.0.3.16/30\",\"protocol\":\"tcp\",\"port\":139},{\"cidr\":\"10.0.0.181/32\",\"protocol\":\"udp\",\"port\":241,\"endPort\":241},{\"cidr\":\"2001:db8::138/128\",\"protocol\":\"any\",\"port\":155},{\"cidr\":\"10.0.1.28/30\",\"protocol\":\"tcp\",\"port\":351,\"endPort\":355},{\"cidr\":\"10.0.3.42/32\",\"protocol\":\"tcp\",\"port\":389},{\"cidr\":\"2001:db8::284/128\",\"protocol\":\"any\",\"port\":395},{\"cidr\":\"2001:db8::31e/128\",\"protocol\":\"udp\",\"port\":201},{\"cidr\":\"10.0.0.249/32\",\"protocol\":\"udp\",\"port\":35},{\"cidr\":\"2001:db8::271/128\",\"protocol\":\"any\",\"port\":197},{\"cidr\":\"2001:db8::202/128\",\"protocol\":\"tcp\",\"port\":225,\"endPort\":229},{\"cidr\":\"10.0.3.209/32\",\"protocol\":\"any\",\"port\":133,\"endPort\":137},{\"cidr\":\"2001:db8::20b/128\",\"protocol\":\"udp\",\"port\":105},{\"cidr\":\"10.0.2.216/31\",\"protocol\":\"udp\",\"port\":109},{\"cidr\":\"10.0.1.96/30\",\"protocol\":\"tcp\",\"port\":79},{\"cidr\":\"2001:db8::1d1/128\",\"protocol\":\"udp\",\"port\":377},{\"cidr\":\"10.0.2.115/32\",\"protocol\":\"udp\",\"port\":107},{\"cidr\":\"10.0.0.128/30\",\"protocol\":\"any\",\"port\":117},{\"cidr\":\"2001:db8::7/128\",\"protocol\":\"udp\",\"port\":265},{\"cidr\":\"2001:db8::21c/128\",\"protocol\":\"any\",\"port\":239},{\"cidr\":\"2001:db8::377/128\",\"protocol\":\"udp\",\"port\":25},{\"cidr\":\"10.0.1.112/30\",\"protocol\":\"tcp\",\"port\":77,\"endPort\":80},{\"cidr\":\"10.0.0.200/30\",\"protocol\":\"udp\",\"port\":213},{\"cidr\":\"2001:db8::287/128\",\"protocol\":\"tcp\",\"port\":63},{\"cidr\":\"10.0.1.18/31\",\"protocol\":\"any\",\"port\":67},{\"cidr\":\"10.0.1.13/32\",\"protocol\":\"udp\",\"port\":35},{\"cidr\":\"2001:db8::16a/128\",\"protocol\":\"any\",\"port\":357},{\"cidr\":\"2001:db8::15/128\",\"protocol\":\"tcp\",\"port\":265},{\"cidr\":\"10.0.2.18/32\",\"protocol\":\"tcp\",\"port\":321},{\"cidr\":\"10.0.0.94/32\",\"protocol\":\"tcp\",\"port\":109},{\"cidr\":\"10.0.0.26/31\",\"protocol\":\"any\",\"port\":15,\"endPort\":16},{\"cidr\":\"2001:db8::1a1/128\",\"protocol\":\"tcp\",\"port\":311},{\"cidr\":\"2001:db8::1c3/128\",\"protocol\":\"any\",\"port\":5},{\"cidr\":\"10.0.3.148/30\",\"protocol\":\"udp\",\"port\":173},{\"cidr\":\"10.0.3.60/32\",\"protocol\":\"any\",\"port\":91},{\"cidr\":\"10.0.3.244/31\",\"protocol\":\"udp\",\"port\":289},{\"cidr\":\"10.0.0.128/32\",\"protocol\":\"any\",\"port\":111},{\"cidr\":\"10.0.1.36/31\",\"protocol\":\"any\",\"port\":221,\"endPort\":225},{\"cidr\":\"2001:db8::12a/128\",\"protocol\":\"udp\",\"port\":147,\"endPort\":148},{\"cidr\":\"10.0.3.58/31\",\"protocol\":\"udp\",\"port\":29},{\"cidr\":\"2001:db8::1b3/128\",\"protocol\":\"any\",\"port\":351},{\"cidr\":\"2001:db8::3a8/128\",\"protocol\":\"tcp\",\"port\":51},{\"cidr\":\"10.0.1.150/32\",\"protocol\":\"tcp\",\"port\":297},{\"cidr\":\"10.0.0.98/31\",\"protocol\":\"udp\",\"port\":277},{\"cidr\":\"10.0.1.7/32\",\"protocol\":\"tcp\",\"port\":283},{\"cidr\":\"2001:db8::42/128\",\"protocol\":\"any\",\"port\":165,\"endPort\":167},{\"cidr\":\"10.0.2.68/30\",\"protocol\":\"any\",\"port\":77,\"endPort\":77},{\"cidr\":\"10.0.0.120/30\",\"protocol\":\"udp\",\"port\":33},{\"cidr\":\"10.0.1.56/30\",\"protocol\":\"udp\",\"port\":301},{\"cidr\":\"10.0.2.82/31\",\"protocol\":\"udp\",\"port\":393,\"endPort\":393},{\"cidr\":\"10.0.3.64/31\",\"protocol\":\"any\",\"port\":281,\"endPort\":281},{\"cidr\":\"2001:db8::28/128\",\"protocol\":\"udp\",\"port\":157},{\"cidr\":\"10.0.3.224/31\",\"protocol\":\"tcp\",\"port\":91},{\"cidr\":\"10.0.2.148/31\",\"protocol\":\"any\",\"port\":99},{\"cidr\":\"2001:db8::3db/128\",\"protocol\":\"udp\",\"port\":191},{\"cidr\":\"2001:db8::3a4/128\",\"protocol\":\"tcp\",\"port\":277,\"endPort\":279},{\"cidr\":\"10.0.1.192/32\",\"protocol\":\"any\",\"port\":333,\"endPort\":337},{\"cidr\":\"2001:db8::254/128\",\"protocol\":\"any\",\"port\":35},{\"cidr\":\"2001:db8::d0/128\",\"protocol\":\"tcp\",\"port\":391,\"endPort\":394},{\"cidr\":\"10.0.3.184/30\",\"protocol\":\"tcp\",\"port\":259},{\"cidr\":\"10.0.3.22/31\",\"protocol\":\"tcp\",\"port\":229},{\"cidr\":\"10.0.0.160/30\",\"protocol\":\"any\",\"port\":163},{\"cidr\":\"10.0.2.124/31\",\"protocol\":\"udp\",\"port\":277},{\"cidr\":\"10.0.2.140/30\",\"protocol\":\"tcp\",\"port\":121},{\"cidr\":\"10.0.3.37/32\",\"protocol\":\"udp\",\"port\":137,\"endPort\":141},{\"cidr\":\"10.0.2.247/32\",\"protocol\":\"tcp\",\"port\":269},{\"cidr\":\"10.0.1.220/31\",\"protocol\":\"udp\",\"port\":361},{\"cidr\":\"10.0.2.140/30\",\"protocol\":\"udp\",\"port\":395},{\"cidr\":\"10.0.0.36/32\",\"protocol\":\"tcp\",\"port\":59,\"endPort\":60},{\"cidr\":\"10.0.2.116/30\",\"protocol\":\"udp\",\"port\":105},{\"cidr\":\"10.0.3.48/31\",\"protocol\":\"any\",\"port\":121},{\"cidr\":\"10.0.0.60/30\",\"protocol\":\"tcp\",\"port\":309,\"endPort\":313},{\"cidr\":\"10.0.3.72/30\",\"protocol\":\"udp\",\"port\":45,\"endPort\":45},{\"cidr\":\"10.0.2.178/31\",\"protocol\":\"udp\",\"port\":337,\"endPort\":339},{\"cidr\":\"10.0.0.0/30\",\"protocol\":\"tcp\",\"port\":369},{\"cidr\":\"2001:db8::33b/128\",\"protocol\":\"any\",\"port\":323,\"endPort\":324},{\"cidr\":\"10.0.1.248/30\",\"protocol\":\"udp\",\"port\":287},{\"cidr\":\"10.0.1.56/30\",\"protocol\":\"tcp\",\"port\":295},{\"cidr\":\"2001:db8::329/128\",\"protocol\":\"udp\",\"port\":379},{\"cidr\":\"2001:db8::13b/128\",\"protocol\":\"tcp\",\"port\":375},{\"cidr\":\"2001:db8::26a/128\",\"protocol\":\"tcp\",\"port\":297},{\"cidr\":\"10.0.2.100/31\",\"protocol\":\"tcp\",\"port\":21},{\"cidr\":\"2001:db8::b5/128\",\"protocol\":\"tcp\",\"port\":31,\"endPort\":35},{\"cidr\":\"2001:db8::221/128\",\"protocol\":\"tcp\",\"port\":105},{\"cidr\":\"10.0.3.96/30\",\"protocol\":\"udp\",\"port\":187,\"endPort\":187},{\"cidr\":\"10.0.1.152/30\",\"protocol\":\"udp\",\"port\":81},{\"cidr\":\"10.0.2.100/30\",\"protocol\":\"udp\",\"port\":95},{\"cidr\":\"10.0.1.220/30\",\"protocol\":\"tcp\",\"port\":311,\"endPort\":312},{\"cidr\":\"10.0.1.196/30\",\"protocol\":\"udp\",\"port\":325},{\"cidr\":\"10.0.0.53/32\",\"protocol\":\"udp\",\"port\":25,\"endPort\":29}]}"}],"expected":{"allow":["10.0.0.0/24:tcp","10.0.0.20/31:udp:219","10.0.0.26/31:udp:15-16","10.0.0.53/32:udp:25-29","10.0.0.98/31:udp:277","10.0.0.120/30:udp:33","10.0.0.128/30:udp:117","10.0.0.128/32:udp:111","10.0.0.160/30:udp:163","10.0.0.162/32:udp:135","10.0.0.181/32:udp:241","10.0.0.184/30:udp:265-266","10.0.0.200/30:udp:213","10.0.0.249/32:udp:35","10.0.0.252/31:udp:201","10.0.0.254/32:udp:121","10.0.1.0/26:tcp","10.0.1.13/32:udp:35","10.0.1.18/31:udp:67","10.0.1.36/31:udp:221-225","10.0.1.56/30:udp:301","10.0.1.64/27:tcp","10.0.1.80/31:udp:143-147","10.0.1.92/30:udp:163","10.0.1.96/30:tcp:79","10.0.1.112/30:tcp:77-80","10.0.1.128/25:tcp","10.0.1.140/31:udp:257","10.0.1.152/30:udp:81","10.0.1.162/31:udp:33-35","10.0.1.192/32:udp:333-337","10.0.1.196/30:udp:325","10.0.1.208/30:udp:113","10.0.1.218/31:udp:67","10.0.1.220/31:udp:361","10.0.1.248/30:udp:287","10.0.2.0/23:tcp","10.0.2.68/30:udp:77","10.0.2.82/31:udp:393","10.0.2.96/30:udp:37","10.0.2.100/30:udp:95","10.0.2.115/32:udp:107","10.0.2.116/30:udp:105","10.0.2.124/31:udp:277","10.0.2.140/30:udp:395","10.0.2.148/31:udp:99","10.0.2.172/31:udp:119","10.0.2.178/31:udp:337-339","10.0.2.216/31:udp:109","10.0.2.244/30:udp:53","10.0.3.10/31:udp:333","10.0.3.37/32:udp:137-141","10.0.3.48/31:udp:121","10.0.3.53/32:udp:341","10.0.3.58/31:udp:29","10.0.3.58/32:udp:233","10.0.3.60/32:udp:91","10.0.3.64/31:udp:281","10.0.3.72/30:udp:45","10.0.3.80/31:udp:21","10.0.3.96/30:udp:187","10.0.3.144/31:udp:295-297","10.0.3.148/30:udp:173","10.0.3.188/31:udp:79","10.0.3.198/32:udp:57-58","10.0.3.209/32:udp:133-137","10.0.3.244/31:udp:289","1ef:5762:a2c3:7878:d7f:c7ae:c117:2500/120:tcp:65530","1ef:5762:a2c3:7878:d7f:c7ae:c117:2500/120:tcp:65534","1ef:5762:a2c3:7878:d7f:c7ae:c117:2500/120:udp:440-444","1ef:5762:a2c3:7878:d7f:c7ae:c117:2500/120:udp:65534","2001:db8::/118:tcp:65530","2001:db8::/118:tcp:65534","2001:db8::/118:udp:440-444","2001:db8::/118:udp:65534","2001:db8::/122","2001:db8::42/128:tcp:165-167","2001:db8::42/128:udp:165-167","2001:db8::68/128:tcp:301-305","2001:db8::80/121","2001:db8::100/120","2001:db8::202/128:tcp:225-229","2001:db8::20b/128:udp:105","2001:db8::21c/128:tcp:239","2001:db8::21c/128:udp:239","2001:db8::221/128:tcp:105","2001:db8::22d/128:tcp:141","2001:db8::231/128:udp:209","2001:db8::254/128:tcp:35","2001:db8::254/128:udp:35","2001:db8::25e/128:tcp:47-49","2001:db8::26a/128:tcp:297","2001:db8::26e/128:tcp:397","2001:db8::26e/128:udp:397","2001:db8::271/128:tcp:197","2001:db8::271/128:udp:197","2001:db8::284/128:tcp:395","2001:db8::284/128:udp:395","2001:db8::287/128:tcp:63","2001:db8::2bb/128:udp:9","2001:db8::318/128:udp:235","2001:db8::31e/128:udp:201","2001:db8::329/128:udp:379","2001:db8::33b/128:tcp:323-324","2001:db8::33b/128:udp:323-324","2001:db8::350/128:tcp:271","2001:db8::377/128:udp:25","2001:db8::3a4/128:tcp:277-279","2001:db8::3a8/128:tcp:51","2001:db8::3db/128:udp:191","2001:db8::3f2/128:udp:339","20a3:2087:9c8b:505:d7d0:fa3c::/97","20a3:2087:9c8b:505:d7d0:fa3c:8000:0/101","20a3:2087:9c8b:505:d7d0:fa3c:9000:0/100","20a3:2087:9c8b:505:d7d0:fa3c:a000:0/99","20a3:2087:9c8b:505:d7d0:fa3c:c000:0/98"],"deny":[]}}, + {"name":"random #60","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #61","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.96/00029\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:DB8::250/125\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.172/255.255.255.252\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.217/31\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"114.113.0.0/16\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0368/125\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65532\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 79, \n\t\t\t\t\t\"endPort\": 80\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 3\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.0.96/29","10.0.1.172/30","10.0.3.216/31","114.113.0.0/16","2001:db8::250/125","2001:db8::368/125"],"deny":[]}}, + {"name":"random #62","input":[{"text":"{\"allow\": []}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #63","input":[{"text":"{\"allow\":[{\"protocol\":\"udp\",\"port\":78,\"endPort\":81},{\"cidr\":\"10.0.0.0/23\",\"protocol\":\"tcp\"},{},{\"to\":[{\"cidr\":\"10.0.2.32/0027\",\"except\":[\"10.0.2.32/27\"]},{\"cidr\":\"2001:db8::200/119\",\"except\":[\"2001:db8::32f/127\",\"2001:0db8:0000:0000:0000:0000:0000:0390/000124\"]},{\"cidr\":\"10.0.0.0/22\",\"except\":[\"10.0.0.128/25\"]},{\"cidr\":\"10.0.3.124/31\",\"except\":[\"10.0.3.125\"]}],\"ports\":[{\"protocol\":\"tcp\",\"port\":80,\"endPort\":85}]}],\"deny\":[]}"}],"expected":{"error":"allow[0].cidr is required"}}, + {"name":"random #64","input":[{"text":"{\n\"allow\":[\n{\n\"cidr\":\"10.0.2.245/0030\",\n\"protocol\":\"udp\",\n\"port\":78,\n\"endPort\":81\n}\n],\n\"deny\":[]\n}"}],"expected":{"allow":["10.0.2.244/30:udp:78-81"],"deny":[]}}, + {"name":"random #65","input":[{"text":"{\"allow\": [], \"allow\": [{\"to\": [{\"cidr\": \"10.0.3.64/27\"}, {\"cidr\": \"2001:db8::/118\"}, {\"cidr\": \"10.0.3.23/32\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 79, \"endPort\": 85}, {\"protocol\": \"tcp\", \"port\": 79}, {\"protocol\": \"any\"}]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:030a/127\", \"except\": [\"2001:db8::30b/128\", \"2001:DB8::30B/128\"]}, {\"cidr\": \"10.0.0.128/25\", \"protocol\": \"tcp\", \"port\": 82, \"endPort\": 85}, {\"cidr\": \"d7e:aee2:7423:73d0::/64\", \"protocol\": \"tcp\", \"port\": 443, \"endPort\": 446}, {\"to\": [{\"cidr\": \"10.0.2.64/26\"}, {\"cidr\": \"2001:db8::200/120\", \"except\": [\"2001:db8::240/122\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65532, \"endPort\": 65535}]}, {\"ports\": [{\"protocol\": \"tcp\", \"port\": 12, \"endPort\": 16}, {\"protocol\": \"tcp\", \"port\": 78}, {\"protocol\": \"tcp\", \"port\": 8}]}, {\"to\": [{\"cidr\": \"10.0.3.223/30\", \"except\": []}, {\"cidr\": \"68.82.198.45/4\", \"except\": [\"71.10.165.254/11\", \"64.0.0.0/4\", \"73.144.0.0/12\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 441}]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:02e0/126\", \"protocol\": \"udp\", \"port\": 80}, {\"cidr\": \"2001:db8::c0/123\", \"protocol\": \"icmp\"}, {\"cidr\": \"10.0.0.199/25\", \"except\": [], \"protocol\": \"tcp\", \"port\": 443}, {\"to\": [{\"cidr\": \"10.0.3.240/255.255.255.255\", \"except\": [\"10.0.3.240/32\", \"10.0.3.240\", \"10.0.3.240/32\"]}, {\"cidr\": \"10.0.0.136/30\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"any\", \"port\": 8, \"endPort\": 53807}, {\"protocol\": \"any\"}, {\"protocol\": \"icmp\"}]}, {\"cidr\": \"2001:db8::3a0/124\", \"protocol\": \"udp\", \"port\": 440}, {\"to\": [{\"cidr\": \"238.97.210.57/8\", \"except\": [\"238.32.0.0/0011\", \"238.0.0.0/0.255.255.255\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 80}, {\"protocol\": \"tcp\", \"port\": 65533, \"endPort\": 65534}]}, {\"to\": [{\"cidr\": \"10.0.1.0/24\"}, {\"cidr\": \"10.0.1.55/255.255.255.254\"}, {\"cidr\": \"2001:db8::324/126\", \"except\": [\"2001:db8::327/128\", \"2001:db8::327/128\", \"2001:db8::326/127\"]}, {\"cidr\": \"2001:db8::2a0/127\", \"except\": [\"2001:db8::2a0\", \"2001:db8::2a1\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 443}, {\"protocol\": \"tcp\"}, {\"protocol\": \"udp\"}]}, {\"to\": [{\"cidr\": \"10.0.2.24/022\"}, {\"cidr\": \"0.0.0.0/0\"}, {\"cidr\": \"2001:db8::280/124\", \"except\": [\"2001:db8::289\"]}], \"ports\": [{\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"10.0.2.242/032\", \"except\": [\"10.0.2.242/32\", \"10.0.2.242\", \"10.0.2.242\"]}, {\"cidr\": \"2001:db8::/00118\"}, {\"cidr\": \"10.0.1.23/22\", \"except\": []}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 81, \"endPort\": 12861}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"64.0.0.0/2\"}, {\"cidr\": \"10.0.0.0/23\"}, {\"cidr\": \"10.0.2.143/255.255.255.248\"}]}, {\"to\": [{\"cidr\": \"10.0.2.0/25\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65534}, {\"protocol\": \"udp\"}]}, {\"to\": [{\"cidr\": \"2001:DB8::300/120\", \"except\": [\"2001:db8::300/120\", \"2001:db8::373/126\", \"2001:db8::3cb/123\"]}], \"ports\": [{\"protocol\": \"any\", \"port\": 65532, \"endPort\": 65534}]}, {\"to\": [{\"cidr\": \"10.0.1.72/30\", \"except\": [\"10.0.1.72/31\"]}]}, {\"cidr\": \"2.13.177.0/24\"}, {\"to\": [{\"cidr\": \"10.0.2.0/00024\"}, {\"cidr\": \"10.0.0.0/22\"}, {\"cidr\": \"10.0.1.128/26\", \"except\": [\"10.0.1.168/30\"]}, {\"cidr\": \"2001:db8::/118\"}]}, {\"cidr\": \"10.0.0.158/0.0.0.63\", \"protocol\": \"any\"}, {\"to\": [{\"cidr\": \"10.0.1.220/32\"}, {\"cidr\": \"10.0.3.213/28\"}, {\"cidr\": \"::/1\"}, {\"cidr\": \"7041:8e5c:d691:dc99:404a:5afe:332f:4200/120\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 445, \"endPort\": 448}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 81}]}, {\"to\": [{\"cidr\": \"10.0.0.150/27\", \"except\": []}, {\"cidr\": \"10.0.2.32/28\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65533}]}, {\"to\": [{\"cidr\": \"10.0.2.240/29\", \"except\": []}, {\"cidr\": \"2001:db8::/119\", \"except\": []}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 81, \"endPort\": 84}, {\"protocol\": \"any\", \"port\": 80}, {\"port\": 82}]}, {\"to\": [{\"cidr\": \"b0c8:6cf5:62f5:3d45:6c6a:2a08:ee00:0/104\", \"except\": [\"b0c8:6cf5:62f5:3d45:6c6a:2a08:ee30:0/108\", \"b0c8:6cf5:62f5:3d45:6c6a:2a08:ee68:e6a4/112\"]}]}, {\"cidr\": \"10.0.2.0/255.255.255.0\", \"protocol\": \"udp\", \"port\": 65531, \"endPort\": 65535}, {\"to\": [{\"cidr\": \"10.0.1.0/24\"}, {\"cidr\": \"0.0.0.0/0\"}, {\"cidr\": \"2001:db8::74/126\"}, {\"cidr\": \"10.0.0.0/22\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 442}, {\"port\": 5}]}, {\"to\": [{\"cidr\": \"c000::/3\", \"except\": []}, {\"cidr\": \"10.0.0.32/27\"}, {\"cidr\": \"10.0.2.0/25\", \"except\": [\"10.0.2.64/0.0.0.63\"]}, {\"cidr\": \"10.0.0.0/0.0.1.255\", \"except\": []}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 441, \"endPort\": 443}, {\"protocol\": \"any\", \"port\": 444}, {\"protocol\": \"any\", \"port\": 65532, \"endPort\": 65535}, {}]}, {\"to\": [{\"cidr\": \"243.95.181.206/1\"}, {\"cidr\": \"5ba5:168c:f289:a649:9763:2922:895a:216a/127\"}, {\"cidr\": \"5.0.0.0/255.240.0.0\"}]}, {\"to\": [{\"cidr\": \"10.0.3.219/30\"}, {\"cidr\": \"10.0.3.162/23\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"udp\", \"port\": 443, \"endPort\": 449}, {}]}, {\"to\": [{\"cidr\": \"10.0.3.66/28\"}, {\"cidr\": \"2001:db8::/118\", \"except\": []}, {\"cidr\": \"10.0.0.0/23\"}]}, {\"cidr\": \"10.0.0.0/255.255.252.0\", \"protocol\": \"tcp\", \"port\": 78}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0200/119\"}, {\"cidr\": \"10.0.2.128/25\"}, {\"cidr\": \"2001:db8::228/125\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 9, \"endPort\": 15}]}, {\"cidr\": \"5e3a:5bc8:d088:950a:3707:d119:e3ad:4de7/1\", \"protocol\": \"any\", \"port\": 79, \"endPort\": 79}, {\"cidr\": \"2001:db8::340/122\", \"protocol\": \"icmp\"}, {\"cidr\": \"10.0.2.128/24\", \"protocol\": \"any\", \"port\": 441}, {\"to\": [{\"cidr\": \"17.182.59.0/31\"}, {\"cidr\": \"b127:2eeb:60b7:b773:3951:2ca9:5300:0/104\", \"except\": [\"b127:2eeb:60b7:b773:3951:2ca9:5360:0000/108\", \"b127:2eeb:60b7:b773:3951:2ca9:53f8:0/109\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0180/121\"}, {\"cidr\": \"10.0.2.98/31\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"udp\", \"port\": 7}, {\"protocol\": \"any\", \"port\": 65535, \"endPort\": 65535}]}], \"deny\": [{\"cidr\": \"2001:DB8::340/123\", \"except\": [], \"protocol\": \"tcp\", \"port\": 78}, {\"cidr\": \"10.0.1.32/27\", \"protocol\": \"tcp\", \"port\": 65533}, {\"to\": [{\"cidr\": \"10.0.1.98\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 80, \"endPort\": 81}]}, {\"cidr\": \"225.166.241.163\", \"except\": [\"225.166.241.163\"], \"protocol\": \"tcp\"}]}"}],"expected":{"error":"duplicate JSON property: allow"}}, + {"name":"random #66","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"10.0.2.44/25\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":9},{\"protocol\":\"icmp\"},{\"protocol\":\"tcp\",\"port\":80,\"endPort\":81}]},{\"to\":[{\"cidr\":\"3.188.163.65/0\"}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"any\",\"port\":81,\"endPort\":82},{\"protocol\":\"any\",\"port\":81,\"endPort\":81},{\"protocol\":\"any\",\"port\":5,\"endPort\":7}]},{\"to\":[{\"cidr\":\"4004:a5da:24c2:8a6e:b313:af45:3153:7cae/3\",\"except\":[\"5000::/5\"]}],\"ports\":[{\"protocol\":\"udp\",\"port\":65533,\"endPort\":65535}]},{\"to\":[{\"cidr\":\"0.0.0.0/128.0.0.0\"},{\"cidr\":\"110.85.110.95/16\"},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.2.0/119\"},{\"cidr\":\"10.0.2.0/025\",\"except\":[\"10.0.2.32/27\",\"10.0.2.80/28\",\"10.0.2.100/31\"]}],\"ports\":[{\"protocol\":\"tcp\",\"port\":65530},{\"protocol\":\"any\",\"port\":2,\"endPort\":3975}]},{\"ports\":[{\"protocol\":\"any\",\"port\":82},{\"protocol\":\"any\",\"port\":7,\"endPort\":11},{\"protocol\":\"icmp\"},{\"protocol\":\"tcp\",\"port\":65532,\"endPort\":65534}]},{},{\"to\":[{\"cidr\":\"2001:db8::200/00119\"}]},{\"to\":[{\"cidr\":\"10.0.3.0/24\"},{\"cidr\":\"10.0.2.0/23\"}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"udp\",\"port\":3},{\"protocol\":\"udp\",\"port\":6}]},{\"cidr\":\"10.0.0.32/32\",\"protocol\":\"tcp\",\"port\":213},{\"cidr\":\"10.0.1.196/31\",\"protocol\":\"tcp\",\"port\":231},{\"cidr\":\"10.0.1.99/32\",\"protocol\":\"tcp\",\"port\":5},{\"cidr\":\"10.0.1.180/31\",\"protocol\":\"tcp\",\"port\":195},{\"cidr\":\"10.0.0.60/30\",\"protocol\":\"any\",\"port\":25},{\"cidr\":\"2001:db8::2be/128\",\"protocol\":\"tcp\",\"port\":97,\"endPort\":101},{\"cidr\":\"10.0.3.17/32\",\"protocol\":\"any\",\"port\":57},{\"cidr\":\"2001:db8::1c1/128\",\"protocol\":\"tcp\",\"port\":215},{\"cidr\":\"2001:db8::299/128\",\"protocol\":\"any\",\"port\":157},{\"cidr\":\"10.0.2.108/30\",\"protocol\":\"udp\",\"port\":15},{\"cidr\":\"10.0.3.148/30\",\"protocol\":\"udp\",\"port\":205},{\"cidr\":\"10.0.3.7/32\",\"protocol\":\"any\",\"port\":317,\"endPort\":319},{\"cidr\":\"2001:db8::1f5/128\",\"protocol\":\"udp\",\"port\":153},{\"cidr\":\"10.0.0.182/32\",\"protocol\":\"udp\",\"port\":371},{\"cidr\":\"2001:db8::157/128\",\"protocol\":\"udp\",\"port\":335,\"endPort\":339},{\"cidr\":\"10.0.1.180/31\",\"protocol\":\"tcp\",\"port\":193},{\"cidr\":\"10.0.3.220/30\",\"protocol\":\"tcp\",\"port\":335},{\"cidr\":\"10.0.3.127/32\",\"protocol\":\"tcp\",\"port\":321},{\"cidr\":\"10.0.3.128/31\",\"protocol\":\"tcp\",\"port\":59,\"endPort\":61},{\"cidr\":\"2001:db8::32c/128\",\"protocol\":\"udp\",\"port\":265},{\"cidr\":\"10.0.1.108/31\",\"protocol\":\"any\",\"port\":199},{\"cidr\":\"10.0.0.235/32\",\"protocol\":\"any\",\"port\":363,\"endPort\":364},{\"cidr\":\"2001:db8::1a0/128\",\"protocol\":\"tcp\",\"port\":275},{\"cidr\":\"10.0.3.100/30\",\"protocol\":\"udp\",\"port\":275},{\"cidr\":\"2001:db8::be/128\",\"protocol\":\"udp\",\"port\":205},{\"cidr\":\"10.0.1.24/30\",\"protocol\":\"any\",\"port\":345,\"endPort\":349},{\"cidr\":\"10.0.1.158/32\",\"protocol\":\"udp\",\"port\":243,\"endPort\":247},{\"cidr\":\"10.0.1.213/32\",\"protocol\":\"udp\",\"port\":289},{\"cidr\":\"2001:db8::2b7/128\",\"protocol\":\"any\",\"port\":11,\"endPort\":15},{\"cidr\":\"2001:db8::18/128\",\"protocol\":\"udp\",\"port\":301,\"endPort\":304},{\"cidr\":\"10.0.2.148/30\",\"protocol\":\"udp\",\"port\":251},{\"cidr\":\"10.0.0.152/30\",\"protocol\":\"tcp\",\"port\":113},{\"cidr\":\"2001:db8::b8/128\",\"protocol\":\"tcp\",\"port\":59},{\"cidr\":\"2001:db8::293/128\",\"protocol\":\"any\",\"port\":215},{\"cidr\":\"10.0.0.196/30\",\"protocol\":\"udp\",\"port\":273},{\"cidr\":\"10.0.3.210/31\",\"protocol\":\"udp\",\"port\":235},{\"cidr\":\"2001:db8::2e3/128\",\"protocol\":\"tcp\",\"port\":327},{\"cidr\":\"10.0.2.192/31\",\"protocol\":\"any\",\"port\":209},{\"cidr\":\"10.0.2.160/30\",\"protocol\":\"tcp\",\"port\":315},{\"cidr\":\"10.0.3.46/32\",\"protocol\":\"tcp\",\"port\":367},{\"cidr\":\"10.0.0.40/30\",\"protocol\":\"any\",\"port\":121},{\"cidr\":\"10.0.3.73/32\",\"protocol\":\"udp\",\"port\":365},{\"cidr\":\"2001:db8::172/128\",\"protocol\":\"any\",\"port\":307},{\"cidr\":\"2001:db8::379/128\",\"protocol\":\"tcp\",\"port\":301},{\"cidr\":\"2001:db8::be/128\",\"protocol\":\"any\",\"port\":73},{\"cidr\":\"10.0.0.6/31\",\"protocol\":\"udp\",\"port\":29},{\"cidr\":\"10.0.2.136/31\",\"protocol\":\"any\",\"port\":39},{\"cidr\":\"2001:db8::10f/128\",\"protocol\":\"any\",\"port\":327},{\"cidr\":\"10.0.3.144/30\",\"protocol\":\"any\",\"port\":209},{\"cidr\":\"10.0.1.180/30\",\"protocol\":\"tcp\",\"port\":273},{\"cidr\":\"10.0.3.166/31\",\"protocol\":\"any\",\"port\":103},{\"cidr\":\"10.0.0.240/31\",\"protocol\":\"any\",\"port\":193},{\"cidr\":\"2001:db8::3b0/128\",\"protocol\":\"any\",\"port\":183},{\"cidr\":\"10.0.2.128/31\",\"protocol\":\"udp\",\"port\":27,\"endPort\":27},{\"cidr\":\"10.0.2.145/32\",\"protocol\":\"any\",\"port\":221},{\"cidr\":\"2001:db8::2f7/128\",\"protocol\":\"tcp\",\"port\":153},{\"cidr\":\"10.0.0.80/30\",\"protocol\":\"any\",\"port\":291},{\"cidr\":\"2001:db8::11e/128\",\"protocol\":\"tcp\",\"port\":175},{\"cidr\":\"2001:db8::29a/128\",\"protocol\":\"tcp\",\"port\":391,\"endPort\":393},{\"cidr\":\"2001:db8::31a/128\",\"protocol\":\"tcp\",\"port\":233},{\"cidr\":\"10.0.1.144/32\",\"protocol\":\"udp\",\"port\":93},{\"cidr\":\"10.0.0.180/30\",\"protocol\":\"udp\",\"port\":263,\"endPort\":267},{\"cidr\":\"10.0.2.64/30\",\"protocol\":\"udp\",\"port\":319,\"endPort\":320},{\"cidr\":\"10.0.0.116/30\",\"protocol\":\"any\",\"port\":337},{\"cidr\":\"10.0.2.36/30\",\"protocol\":\"any\",\"port\":109},{\"cidr\":\"10.0.2.224/32\",\"protocol\":\"udp\",\"port\":61,\"endPort\":64},{\"cidr\":\"2001:db8::150/128\",\"protocol\":\"udp\",\"port\":393},{\"cidr\":\"10.0.2.56/30\",\"protocol\":\"udp\",\"port\":169},{\"cidr\":\"2001:db8::27a/128\",\"protocol\":\"tcp\",\"port\":389},{\"cidr\":\"10.0.2.84/30\",\"protocol\":\"any\",\"port\":307},{\"cidr\":\"2001:db8::3f8/128\",\"protocol\":\"tcp\",\"port\":345},{\"cidr\":\"2001:db8::e9/128\",\"protocol\":\"any\",\"port\":229},{\"cidr\":\"10.0.2.240/30\",\"protocol\":\"tcp\",\"port\":243},{\"cidr\":\"10.0.2.164/31\",\"protocol\":\"udp\",\"port\":237},{\"cidr\":\"2001:db8::10c/128\",\"protocol\":\"any\",\"port\":89},{\"cidr\":\"2001:db8::bc/128\",\"protocol\":\"tcp\",\"port\":313},{\"cidr\":\"10.0.3.12/30\",\"protocol\":\"any\",\"port\":51,\"endPort\":52},{\"cidr\":\"10.0.0.241/32\",\"protocol\":\"any\",\"port\":177},{\"cidr\":\"10.0.1.168/32\",\"protocol\":\"any\",\"port\":191,\"endPort\":195},{\"cidr\":\"10.0.2.184/30\",\"protocol\":\"any\",\"port\":361,\"endPort\":364},{\"cidr\":\"10.0.0.113/32\",\"protocol\":\"any\",\"port\":257,\"endPort\":258},{\"cidr\":\"10.0.3.32/30\",\"protocol\":\"tcp\",\"port\":287},{\"cidr\":\"2001:db8::2d1/128\",\"protocol\":\"any\",\"port\":231},{\"cidr\":\"10.0.0.226/32\",\"protocol\":\"udp\",\"port\":201},{\"cidr\":\"10.0.1.220/30\",\"protocol\":\"udp\",\"port\":111,\"endPort\":115},{\"cidr\":\"10.0.2.41/32\",\"protocol\":\"any\",\"port\":77},{\"cidr\":\"10.0.1.88/30\",\"protocol\":\"udp\",\"port\":165},{\"cidr\":\"10.0.0.128/30\",\"protocol\":\"any\",\"port\":393},{\"cidr\":\"10.0.2.168/30\",\"protocol\":\"udp\",\"port\":13},{\"cidr\":\"2001:db8::2df/128\",\"protocol\":\"udp\",\"port\":137,\"endPort\":137},{\"cidr\":\"2001:db8::1c6/128\",\"protocol\":\"udp\",\"port\":261},{\"cidr\":\"2001:db8::1c7/128\",\"protocol\":\"tcp\",\"port\":53},{\"cidr\":\"10.0.0.250/31\",\"protocol\":\"any\",\"port\":307,\"endPort\":307},{\"cidr\":\"2001:db8::338/128\",\"protocol\":\"udp\",\"port\":185},{\"cidr\":\"10.0.0.6/32\",\"protocol\":\"tcp\",\"port\":89},{\"cidr\":\"10.0.1.76/30\",\"protocol\":\"any\",\"port\":311},{\"cidr\":\"10.0.1.236/30\",\"protocol\":\"udp\",\"port\":231},{\"cidr\":\"10.0.0.124/30\",\"protocol\":\"tcp\",\"port\":227,\"endPort\":230},{\"cidr\":\"2001:db8::2c9/128\",\"protocol\":\"any\",\"port\":353},{\"cidr\":\"10.0.1.212/30\",\"protocol\":\"any\",\"port\":379,\"endPort\":383},{\"cidr\":\"2001:db8::36c/128\",\"protocol\":\"any\",\"port\":313},{\"cidr\":\"10.0.0.240/31\",\"protocol\":\"tcp\",\"port\":43},{\"cidr\":\"2001:db8::e3/128\",\"protocol\":\"any\",\"port\":53},{\"cidr\":\"2001:db8::34c/128\",\"protocol\":\"any\",\"port\":197,\"endPort\":197},{\"cidr\":\"10.0.3.200/30\",\"protocol\":\"tcp\",\"port\":315},{\"cidr\":\"2001:db8::f9/128\",\"protocol\":\"udp\",\"port\":273},{\"cidr\":\"10.0.1.137/32\",\"protocol\":\"udp\",\"port\":125,\"endPort\":129},{\"cidr\":\"2001:db8::172/128\",\"protocol\":\"any\",\"port\":257},{\"cidr\":\"2001:db8::f2/128\",\"protocol\":\"any\",\"port\":59},{\"cidr\":\"10.0.0.152/31\",\"protocol\":\"any\",\"port\":191},{\"cidr\":\"2001:db8::65/128\",\"protocol\":\"any\",\"port\":105},{\"cidr\":\"10.0.3.210/32\",\"protocol\":\"udp\",\"port\":309},{\"cidr\":\"10.0.2.204/30\",\"protocol\":\"any\",\"port\":225,\"endPort\":227},{\"cidr\":\"2001:db8::308/128\",\"protocol\":\"udp\",\"port\":203},{\"cidr\":\"2001:db8::/128\",\"protocol\":\"any\",\"port\":115},{\"cidr\":\"10.0.3.54/32\",\"protocol\":\"any\",\"port\":37,\"endPort\":40},{\"cidr\":\"10.0.3.147/32\",\"protocol\":\"udp\",\"port\":383},{\"cidr\":\"10.0.3.201/32\",\"protocol\":\"udp\",\"port\":341},{\"cidr\":\"10.0.0.123/32\",\"protocol\":\"any\",\"port\":49},{\"cidr\":\"10.0.3.180/30\",\"protocol\":\"udp\",\"port\":313},{\"cidr\":\"2001:db8::393/128\",\"protocol\":\"any\",\"port\":61},{\"cidr\":\"2001:db8::2ac/128\",\"protocol\":\"udp\",\"port\":1},{\"cidr\":\"10.0.2.62/31\",\"protocol\":\"any\",\"port\":245},{\"cidr\":\"10.0.1.212/30\",\"protocol\":\"tcp\",\"port\":225},{\"cidr\":\"10.0.3.102/32\",\"protocol\":\"udp\",\"port\":193},{\"cidr\":\"10.0.0.132/31\",\"protocol\":\"any\",\"port\":253},{\"cidr\":\"10.0.2.156/30\",\"protocol\":\"any\",\"port\":291,\"endPort\":291},{\"cidr\":\"10.0.2.95/32\",\"protocol\":\"udp\",\"port\":295},{\"cidr\":\"10.0.3.32/32\",\"protocol\":\"tcp\",\"port\":381},{\"cidr\":\"10.0.3.156/30\",\"protocol\":\"tcp\",\"port\":29},{\"cidr\":\"10.0.3.16/30\",\"protocol\":\"udp\",\"port\":67},{\"cidr\":\"10.0.2.120/31\",\"protocol\":\"tcp\",\"port\":387},{\"cidr\":\"10.0.1.24/31\",\"protocol\":\"any\",\"port\":83},{\"cidr\":\"10.0.2.184/31\",\"protocol\":\"any\",\"port\":185},{\"cidr\":\"10.0.3.189/32\",\"protocol\":\"any\",\"port\":51,\"endPort\":53},{\"cidr\":\"10.0.3.84/30\",\"protocol\":\"udp\",\"port\":267},{\"cidr\":\"10.0.1.223/32\",\"protocol\":\"tcp\",\"port\":131},{\"cidr\":\"10.0.1.43/32\",\"protocol\":\"any\",\"port\":251,\"endPort\":253},{\"cidr\":\"2001:db8::218/128\",\"protocol\":\"tcp\",\"port\":193,\"endPort\":195},{\"cidr\":\"10.0.1.100/31\",\"protocol\":\"any\",\"port\":211},{\"cidr\":\"10.0.3.140/30\",\"protocol\":\"any\",\"port\":195},{\"cidr\":\"10.0.0.152/30\",\"protocol\":\"any\",\"port\":203,\"endPort\":207},{\"cidr\":\"10.0.1.72/30\",\"protocol\":\"tcp\",\"port\":101},{\"cidr\":\"10.0.1.4/30\",\"protocol\":\"tcp\",\"port\":179},{\"cidr\":\"10.0.3.88/31\",\"protocol\":\"udp\",\"port\":17,\"endPort\":17},{\"cidr\":\"2001:db8::143/128\",\"protocol\":\"udp\",\"port\":115},{\"cidr\":\"10.0.2.238/31\",\"protocol\":\"any\",\"port\":147,\"endPort\":149},{\"cidr\":\"2001:db8::99/128\",\"protocol\":\"tcp\",\"port\":303},{\"cidr\":\"10.0.1.84/32\",\"protocol\":\"any\",\"port\":227},{\"cidr\":\"10.0.3.44/30\",\"protocol\":\"tcp\",\"port\":325},{\"cidr\":\"2001:db8::2b8/128\",\"protocol\":\"tcp\",\"port\":197},{\"cidr\":\"2001:db8::1d2/128\",\"protocol\":\"udp\",\"port\":29},{\"cidr\":\"10.0.1.192/30\",\"protocol\":\"tcp\",\"port\":261},{\"cidr\":\"10.0.2.140/31\",\"protocol\":\"any\",\"port\":287},{\"cidr\":\"2001:db8::2e8/128\",\"protocol\":\"udp\",\"port\":235,\"endPort\":237},{\"cidr\":\"10.0.1.120/31\",\"protocol\":\"any\",\"port\":119},{\"cidr\":\"10.0.3.59/32\",\"protocol\":\"udp\",\"port\":333},{\"cidr\":\"10.0.1.164/30\",\"protocol\":\"any\",\"port\":25,\"endPort\":26},{\"cidr\":\"10.0.1.60/31\",\"protocol\":\"tcp\",\"port\":353,\"endPort\":353},{\"cidr\":\"2001:db8::35a/128\",\"protocol\":\"any\",\"port\":163},{\"cidr\":\"10.0.1.96/31\",\"protocol\":\"tcp\",\"port\":77},{\"cidr\":\"2001:db8::29/128\",\"protocol\":\"tcp\",\"port\":159,\"endPort\":162},{\"cidr\":\"10.0.3.60/30\",\"protocol\":\"udp\",\"port\":399},{\"cidr\":\"10.0.2.48/30\",\"protocol\":\"any\",\"port\":65},{\"cidr\":\"10.0.0.198/31\",\"protocol\":\"tcp\",\"port\":129},{\"cidr\":\"10.0.1.220/30\",\"protocol\":\"any\",\"port\":139},{\"cidr\":\"10.0.0.8/30\",\"protocol\":\"udp\",\"port\":185,\"endPort\":185},{\"cidr\":\"10.0.1.18/31\",\"protocol\":\"udp\",\"port\":349},{\"cidr\":\"10.0.2.92/30\",\"protocol\":\"udp\",\"port\":363},{\"cidr\":\"10.0.0.60/30\",\"protocol\":\"udp\",\"port\":319,\"endPort\":322},{\"cidr\":\"2001:db8::287/128\",\"protocol\":\"tcp\",\"port\":241,\"endPort\":242},{\"cidr\":\"2001:db8::cc/128\",\"protocol\":\"tcp\",\"port\":211,\"endPort\":213},{\"cidr\":\"10.0.2.228/30\",\"protocol\":\"udp\",\"port\":113},{\"cidr\":\"2001:db8::371/128\",\"protocol\":\"udp\",\"port\":377},{\"cidr\":\"10.0.3.228/31\",\"protocol\":\"any\",\"port\":347},{\"cidr\":\"10.0.0.217/32\",\"protocol\":\"tcp\",\"port\":267},{\"cidr\":\"10.0.2.184/32\",\"protocol\":\"any\",\"port\":29},{\"cidr\":\"2001:db8::190/128\",\"protocol\":\"tcp\",\"port\":257,\"endPort\":258},{\"cidr\":\"2001:db8::210/128\",\"protocol\":\"udp\",\"port\":13},{\"cidr\":\"2001:db8::fe/128\",\"protocol\":\"any\",\"port\":61},{\"cidr\":\"10.0.0.198/31\",\"protocol\":\"tcp\",\"port\":257},{\"cidr\":\"10.0.3.108/30\",\"protocol\":\"udp\",\"port\":45,\"endPort\":47},{\"cidr\":\"10.0.1.167/32\",\"protocol\":\"udp\",\"port\":297},{\"cidr\":\"10.0.3.94/32\",\"protocol\":\"tcp\",\"port\":307},{\"cidr\":\"10.0.3.58/31\",\"protocol\":\"tcp\",\"port\":23},{\"cidr\":\"10.0.0.200/32\",\"protocol\":\"any\",\"port\":103,\"endPort\":105},{\"cidr\":\"10.0.3.13/32\",\"protocol\":\"tcp\",\"port\":37,\"endPort\":40},{\"cidr\":\"10.0.0.62/31\",\"protocol\":\"udp\",\"port\":267},{\"cidr\":\"10.0.1.194/32\",\"protocol\":\"any\",\"port\":269},{\"cidr\":\"10.0.1.156/31\",\"protocol\":\"tcp\",\"port\":3},{\"cidr\":\"10.0.1.24/30\",\"protocol\":\"udp\",\"port\":47,\"endPort\":48},{\"cidr\":\"10.0.1.36/32\",\"protocol\":\"any\",\"port\":253},{\"cidr\":\"2001:db8::323/128\",\"protocol\":\"any\",\"port\":31,\"endPort\":34},{\"cidr\":\"10.0.2.169/32\",\"protocol\":\"udp\",\"port\":183},{\"cidr\":\"10.0.1.218/31\",\"protocol\":\"udp\",\"port\":47},{\"cidr\":\"10.0.1.120/31\",\"protocol\":\"any\",\"port\":53},{\"cidr\":\"2001:db8::160/128\",\"protocol\":\"any\",\"port\":147},{\"cidr\":\"10.0.0.183/32\",\"protocol\":\"tcp\",\"port\":75},{\"cidr\":\"10.0.2.60/30\",\"protocol\":\"udp\",\"port\":115},{\"cidr\":\"10.0.1.154/31\",\"protocol\":\"tcp\",\"port\":285,\"endPort\":288},{\"cidr\":\"10.0.1.138/31\",\"protocol\":\"tcp\",\"port\":295},{\"cidr\":\"2001:db8::2ac/128\",\"protocol\":\"udp\",\"port\":143},{\"cidr\":\"10.0.3.212/30\",\"protocol\":\"any\",\"port\":125},{\"cidr\":\"2001:db8::6e/128\",\"protocol\":\"any\",\"port\":247,\"endPort\":248},{\"cidr\":\"2001:db8::4/128\",\"protocol\":\"udp\",\"port\":111},{\"cidr\":\"2001:db8::114/128\",\"protocol\":\"tcp\",\"port\":97},{\"cidr\":\"10.0.3.192/31\",\"protocol\":\"any\",\"port\":315},{\"cidr\":\"10.0.1.37/32\",\"protocol\":\"tcp\",\"port\":115},{\"cidr\":\"10.0.0.160/31\",\"protocol\":\"any\",\"port\":67},{\"cidr\":\"10.0.2.136/32\",\"protocol\":\"any\",\"port\":385},{\"cidr\":\"2001:db8::12f/128\",\"protocol\":\"any\",\"port\":239,\"endPort\":242},{\"cidr\":\"2001:db8::1f3/128\",\"protocol\":\"tcp\",\"port\":179},{\"cidr\":\"10.0.1.20/30\",\"protocol\":\"tcp\",\"port\":35},{\"cidr\":\"2001:db8::173/128\",\"protocol\":\"udp\",\"port\":133,\"endPort\":137},{\"cidr\":\"10.0.0.224/32\",\"protocol\":\"udp\",\"port\":61,\"endPort\":63},{\"cidr\":\"10.0.2.20/31\",\"protocol\":\"any\",\"port\":191},{\"cidr\":\"10.0.3.240/31\",\"protocol\":\"udp\",\"port\":157,\"endPort\":160},{\"cidr\":\"10.0.3.10/32\",\"protocol\":\"any\",\"port\":167},{\"cidr\":\"10.0.1.70/31\",\"protocol\":\"any\",\"port\":39,\"endPort\":39},{\"cidr\":\"2001:db8::35e/128\",\"protocol\":\"udp\",\"port\":265},{\"cidr\":\"2001:db8::36d/128\",\"protocol\":\"any\",\"port\":111,\"endPort\":115},{\"cidr\":\"2001:db8::168/128\",\"protocol\":\"tcp\",\"port\":221},{\"cidr\":\"10.0.2.190/31\",\"protocol\":\"tcp\",\"port\":157,\"endPort\":160},{\"cidr\":\"2001:db8::283/128\",\"protocol\":\"any\",\"port\":251,\"endPort\":252}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":[]}}, + {"name":"random #67","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::25c/126\", \"protocol\": \"udp\", \"port\": 1, \"endPort\": 5}, {\"cidr\": \"::/1\", \"protocol\": \"icmp\"}, {\"to\": [{\"cidr\": \"128.0.0.0/2\"}]}, {\"to\": [{\"cidr\": \"209.166.168.72/31\"}, {\"cidr\": \"2001:db8::340/123\", \"except\": [\"2001:db8::35f/000124\"]}, {\"cidr\": \"10.0.2.132\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"udp\"}, {\"protocol\": \"any\"}]}, {\"ports\": [{\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"211.104.110.205/8\"}, {\"cidr\": \"2001:db8::229/119\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0.0.2.28/126\", \"2001:db8::37a/124\"]}, {\"cidr\": \"ee62:a27c:f997:8459:8dc5:4be1:e430:9500/120\"}, {\"cidr\": \"b159:2591::/00032\", \"except\": [\"B159:2591:8000::/33\", \"b159:2591:9000::/36\", \"b159:2591:6000:0000:0000:0000:0.0.0.0/0036\"]}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\", \"port\": 7}]}, {\"to\": [{\"cidr\": \"10.0.0.0/0022\"}, {\"cidr\": \"10.0.3.56/30\", \"except\": [\"10.0.3.56/30\", \"10.0.3.58/32\"]}, {\"cidr\": \"2001:db8::80/122\", \"except\": [\"2001:db8::9f/124\", \"2001:db8::94/125\", \"2001:db8::98\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 440, \"endPort\": 446}]}, {\"cidr\": \"10.0.3.43\", \"except\": [], \"protocol\": \"any\", \"port\": 9, \"endPort\": 11}], \"deny\": [{\"ports\": [{\"protocol\": \"tcp\"}]}, {\"cidr\": \"10.0.1.0/27\", \"protocol\": \"udp\"}, {\"to\": [{\"cidr\": \"cbf9:5332:74d2:5fe9:0fa9:043b:3b00:0000/104\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 442, \"endPort\": 443}]}]}"}],"expected":{"allow":["0.0.0.0/0:icmp","10.0.0.0/22:udp:440-446","10.0.2.132/32","10.0.3.43/32:tcp:9-11","10.0.3.43/32:udp:9-11","128.0.0.0/2","209.166.168.72/31","211.0.0.0/8:tcp:7","::/0:icmp","2001:db8::80/124:udp:440-446","2001:db8::a0/123:udp:440-446","2001:db8::200/124:tcp:7","2001:db8::210/125:tcp:7","2001:db8::218/126:tcp:7","2001:db8::220/123:tcp:7","2001:db8::240/122:tcp:7","2001:db8::25c/126:udp:1-5","2001:db8::280/121:tcp:7","2001:db8::300/122:tcp:7","2001:db8::340/123:tcp:7","2001:db8::340/124","2001:db8::360/124:tcp:7","2001:db8::380/121:tcp:7","b159:2591::/34:tcp:7","b159:2591:4000::/35:tcp:7","b159:2591:7000::/36:tcp:7","ee62:a27c:f997:8459:8dc5:4be1:e430:9500/120:tcp:7"],"deny":["0.0.0.0/0:tcp","10.0.1.0/27:udp","::/0:tcp","cbf9:5332:74d2:5fe9:fa9:43b:3b00:0/104:udp:442-443"]}}, + {"name":"random #68","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"::/0\"}, {\"cidr\": \"f1e1:0610:d725:0df6:0000:0000:0000:0000/0064\", \"except\": [\"f1e1:0610:d725:0df6:a000:0000:0000:0000/67\", \"f1e1:610:d725:df6:ce00::/71\", \"F1E1:610:D725:DF6:8000::/65\"]}, {\"cidr\": \"2001:db8::3e0/124\", \"except\": []}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65533}, {\"protocol\": \"udp\", \"port\": 442}]}, {\"cidr\": \"2001:db8::1a3/000118\", \"protocol\": \"any\", \"port\": 65533}, {\"to\": [{\"cidr\": \"2001:db8::20/123\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 78, \"endPort\": 78}, {\"protocol\": \"udp\", \"port\": 441}, {\"protocol\": \"udp\"}]}, {\"to\": [{\"cidr\": \"234.41.212.0/255.255.255.0\"}, {\"cidr\": \"2001:db8::202/127\"}, {\"cidr\": \"10.0.2.128/25\", \"except\": [\"10.0.2.198/27\", \"10.0.2.216/030\"]}, {\"cidr\": \"10.0.3.224/31\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 445, \"endPort\": 448}, {\"protocol\": \"udp\", \"port\": 79, \"endPort\": 17672}, {\"protocol\": \"udp\"}]}, {\"to\": [{\"cidr\": \"2001:db8::3d8/125\"}, {\"cidr\": \"10.0.1.120/29\"}, {\"cidr\": \"10.0.0.0/28\"}, {\"cidr\": \"10.0.3.97/0.0.0.15\"}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\"}, {\"protocol\": \"any\", \"port\": 80}]}, {\"to\": [{\"cidr\": \"50.61.64.71/1\", \"except\": [\"19.0.0.0/8\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.3.63/126\"}]}, {\"to\": [{\"cidr\": \"bd70:e3d3:c594:70c8:49a9:ad94:b572:d05e/64\"}, {\"cidr\": \"10.0.2.32/27\"}, {\"cidr\": \"2001:db8::290\"}]}, {\"to\": [{\"cidr\": \"10.0.3.32/29\"}, {\"cidr\": \"10.0.2.192/26\"}, {\"cidr\": \"10.0.2.224/27\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"port\": 78, \"endPort\": 78}, {\"protocol\": \"udp\", \"port\": 81}]}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.1.144/255.255.255.240\"}, {\"cidr\": \"0000:0000:0000:0000:0000:ffff:168.0.0.0/104\"}, {\"cidr\": \"3f98:69a5:5327:b051:4c9a:3255:d7cd:a68d/128\", \"except\": [\"3f98:69a5:5327:b051:4c9a:3255:d7cd:a68d/00128\", \"3f98:69a5:5327:b051:4c9a:3255:d7cd:a68d\"]}, {\"cidr\": \"2001:db8::240/128\"}], \"ports\": [{\"protocol\": \"tcp\"}]}]}"}],"expected":{"allow":["0.0.0.0/4","16.0.0.0/7","18.0.0.0/8","20.0.0.0/6","24.0.0.0/5","32.0.0.0/3","64.0.0.0/2","234.41.212.0/24:tcp:445-448","234.41.212.0/24:udp","::/0:tcp:65533","::/0:udp:442","2001:db8::/118:udp:65533","2001:db8::20/123:tcp:78","2001:db8::20/123:udp","2001:db8::202/127:tcp:445-448","2001:db8::202/127:udp","2001:db8::290/128","2001:db8::33c/126","2001:db8::3d8/125:tcp","2001:db8::3d8/125:udp:80","bd70:e3d3:c594:70c8::/64"],"deny":["10.0.1.144/28:tcp","::ffff:168.0.0.0/104:tcp","2001:db8::240/128:tcp"]}}, + {"name":"random #69","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::2ab/128\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:DB8::2AB/128\",\n\t\t\t\t\t\t\"2001:db8::2ab\",\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:02ab\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::/0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.112/028\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.128/25\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.3.128/25\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":1,\n\t\t\t\t\t\"endPort\":1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":82\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:DB8::DE\",\n\t\t\t\"except\":[\n\t\t\t\t\"2001:DB8::DE\"\n\t\t\t],\n\t\t\t\"protocol\":\"any\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"119.152.73.116/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":81\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:DB8::320/123\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.112/29\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"7.18.28.0/0.0.0.255\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"7.18.28.45/255.255.255.224\",\n\t\t\t\t\t\t\"7.18.28.149/26\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.1.0/25\",\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:0000/119\",\n\t\t\t\t\t\t\"10.0.3.241/255.255.255.224\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"port\":65533\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"855:41a0:2051:1630:c287:8214:5350:3cea/104\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"0855:41a0:2051:1630:c287:8214:53d6:bd21/108\",\n\t\t\t\t\t\t\"855:41a0:2051:1630:c287:8214:5319:853c/107\",\n\t\t\t\t\t\t\"855:41a0:2051:1630:c287:8214:53d4:1fa2/110\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.112/255.255.255.240\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::200/120\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":79,\n\t\t\t\t\t\"endPort\":80\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.224/27\",\n\t\t\t\"except\":[\n\t\t\t\t\"10.0.0.240/27\"\n\t\t\t],\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":10\n\t\t}\n\t],\n\t\"deny\":[\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.255\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.131\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::/118\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.1.102/030\",\n\t\t\t\t\t\t\"10.0.3.0/24\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.221/0.0.0.63\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.3.238/31\",\n\t\t\t\t\t\t\"10.0.3.220/30\",\n\t\t\t\t\t\t\"10.0.3.192/255.255.255.192\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"allow[3].to[3].except[1] must be contained in 10.0.0.0/22"}}, + {"name":"random #70","input":[{"text":"{\"allow\":[{\"cidr\":\"10.0.2.43/26\",\"except\":[\"10.0.2.48/29\"],\"protocol\":\"any\"},{\"to\":[{\"cidr\":\"10.0.3.0/25\",\"except\":[\"::ffff:216.30.143.225\",\"10.0.3.12/30\"]},{\"cidr\":\"10.0.2.224/00027\",\"except\":[\"10.0.2.224/27\",\"10.0.2.231/0029\",\"10.0.2.224/28\"]},{\"cidr\":\"10.0.0.0/22\"},{\"cidr\":\"10.0.0.0/24\"}]},{\"ports\":[{\"protocol\":\"udp\",\"port\":79},{\"protocol\":\"icmp\"},{\"protocol\":\"any\",\"port\":441,\"endPort\":443}]},{\"cidr\":\"10.0.2.118/25\",\"except\":[],\"protocol\":\"tcp\",\"port\":445},{\"cidr\":\"10.0.0.200/28\",\"except\":[\"10.0.0.192/29\"],\"protocol\":\"udp\"},{\"cidr\":\"143.93.111.62\",\"protocol\":\"any\",\"port\":445}],\"deny\":[{\"cidr\":\"0.0.0.0/01\",\"except\":[\"44.0.0.0/6\",\"9.51.130.105/8\"]},{\"to\":[{\"cidr\":\"10.0.3.63/30\",\"except\":[\"10.0.3.62/31\",\"10.0.3.63/32\",\"10.0.3.60/30\"]}],\"ports\":[{\"protocol\":\"tcp\",\"port\":82,\"endPort\":82},{\"protocol\":\"tcp\",\"port\":79}]},{\"cidr\":\"10.0.2.144/29\",\"protocol\":\"TCP\",\"port\":7,\"endPort\":9},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.1.208/127\"},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:01e9/120\"}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"any\",\"port\":444,\"endPort\":449}]},{\"to\":[{\"cidr\":\"2001:db8::118/125\"},{\"cidr\":\"0.0.0.0/0\",\"except\":[\"12.0.0.0/6\"]},{\"cidr\":\"10.0.0.0/255.255.254.0\"},{\"cidr\":\"10.0.3.192/27\"}]},{\"cidr\":\"10.0.2.17/29\",\"protocol\":\"any\"},{\"cidr\":\"10.0.1.0/24\",\"protocol\":\"icmp\"},{\"cidr\":\"10.0.0.207/26\",\"protocol\":\"any\"}]}"}],"expected":{"error":"allow[1].to[0].except[0] must be contained in 10.0.3.0/25"}}, + {"name":"random #71","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.3.64/27\"}, {\"cidr\": \"10.0.1.12/30\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 65534}]}, {\"ports\": [{\"protocol\": \"udp\", \"port\": 65534}]}, {\"to\": [{\"cidr\": \"10.0.0.0/255.255.255.192\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 2, \"endPort\": 4}, {\"protocol\": \"udp\", \"port\": 444, \"endPort\": 448}, {\"protocol\": \"any\", \"port\": 444}]}], \"deny\": []}"}],"expected":{"allow":["0.0.0.0/0:udp:65534","10.0.0.0/26:tcp:2-4","10.0.0.0/26:tcp:444","10.0.0.0/26:udp:2-4","10.0.0.0/26:udp:444-448","10.0.1.12/30:tcp:65534","10.0.3.64/27:tcp:65534","::/0:udp:65534"],"deny":[]}}, + {"name":"random #72","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"10.0.0.214/29\"},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0192/124\"},{\"cidr\":\"10.0.2.64/27\",\"except\":[\"10.0.2.82/30\",\"10.0.2.65/30\"]},{\"cidr\":\"2001:db8::100/120\"}],\"ports\":[{\"protocol\":\"tcp\"}]},{\"to\":[{\"cidr\":\"10.0.3.212/31\"},{\"cidr\":\"187.7.219.204/31\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":8,\"endPort\":11},{\"protocol\":\"any\",\"port\":2,\"endPort\":54369}]},{\"cidr\":\"10.0.2.0/25\",\"protocol\":\"icmp\"},{\"cidr\":\"2001:db8::100/120\",\"protocol\":\"any\"},{\"cidr\":\"10.0.2.88/23\",\"protocol\":\"tcp\"},{\"cidr\":\"10.0.1.163/25\",\"protocol\":\"tcp\",\"port\":9},{\"to\":[{\"cidr\":\"197.76.177.206/16\",\"except\":[\"197.76.248.0/0023\"]}],\"ports\":[{\"protocol\":\"icmp\"}]},{\"to\":[{\"cidr\":\"10.0.1.0/255.255.255.224\",\"except\":[]},{\"cidr\":\"10.0.3.64/27\"},{\"cidr\":\"213.232.227.87/128.0.0.0\"}],\"ports\":[{\"protocol\":\"udp\"},{\"protocol\":\"udp\",\"port\":79,\"endPort\":83}]},{\"to\":[{\"cidr\":\"10.0.1.0/255.255.255.0\",\"except\":[\"10.0.1.192/27\",\"10.0.1.144/31\"]}],\"ports\":[{\"protocol\":\"any\",\"port\":7,\"endPort\":11}]},{\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"any\",\"port\":65535},{\"protocol\":\"udp\",\"port\":82}]},{\"cidr\":\"2001:db8::/118\"},{\"to\":[{\"cidr\":\"2001:db8::3a/119\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":7,\"endPort\":12},{\"protocol\":\"tcp\",\"port\":65532,\"endPort\":65534},{\"protocol\":\"any\"},{\"protocol\":\"any\",\"port\":12,\"endPort\":13}]},{\"to\":[{\"cidr\":\"2001:db8::240/122\",\"except\":[]},{\"cidr\":\"0.20.38.21/1\"},{\"cidr\":\"10.0.1.112/30\"},{\"cidr\":\"2001:db8::115/127\"}]},{\"to\":[{\"cidr\":\"10.0.1.110/0028\"},{\"cidr\":\"2001:DB8::2BA/119\"}]},{\"to\":[{\"cidr\":\"4000::/3\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":65533,\"endPort\":65535}]},{\"cidr\":\"10.0.1.0/0.0.0.255\",\"protocol\":\"tcp\"},{\"to\":[{\"cidr\":\"f19:599::/32\"},{\"cidr\":\"10.0.2.0/0.0.1.255\"},{\"cidr\":\"10.0.0.0/23\"},{\"cidr\":\"e876:5afc:bf77:3056:e371:159f:26.243.212.238/127\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":445,\"endPort\":445},{\"port\":65530,\"endPort\":65530},{\"protocol\":\"tcp\",\"port\":9,\"endPort\":13}]},{\"cidr\":\"2001:db8::300/122\",\"except\":[\"2001:db8::331/123\",\"2001:db8::300/123\"],\"protocol\":\"tcp\",\"port\":4},{\"to\":[{\"cidr\":\"2b09:3066:fb6b:9706:d84:bbf0:9de1:eebf/000127\"},{\"cidr\":\"10.0.0.0/24\"},{\"cidr\":\"10.0.0.88/29\",\"except\":[\"10.0.0.94\",\"10.0.0.88/00029\",\"10.0.0.93/31\"]},{\"cidr\":\"10.0.3.192/26\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":441}]},{\"to\":[{\"cidr\":\"10.0.2.0/0024\"},{\"cidr\":\"10.0.2.179/0.0.3.255\"},{\"cidr\":\"10.0.0.81/22\"},{\"cidr\":\"10.0.0.0/0024\"}],\"ports\":[{\"port\":65531,\"endPort\":65535},{\"protocol\":\"udp\",\"port\":78,\"endPort\":79},{\"port\":10,\"endPort\":15},{\"protocol\":\"any\",\"port\":79}]},{\"cidr\":\"2001:db8::2ec/125\"},{\"to\":[{\"cidr\":\"10.0.3.56/23\"},{\"cidr\":\"10.0.0.216/00028\"},{\"cidr\":\"10.0.1.168/00031\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":82},{\"protocol\":\"icmp\"},{\"protocol\":\"udp\"},{\"protocol\":\"tcp\"}]},{\"cidr\":\"efd7:83b3:37c:8fe2:e639:e5cd:a6d:cc21/64\",\"except\":[\"EFD7:83B3:37C:8FE2:C800::/71\",\"efd7:83b3:037c:8fe2:5569:8d6d:675e:c9b5/68\"],\"protocol\":\"any\",\"port\":443,\"endPort\":447},{\"to\":[{\"cidr\":\"c2ce:a716:280d:3c08:bd0a:a4c:8a4d:4067/127\"},{\"cidr\":\"215.80.0.0/255.240.0.0\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":65530,\"endPort\":65532},{\"port\":82},{\"protocol\":\"tcp\"},{\"protocol\":\"udp\",\"port\":5,\"endPort\":11}]},{\"cidr\":\"10.0.2.175/26\"},{\"to\":[{\"cidr\":\"10.0.0.134\"},{\"cidr\":\"10.0.0.96/255.255.255.240\"}],\"ports\":[{\"protocol\":\"icmp\"},{\"protocol\":\"udp\",\"port\":444,\"endPort\":444}]},{\"to\":[{\"cidr\":\"10.0.0.0/23\"},{\"cidr\":\"10.0.2.32/27\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":444,\"endPort\":444}]},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:012a/124\"},{\"cidr\":\"249.201.109.251/00\"},{\"cidr\":\"10.0.0.224/028\"},{\"cidr\":\"137.15.252.210/255.240.0.0\",\"except\":[\"137.8.0.0/13\",\"137.7.0.0/17\",\"137.3.128.0/17\"]}],\"ports\":[{\"protocol\":\"udp\"},{\"port\":9}]},{\"to\":[{\"cidr\":\"10.0.0.0/22\"},{\"cidr\":\"10.0.1.112/28\",\"except\":[\"10.0.1.112/29\",\"10.0.3.160/255.255.255.224\"]}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"udp\"}]},{\"ports\":[{\"protocol\":\"any\",\"port\":442}]},{\"to\":[{\"cidr\":\"74.255.82.232/31\"},{\"cidr\":\"10.0.2.128/00029\",\"except\":[\"10.0.2.133\",\"10.0.2.128/29\",\"10.0.2.128/29\"]},{\"cidr\":\"131.249.0.0/16\",\"except\":[\"131.249.0.0/21\"]},{\"cidr\":\"2001:DB8::180/121\"}]},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.0.128/122\",\"except\":[]},{\"cidr\":\"2001:db8::280/124\"}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"udp\"},{\"protocol\":\"udp\",\"port\":80}]},{\"to\":[{\"cidr\":\"a136:ff78:a29e:f63a:8dd9:ff2f:25dd:8700/120\"},{\"cidr\":\"0000:0000:0000:0000:0000:ffff:56.52.247.51\",\"except\":[\"x\",\"::ffff:56.52.247.51/128\",\"0000:0000:0000:0000:0000:ffff:56.52.247.51/128\"]},{\"cidr\":\"2001:db8::/120\"},{\"cidr\":\"10.0.3.0/26\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":444,\"endPort\":446}]},{\"to\":[{\"cidr\":\"2001:db8::1e0/123\",\"except\":[\"fe80::1%eth0\",\"2001:0db8:0000:0000:0000:0000:0000:01ec/126\",\"2001:db8::1f5/128\"]}],\"ports\":[{\"protocol\":\"any\",\"port\":78},{\"protocol\":\"any\"},{\"protocol\":\"tcp\",\"port\":441},{\"protocol\":\"tcp\",\"port\":65532}]},{\"to\":[{\"cidr\":\"125.84.246.233/16\"},{\"cidr\":\"10.0.0.94/0031\"}]},{\"to\":[{\"cidr\":\"0b95:d855:64ad:52d9:cf4a:28c2:253.252.188.45\",\"except\":[]}],\"ports\":[{\"protocol\":\"tcp\",\"port\":65531,\"endPort\":65534},{\"port\":79,\"endPort\":80},{\"protocol\":\"tcp\",\"port\":441}]},{},{\"to\":[{\"cidr\":\"2001:db8::313/122\",\"except\":[\"2001:db8::324/127\",\"2001:db8::300/00122\",\"2001:db8::306\"]},{\"cidr\":\"8000:0000:0000:0000:0000:0000:0.0.0.0/1\",\"except\":[\"9369:188a:2abb:24c:cf0b:7f73:13c7:ddbb/4\",\"c800:0000:0000:0000:0000:0000:0000:0000/5\"]},{\"cidr\":\"10.0.2.222/24\"},{\"cidr\":\"10.0.3.100/22\"}]},{\"to\":[{\"cidr\":\"108.0.0.0/8\"},{\"cidr\":\"10.0.3.40/255.255.255.252\"},{\"cidr\":\"10.0.2.48/29\"},{\"cidr\":\"10.0.2.255\",\"except\":[\"10.0.2.255/32\",\"10.0.2.255/32\",\"10.0.2.255/255.255.255.255\"]}]},{\"cidr\":\"2001:db8::3b2/127\",\"protocol\":\"tcp\",\"port\":79}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.0.219/25\"},{\"cidr\":\"2001:db8::30a/125\"},{\"cidr\":\"10.0.1.171\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":80},{\"protocol\":\"tcp\",\"port\":81,\"endPort\":85},{\"protocol\":\"icmp\"},{\"protocol\":\"any\",\"port\":5}]},{\"ports\":[{\"protocol\":\"any\",\"port\":65535,\"endPort\":65535},{\"protocol\":\"udp\",\"port\":65534,\"endPort\":65535},{\"protocol\":\"tcp\"}]},{\"ports\":[{\"protocol\":\"any\",\"port\":78},{\"protocol\":\"tcp\"}]},{\"cidr\":\"110.126.130.170/255.255.255.0\",\"protocol\":\"any\",\"port\":79,\"endPort\":83},{\"to\":[{\"cidr\":\"118.188.53.30/12\"}],\"ports\":[{\"port\":65531,\"endPort\":65531},{\"protocol\":\"icmp\"}]},{\"ports\":[{\"protocol\":\"udp\",\"port\":65531},{\"protocol\":\"tcp\"}]},{\"cidr\":\"10.0.3.0/25\",\"except\":[\"10.0.3.0/26\"],\"protocol\":\"udp\"},{\"to\":[{\"cidr\":\"10.0.3.25/31\",\"except\":[\"10.0.3.24/31\",\"10.0.3.35/255.255.254.0\"]},{\"cidr\":\"129.121.13.205/4\"},{\"cidr\":\"10.0.0.78/27\",\"except\":[\"10.0.0.69/255.255.255.240\",\"10.0.0.64/29\"]}],\"ports\":[{\"protocol\":\"udp\",\"port\":440,\"endPort\":444},{\"protocol\":\"udp\",\"port\":445}]}]}"}],"expected":{"error":"allow[28].to[1].except[1] must be contained in 10.0.1.112/28"}}, + {"name":"random #73","input":[{"text":"{\n \"allow\": [], \n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.16/32\"\n }, \n {\n \"cidr\": \"2001:db8::1cc/000124\"\n }, \n {\n \"cidr\": \"10.0.2.128/25\"\n }, \n {\n \"cidr\": \"10.0.2.106\", \n \"except\": [\n \"10.0.2.106/32\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 4\n }, \n {\n \"protocol\": \"any\", \n \"port\": 7, \n \"endPort\": 10\n }, \n {\n \"protocol\": \"any\", \n \"port\": 440, \n \"endPort\": 443\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 9\n }\n ]\n }, \n {\n \"cidr\": \"27.62.139.132/32\", \n \"protocol\": \"icmp\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.19/255.255.255.254\"\n }, \n {\n \"cidr\": \"::/0\", \n \"except\": [\n \"9a92:5186:51a0:5212:245d:a646:8223:f6b7/5\", \n \"::/1\", \n \"1ab0:2980:c9c6:8d27:3085:7725:577f:c50c/7\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\"\n }\n ]\n }\n ]\n}"}],"expected":{"allow":[],"deny":["10.0.0.18/31:udp","10.0.2.128/25:tcp:4","10.0.2.128/25:tcp:7-10","10.0.2.128/25:tcp:440-443","10.0.2.128/25:udp:7-10","10.0.2.128/25:udp:440-443","10.0.3.16/32:tcp:4","10.0.3.16/32:tcp:7-10","10.0.3.16/32:tcp:440-443","10.0.3.16/32:udp:7-10","10.0.3.16/32:udp:440-443","27.62.139.132/32:icmp","2001:db8::1c0/124:tcp:4","2001:db8::1c0/124:tcp:7-10","2001:db8::1c0/124:tcp:440-443","2001:db8::1c0/124:udp:7-10","2001:db8::1c0/124:udp:440-443","8000::/4:udp","9000::/5:udp","a000::/3:udp","c000::/2:udp"]}}, + {"name":"random #74","input":[{"text":"{\"deny\":[{\"cidr\":\"226.200.242.0/24\",\"except\":[],\"protocol\":\"udp\"}]}"}],"expected":{"allow":[],"deny":["226.200.242.0/24:udp"]}}, + {"name":"random #75","input":[{"text":"{\"allow\":[],\"deny\":[{\"cidr\":\"10.0.1.64/26\",\"protocol\":\"udp\",\"port\":3,\"endPort\":3}]}"}],"expected":{"allow":[],"deny":["10.0.1.64/26:udp:3"]}}, + {"name":"random #76","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::144/127\",\n \"except\": [\n \"2001:db8::144/127\",\n \"2001:db8::145\",\n \"2001:db8::144/127\"\n ]\n },\n {\n \"cidr\": \"10.0.2.105/23\"\n },\n {\n \"cidr\": \"10.0.0.0/22\",\n \"except\": [\n \"10.0.2.0/23\",\n \"10.0.2.0/23\"\n ]\n },\n {\n \"cidr\": \"32.0.0.0/4\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"tcp\",\n \"port\": 441\n },\n {\n \"port\": 65535,\n \"endPort\": 65535\n },\n {\n \"protocol\": \"icmp\"\n },\n {\n \"protocol\": \"any\",\n \"port\": 443,\n \"endPort\": 445\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.1.220/31\"\n },\n {\n \"cidr\": \"10.0.3.0/24\",\n \"except\": [\n \"10.0.3.42/0.0.0.0\",\n \"10.0.3.0/027\",\n \"10.0.3.101/25\"\n ]\n },\n {\n \"cidr\": \"77.17.17.40/255.255.255.254\",\n \"except\": [\n \"77.17.17.41\"\n ]\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"tcp\"\n },\n {\n \"protocol\": \"udp\",\n \"port\": 79,\n \"endPort\": 83\n },\n {\n \"protocol\": \"any\",\n \"port\": 65532\n },\n {\n \"protocol\": \"tcp\",\n \"port\": 444,\n \"endPort\": 449\n }\n ]\n },\n {\n \"cidr\": \"80.0.0.0/4\",\n \"except\": [],\n \"protocol\": \"any\",\n \"port\": 82,\n \"endPort\": 83\n },\n {\n \"to\": [\n {\n \"cidr\": \"213.90.229.0/0.0.0.255\"\n },\n {\n \"cidr\": \"10.0.3.96/28\",\n \"except\": [\n \"10.0.3.102/31\",\n \"10.0.3.108/30\",\n \"10.0.3.107/255.255.255.255\"\n ]\n }\n ]\n },\n {\n \"cidr\": \"2001:DB8::/118\",\n \"protocol\": \"tcp\"\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.1.208/28\",\n \"except\": []\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"udp\",\n \"port\": 65531,\n \"endPort\": 65534\n },\n {\n \"protocol\": \"icmp\"\n }\n ]\n }\n ],\n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"::/1\"\n },\n {\n \"cidr\": \"2001:db8::248/000123\"\n },\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/118\",\n \"except\": [\n \"2001:DB8::200/119\",\n \"2001:DB8::/121\"\n ]\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"tcp\",\n \"port\": 65530\n },\n {\n \"protocol\": \"udp\",\n \"port\": 78,\n \"endPort\": 80\n },\n {},\n {\n \"port\": 65530\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.1.192/26\"\n },\n {\n \"cidr\": \"40.161.139.255/192.0.0.0\",\n \"except\": [\n \"0.0.0.0/2\"\n ]\n },\n {\n \"cidr\": \"10.0.0.0/23\",\n \"except\": []\n }\n ]\n },\n {\n \"cidr\": \"10.0.0.140/25\",\n \"protocol\": \"icmp\"\n },\n {\n \"cidr\": \"10.0.0.110/023\",\n \"protocol\": \"tcp\",\n \"port\": 65530\n },\n {\n \"cidr\": \"10.0.3.64/27\",\n \"except\": [],\n \"protocol\": \"udp\",\n \"port\": 78\n },\n {\n \"cidr\": \"41.12.201.130/012\",\n \"protocol\": \"tcp\",\n \"port\": 65535,\n \"endPort\": 65535\n }\n ]\n}"}],"expected":{"error":"allow[1].to[1].except[0] must be contained in 10.0.3.0/24"}}, + {"name":"random #77","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"128.0.0.0/4\"},{\"cidr\":\"10.0.2.64/00028\"},{\"cidr\":\"0.0.0.0/128.0.0.0\"},{\"cidr\":\"2001:db8::153/122\",\"except\":[\"2001:0db8:0000:0000:0000:0000:0000:0170/124\",\"2001:db8::160/125\"]}],\"ports\":[{\"protocol\":\"udp\"},{}]},{\"to\":[{\"cidr\":\"240.255.178.243/4\"},{\"cidr\":\"10.0.2.0/24\"}]},{\"cidr\":\"10.0.2.44/31\",\"protocol\":\"any\",\"port\":11,\"endPort\":17},{\"to\":[{\"cidr\":\"::/3\"},{\"cidr\":\"2001:db8::280/121\"},{\"cidr\":\"224.105.208.28/32\"}],\"ports\":[{\"protocol\":\"udp\"},{\"protocol\":\"tcp\",\"port\":81},{\"protocol\":\"tcp\",\"port\":81},{\"protocol\":\"tcp\",\"port\":442,\"endPort\":442}]},{\"cidr\":\"2001:db8::3f5/126\"},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0171/119\",\"protocol\":\"any\",\"port\":78}],\"deny\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.0.50/128\",\"except\":[\"2001:DB8::32\",\"2001:0db8:0000:0000:0000:0000:0.0.0.50\"],\"protocol\":\"tcp\",\"port\":82}]}"}],"expected":{"allow":["0.0.0.0/1","128.0.0.0/4","224.105.208.28/32:tcp:81","224.105.208.28/32:tcp:442","224.105.208.28/32:udp","240.0.0.0/4","::/3:tcp:81","::/3:tcp:442","::/3:udp","2001:db8::/119:tcp:78","2001:db8::/119:udp:78","2001:db8::140/123","2001:db8::168/125","2001:db8::280/121:tcp:81","2001:db8::280/121:tcp:442","2001:db8::280/121:udp","2001:db8::3f4/126"],"deny":[]}}, + {"name":"random #78","input":[{"text":"{\"allow\": [{}, {\"cidr\": \"10.0.1.88/29\", \"except\": [\"10.0.1.91/31\", \"10.0.1.89\", \"10.0.1.88/255.255.255.248\"], \"protocol\": \"tcp\", \"port\": 78, \"endPort\": 81}, {\"ports\": [{\"port\": 79, \"endPort\": 80}, {\"protocol\": \"udp\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"64.0.0.0/2\", \"except\": [\"116.128.0.0/9\"]}, {\"cidr\": \"10.0.0.0/23\", \"except\": [\"10.0.1.160/29\"]}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"any\", \"port\": 443}]}, {\"cidr\": \"10.0.0.118/0.0.0.63\", \"protocol\": \"tcp\", \"port\": 65533, \"endPort\": 65535}, {\"to\": [{\"cidr\": \"0.0.0.0/0\"}, {\"cidr\": \"58.97.0.0/16\"}, {\"cidr\": \"::/1\"}, {\"cidr\": \"10.0.0.0/23\"}]}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["0.0.0.0/0","::/1"]}}, + {"name":"random #79","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.2.148/29\"}, {\"cidr\": \"10.0.1.32/00027\", \"except\": [\"10.0.1.48/28\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0256/127\", \"except\": []}, {\"cidr\": \"2001:DB8::330/00125\"}]}]}"}],"expected":{"allow":["10.0.1.32/28","10.0.2.144/29","2001:db8::256/127","2001:db8::330/125"],"deny":[]}}, + {"name":"random #80","input":[{"text":"{\"allow\":[],\"deny\":[{\"to\":[{\"cidr\":\"10.0.1.0/24\",\"except\":[]},{\"cidr\":\"2001:db8::3c8/120\",\"except\":[\"10.0.1.0/24\",\"2001:db8::36e/123\"]},{\"cidr\":\"10.0.0.0/23\",\"except\":[]}]},{\"cidr\":\"bee9:4354::/32\",\"protocol\":\"any\",\"port\":9,\"endPort\":14},{\"cidr\":\"938d:babd:1752:fdf1:04d9:c621:0990:c684/127\",\"except\":[\"938d:babd:1752:fdf1:4d9:c621:990:c684/127\",\"10.0.0.0/33\"],\"protocol\":\"tcp\"},{\"to\":[{\"cidr\":\"186.0.0.0/8\"},{\"cidr\":\"2001:db8::180/122\",\"except\":[]},{\"cidr\":\"65.229.126.156/24\",\"except\":[\"65.229.126.136/29\",\"65.229.126.168/29\",\"65.229.126.64/0029\"]}],\"ports\":[{\"protocol\":\"any\",\"port\":6},{\"protocol\":\"tcp\"}]},{\"to\":[{\"cidr\":\"228.0.0.0/8\"},{\"cidr\":\"9f1b:204c:dd98:6ca3:8b4d:6990:377c:387c/127\"},{\"cidr\":\"2001:DB8::1C7/118\"}]},{\"cidr\":\"10.0.2.217\",\"except\":[\"10.0.2.217\",\"10.0.2.217/32\"],\"protocol\":\"tcp\",\"port\":442},{\"cidr\":\"2001:db8::2d/00123\",\"protocol\":\"icmp\"},{\"to\":[{\"cidr\":\"10.0.1.128/26\"},{\"cidr\":\"10.0.0.48/29\"},{\"cidr\":\"10.0.0.0/0.0.0.31\"},{\"cidr\":\"10.0.2.248/29\"}],\"ports\":[{\"protocol\":\"udp\"},{\"port\":78}]}]}"}],"expected":{"error":"deny[0].to[1].except[0] must be contained in 2001:db8::300/120"}}, + {"name":"random #81","input":[{"text":"{\n\t\"deny\":[]\n}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #82","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.56/0.0.0.7\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 65533\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"5d41:66bf:327e:9273:f581:3abb::/96\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 443,\n\t\t\t\"endPort\": 443\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.128/25\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.248/029\",\n\t\t\t\t\t\t\"10.0.1.176/30\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.64/00030\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 441\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 2\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.125/29\",\n\t\t\t\"except\": [],\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 441\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"b29a:49c:e895:7807:388b:931d:2c8e:b600/120\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::138/127\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::138/128\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/24\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.0.124/31\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/24\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.208/31\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.80/29\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.224/27\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.47\",\n\t\t\t\"except\": [\n\t\t\t\t\"10.0.1.47\",\n\t\t\t\t\"10.0.1.47\",\n\t\t\t\t\"10.0.1.47\"\n\t\t\t],\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 2\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"128.0.0.0/1\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"71a4:245c:ae13:2db4:fefe:8649:4493:4900/120\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"71a4:245c:ae13:2db4:fefe:8649:4493:4988/126\",\n\t\t\t\t\t\t\"71a4:245c:ae13:2db4:fefe:8649:4493:4930/124\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.220/00032\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.192/29\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 81\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.164/30\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.225/26\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.0.224/28\",\n\t\t\t\t\t\t\"10.0.0.192/27\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.224/28\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.239\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 443,\n\t\t\t\t\t\"endPort\": 449\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 1,\n\t\t\t\t\t\"endPort\": 6\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 80\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.224/28\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 65531,\n\t\t\t\"endPort\": 65531\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.0.0/24","10.0.1.56/29:udp:65533","10.0.1.120/29:udp:441","10.0.1.128/27","10.0.1.160/28","10.0.1.180/30","10.0.1.184/29","10.0.1.192/27","10.0.1.224/28","10.0.1.240/29","10.0.2.192/29:tcp","10.0.2.192/29:udp","10.0.3.224/27","128.0.0.0/1:tcp","128.0.0.0/1:udp","2001:db8::139/128:icmp","2001:db8::139/128:tcp","5d41:66bf:327e:9273:f581:3abb::/96:udp:443","71a4:245c:ae13:2db4:fefe:8649:4493:4900/123:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:4900/123:udp","71a4:245c:ae13:2db4:fefe:8649:4493:4920/124:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:4920/124:udp","71a4:245c:ae13:2db4:fefe:8649:4493:4940/122:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:4940/122:udp","71a4:245c:ae13:2db4:fefe:8649:4493:4980/125:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:4980/125:udp","71a4:245c:ae13:2db4:fefe:8649:4493:498c/126:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:498c/126:udp","71a4:245c:ae13:2db4:fefe:8649:4493:4990/124:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:4990/124:udp","71a4:245c:ae13:2db4:fefe:8649:4493:49a0/123:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:49a0/123:udp","71a4:245c:ae13:2db4:fefe:8649:4493:49c0/122:tcp","71a4:245c:ae13:2db4:fefe:8649:4493:49c0/122:udp","b29a:49c:e895:7807:388b:931d:2c8e:b600/120:icmp","b29a:49c:e895:7807:388b:931d:2c8e:b600/120:tcp"],"deny":["0.0.0.0/0:tcp:4","10.0.0.224/27:tcp","10.0.0.224/27:udp","10.0.2.164/30:tcp","10.0.2.164/30:udp","10.0.3.239/32:icmp","10.0.3.239/32:tcp:80","10.0.3.239/32:udp:1-6","10.0.3.239/32:udp:443-449","::/0:tcp:4"]}}, + {"name":"random #83","input":[{"text":"{\n\"allow\":[\n{\n\"to\":[\n{\n\"cidr\":\"60.0.0.0/8\"\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"10.0.2.176/29\"\n},\n{\n\"cidr\":\"10.0.0.0/23\",\n\"except\":[\n\"10.0.0.16/28\",\n\"10.0.0.0/23\"\n]\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"192.0.2.0/24\",\n\"except\":[\n\"10.0.0.0/8\"\n]\n}\n],\n\"ports\":[\n{\n\"protocol\":\"tcp\"\n}\n]\n},\n{}\n],\n\"deny\":[\n{\n\"to\":[\n{\n\"cidr\":\"10.0.2.30/0026\"\n},\n{\n\"cidr\":\"2001:db8::380/122\",\n\"except\":[\n\"2001:db8::3a0/123\"\n]\n}\n],\n\"ports\":[\n{\n\"protocol\":\"udp\",\n\"port\":10,\n\"endPort\":13\n},\n{\n\"protocol\":\"udp\",\n\"port\":65535\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"2001:db8::30/125\"\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"10.0.2.159/28\"\n},\n{\n\"cidr\":\"10.0.1.128/25\"\n},\n{\n\"cidr\":\"E81B:7AED:5B6C:CF67:16FF:5142:589F:D91B/120\"\n},\n{\n\"cidr\":\"149.219.141.0/24\",\n\"except\":[\n\"149.219.141.128/25\",\n\"149.219.141.176/28\",\n\"149.219.141.153/29\"\n]\n}\n],\n\"ports\":[\n{\n\"protocol\":\"any\"\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"2001:db8::89/119\"\n},\n{\n\"cidr\":\"10.0.0.0/24\"\n},\n{\n\"cidr\":\"10.0.2.128/26\"\n},\n{\n\"cidr\":\"10.0.0.0/22\"\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"10.0.1.0/24\"\n},\n{\n\"cidr\":\"10.0.1.11/32\"\n},\n{\n\"cidr\":\"2001:db8::191/122\"\n},\n{\n\"cidr\":\"10.0.3.133/26\"\n}\n],\n\"ports\":[\n{\n\"protocol\":\"icmp\"\n},\n{\n\"protocol\":\"tcp\"\n}\n]\n},\n{\n\"cidr\":\"2001:db8::200/119\",\n\"protocol\":\"any\",\n\"port\":442,\n\"endPort\":444\n}\n]\n}"}],"expected":{"error":"allow[2].to[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"random #84","input":[{"text":"{\"allow\": [], \"deny\": [{\"cidr\": \"176.0.0.0/4\", \"protocol\": \"any\"}, {\"to\": [{\"cidr\": \"224.205.16.60/031\"}]}, {\"to\": [{\"cidr\": \"10.0.3.0/26\"}, {\"cidr\": \"2001:db8::24/126\", \"except\": [\"2001:DB8::24/0126\", \"2001:db8::26/127\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.3.32/123\"}, {\"cidr\": \"10.0.1.0/24\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"any\", \"port\": 10}, {\"protocol\": \"tcp\", \"port\": 78, \"endPort\": 82}]}, {\"to\": [{\"cidr\": \"10.0.0.0/26\"}, {\"cidr\": \"10.0.1.0/24\", \"except\": []}, {\"cidr\": \"10.0.0.0/25\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 65534, \"endPort\": 65535}]}, {\"to\": [{\"cidr\": \"10.0.0.192/0026\"}, {\"cidr\": \"2001:DB8::390/125\"}]}, {\"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"any\", \"port\": 78, \"endPort\": 79}, {\"protocol\": \"tcp\", \"port\": 443, \"endPort\": 29025}, {\"port\": 65531}]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:000d\", \"protocol\": \"tcp\", \"port\": 79}, {\"ports\": [{\"port\": 11}, {\"protocol\": \"tcp\", \"port\": 79, \"endPort\": 85}]}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0:tcp:11","0.0.0.0/0:tcp:78-85","0.0.0.0/0:tcp:443-29025","0.0.0.0/0:tcp:65531","0.0.0.0/0:udp","10.0.0.192/26","10.0.1.0/24:icmp","10.0.1.0/24:tcp:10-11","10.0.3.0/26:icmp","10.0.3.0/26:tcp:10-11","176.0.0.0/4","224.205.16.60/31","::/0:tcp:11","::/0:tcp:78-85","::/0:tcp:443-29025","::/0:tcp:65531","::/0:udp","2001:db8::320/123:icmp","2001:db8::320/123:tcp:10-11","2001:db8::390/125"]}}, + {"name":"random #85","input":[{"text":"{\n\t\"allow\": [], \n\t\"deny\": []\n}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #86","input":[{"text":"{\"allow\": [{\"cidr\": \"128.0.0.0/4\", \"except\": [\"143.240.0.0/12\", \"142.0.0.0/7\"], \"protocol\": \"udp\", \"port\": 78, \"endPort\": 84}, {\"cidr\": \"a000:0000:0000:0000:0000:0000:0000:0000/3\", \"protocol\": \"any\", \"port\": 444, \"endPort\": 449}, {\"cidr\": \"2001:db8::/000118\", \"protocol\": \"tcp\"}], \"deny\": [{\"to\": [{\"cidr\": \"0000:0000:0000:0000:0000:ffff:0.0.0.0/96\"}, {\"cidr\": \"2001:db8::/118\", \"except\": [\"::/0\", \"2001:0db8:0000:0000:0000:0000:0.0.2.0/120\"]}], \"ports\": [{}, {\"protocol\": \"icmp\"}, {\"protocol\": \"any\", \"port\": 82, \"endPort\": 82}]}, {\"to\": [{\"cidr\": \"e0e6:d63a:823b:b849:761b:b624::/96\", \"except\": [\"e0e6:d63a:823b:b849:761b:b624:4000:0/101\", \"e0e6:d63a:823b:b849:761b:b624::/98\"]}, {\"cidr\": \"2001:db8::a/125\"}, {\"cidr\": \"10.0.2.91\"}, {\"cidr\": \"2001:db8::300/120\"}]}, {\"to\": [{\"cidr\": \"10.0.3.128/0.0.0.63\"}, {\"cidr\": \"2001:db8::200/120\"}, {\"cidr\": \"2001:db8::64/122\", \"except\": []}, {\"cidr\": \"10.0.0.96/27\"}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"icmp\"}]}]}"}],"expected":{"error":"deny[0].to[1].except[0] must be contained in 2001:db8::/118"}}, + {"name":"random #87","input":[{"text":"{\n\t\"deny\": [\n\t\t{\n\t\t\t\"cidr\": \"218.161.246.185/31\",\n\t\t\t\"except\": [\n\t\t\t\t\"218.161.246.184/31\"\n\t\t\t],\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"192.0.2.0/24\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.0.0/8\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"0.0.0.0/2\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 79\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/255.255.252.0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::100/0120\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0.0.1.232/125\",\n\t\t\t\t\t\t\"2001:db8::140/122\",\n\t\t\t\t\t\t\"2001:db8::1d0/126\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 65532\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::f8/00125\",\n\t\t\t\"except\": [\n\t\t\t\t\"2001:db8::fc/127\",\n\t\t\t\t\"2001:db8::fc/126\"\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #88","input":[{"text":"{\n\"allow\": [\n{\n\"cidr\": \"10.0.1.0/25\"\n}\n],\n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:DB8::12A/118\",\n\"except\": [\n\"2001:DB8::100/123\",\n\"2001:DB8::360/121\"\n]\n},\n{\n\"cidr\": \"2001:db8::80/121\",\n\"except\": [\n\"2001:0db8:0000:0000:0000:0000:0000:00a0/126\"\n]\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::20c/124\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"udp\"\n},\n{\n\"port\": 444\n},\n{\n\"protocol\": \"any\",\n\"port\": 65533,\n\"endPort\": 65535\n},\n{\n\"protocol\": \"any\",\n\"port\": 443\n}\n]\n},\n{\n\"cidr\": \"10.0.3.215/28\",\n\"protocol\": \"tcp\",\n\"port\": 78,\n\"endPort\": 82\n},\n{\n\"cidr\": \"2001:db8::180/121\",\n\"protocol\": \"tcp\",\n\"port\": 445,\n\"endPort\": 449\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/00022\"\n},\n{\n\"cidr\": \"10.0.1.0/24\"\n},\n{\n\"cidr\": \"76.16.197.226/31\"\n},\n{\n\"cidr\": \"0.0.0.0/2\"\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::1c5/125\",\n\"except\": [\n\"2001:db8::1c1\",\n\"2001:db8::1c0/125\",\n\"2001:db8::1c1\"\n]\n},\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/121\"\n},\n{\n\"cidr\": \"10.0.0.0/24\"\n},\n{\n\"cidr\": \"::/3\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"tcp\",\n\"port\": 8,\n\"endPort\": 12\n},\n{\n\"protocol\": \"udp\"\n}\n]\n},\n{\n\"cidr\": \"2001:db8::388/128\",\n\"protocol\": \"any\",\n\"port\": 315,\n\"endPort\": 317\n},\n{\n\"cidr\": \"10.0.2.48/30\",\n\"protocol\": \"tcp\",\n\"port\": 303,\n\"endPort\": 303\n},\n{\n\"cidr\": \"10.0.1.204/31\",\n\"protocol\": \"udp\",\n\"port\": 221\n},\n{\n\"cidr\": \"10.0.3.158/31\",\n\"protocol\": \"tcp\",\n\"port\": 163\n},\n{\n\"cidr\": \"10.0.0.28/32\",\n\"protocol\": \"udp\",\n\"port\": 217\n},\n{\n\"cidr\": \"10.0.1.252/31\",\n\"protocol\": \"tcp\",\n\"port\": 125,\n\"endPort\": 129\n},\n{\n\"cidr\": \"10.0.1.132/31\",\n\"protocol\": \"tcp\",\n\"port\": 105,\n\"endPort\": 109\n},\n{\n\"cidr\": \"10.0.3.198/31\",\n\"protocol\": \"any\",\n\"port\": 151\n},\n{\n\"cidr\": \"10.0.1.100/30\",\n\"protocol\": \"any\",\n\"port\": 199\n},\n{\n\"cidr\": \"10.0.2.154/31\",\n\"protocol\": \"any\",\n\"port\": 111\n},\n{\n\"cidr\": \"10.0.1.196/30\",\n\"protocol\": \"tcp\",\n\"port\": 199\n},\n{\n\"cidr\": \"10.0.1.36/30\",\n\"protocol\": \"tcp\",\n\"port\": 225,\n\"endPort\": 227\n},\n{\n\"cidr\": \"10.0.1.0/31\",\n\"protocol\": \"udp\",\n\"port\": 385,\n\"endPort\": 387\n},\n{\n\"cidr\": \"10.0.2.56/30\",\n\"protocol\": \"udp\",\n\"port\": 333\n},\n{\n\"cidr\": \"2001:db8::d0/128\",\n\"protocol\": \"any\",\n\"port\": 387,\n\"endPort\": 390\n},\n{\n\"cidr\": \"10.0.3.46/31\",\n\"protocol\": \"udp\",\n\"port\": 131\n},\n{\n\"cidr\": \"10.0.2.144/30\",\n\"protocol\": \"tcp\",\n\"port\": 371\n},\n{\n\"cidr\": \"10.0.0.134/32\",\n\"protocol\": \"udp\",\n\"port\": 243,\n\"endPort\": 246\n},\n{\n\"cidr\": \"10.0.0.56/31\",\n\"protocol\": \"any\",\n\"port\": 19\n},\n{\n\"cidr\": \"10.0.3.168/30\",\n\"protocol\": \"any\",\n\"port\": 5\n},\n{\n\"cidr\": \"2001:db8::1c3/128\",\n\"protocol\": \"any\",\n\"port\": 395\n},\n{\n\"cidr\": \"10.0.1.212/31\",\n\"protocol\": \"any\",\n\"port\": 371\n},\n{\n\"cidr\": \"10.0.3.132/30\",\n\"protocol\": \"udp\",\n\"port\": 379\n},\n{\n\"cidr\": \"10.0.0.174/32\",\n\"protocol\": \"tcp\",\n\"port\": 21\n},\n{\n\"cidr\": \"10.0.3.174/31\",\n\"protocol\": \"any\",\n\"port\": 107,\n\"endPort\": 107\n},\n{\n\"cidr\": \"10.0.1.144/31\",\n\"protocol\": \"tcp\",\n\"port\": 255,\n\"endPort\": 257\n},\n{\n\"cidr\": \"2001:db8::da/128\",\n\"protocol\": \"any\",\n\"port\": 279\n},\n{\n\"cidr\": \"10.0.0.96/30\",\n\"protocol\": \"tcp\",\n\"port\": 85,\n\"endPort\": 88\n},\n{\n\"cidr\": \"10.0.3.160/30\",\n\"protocol\": \"udp\",\n\"port\": 103\n},\n{\n\"cidr\": \"10.0.2.74/31\",\n\"protocol\": \"any\",\n\"port\": 379\n},\n{\n\"cidr\": \"10.0.1.96/30\",\n\"protocol\": \"any\",\n\"port\": 247,\n\"endPort\": 248\n},\n{\n\"cidr\": \"2001:db8::183/128\",\n\"protocol\": \"tcp\",\n\"port\": 243,\n\"endPort\": 244\n},\n{\n\"cidr\": \"2001:db8::17/128\",\n\"protocol\": \"any\",\n\"port\": 355\n},\n{\n\"cidr\": \"2001:db8::120/128\",\n\"protocol\": \"udp\",\n\"port\": 209\n},\n{\n\"cidr\": \"2001:db8::1ab/128\",\n\"protocol\": \"any\",\n\"port\": 339\n},\n{\n\"cidr\": \"2001:db8::24c/128\",\n\"protocol\": \"tcp\",\n\"port\": 315,\n\"endPort\": 317\n},\n{\n\"cidr\": \"10.0.0.152/32\",\n\"protocol\": \"udp\",\n\"port\": 201\n},\n{\n\"cidr\": \"2001:db8::369/128\",\n\"protocol\": \"any\",\n\"port\": 155\n},\n{\n\"cidr\": \"2001:db8::242/128\",\n\"protocol\": \"tcp\",\n\"port\": 25\n},\n{\n\"cidr\": \"2001:db8::2e0/128\",\n\"protocol\": \"any\",\n\"port\": 223\n},\n{\n\"cidr\": \"2001:db8::2d9/128\",\n\"protocol\": \"tcp\",\n\"port\": 381,\n\"endPort\": 384\n},\n{\n\"cidr\": \"10.0.1.35/32\",\n\"protocol\": \"udp\",\n\"port\": 15\n},\n{\n\"cidr\": \"2001:db8::262/128\",\n\"protocol\": \"tcp\",\n\"port\": 335,\n\"endPort\": 339\n},\n{\n\"cidr\": \"10.0.1.132/30\",\n\"protocol\": \"udp\",\n\"port\": 95,\n\"endPort\": 99\n},\n{\n\"cidr\": \"10.0.3.216/30\",\n\"protocol\": \"udp\",\n\"port\": 199\n},\n{\n\"cidr\": \"10.0.2.79/32\",\n\"protocol\": \"udp\",\n\"port\": 139,\n\"endPort\": 140\n},\n{\n\"cidr\": \"10.0.2.162/31\",\n\"protocol\": \"udp\",\n\"port\": 397,\n\"endPort\": 401\n},\n{\n\"cidr\": \"10.0.1.80/30\",\n\"protocol\": \"tcp\",\n\"port\": 107,\n\"endPort\": 107\n},\n{\n\"cidr\": \"10.0.1.90/32\",\n\"protocol\": \"tcp\",\n\"port\": 277\n},\n{\n\"cidr\": \"2001:db8::54/128\",\n\"protocol\": \"any\",\n\"port\": 227\n},\n{\n\"cidr\": \"2001:db8::36e/128\",\n\"protocol\": \"any\",\n\"port\": 239,\n\"endPort\": 241\n},\n{\n\"cidr\": \"2001:db8::3b5/128\",\n\"protocol\": \"tcp\",\n\"port\": 347\n},\n{\n\"cidr\": \"10.0.2.46/31\",\n\"protocol\": \"udp\",\n\"port\": 33\n},\n{\n\"cidr\": \"10.0.3.145/32\",\n\"protocol\": \"tcp\",\n\"port\": 209\n},\n{\n\"cidr\": \"10.0.3.249/32\",\n\"protocol\": \"any\",\n\"port\": 381\n},\n{\n\"cidr\": \"10.0.1.168/30\",\n\"protocol\": \"tcp\",\n\"port\": 351\n},\n{\n\"cidr\": \"10.0.0.26/31\",\n\"protocol\": \"any\",\n\"port\": 381\n},\n{\n\"cidr\": \"10.0.1.208/30\",\n\"protocol\": \"any\",\n\"port\": 317\n},\n{\n\"cidr\": \"10.0.3.216/31\",\n\"protocol\": \"any\",\n\"port\": 363,\n\"endPort\": 366\n},\n{\n\"cidr\": \"10.0.3.16/30\",\n\"protocol\": \"any\",\n\"port\": 41\n},\n{\n\"cidr\": \"10.0.2.108/30\",\n\"protocol\": \"tcp\",\n\"port\": 341,\n\"endPort\": 344\n},\n{\n\"cidr\": \"10.0.3.248/30\",\n\"protocol\": \"udp\",\n\"port\": 145,\n\"endPort\": 146\n},\n{\n\"cidr\": \"10.0.3.176/31\",\n\"protocol\": \"any\",\n\"port\": 289\n},\n{\n\"cidr\": \"10.0.0.179/32\",\n\"protocol\": \"tcp\",\n\"port\": 247,\n\"endPort\": 250\n},\n{\n\"cidr\": \"10.0.0.128/30\",\n\"protocol\": \"udp\",\n\"port\": 243\n},\n{\n\"cidr\": \"10.0.1.207/32\",\n\"protocol\": \"udp\",\n\"port\": 315\n},\n{\n\"cidr\": \"10.0.1.242/31\",\n\"protocol\": \"any\",\n\"port\": 245,\n\"endPort\": 245\n},\n{\n\"cidr\": \"2001:db8::11a/128\",\n\"protocol\": \"udp\",\n\"port\": 305\n},\n{\n\"cidr\": \"10.0.1.232/30\",\n\"protocol\": \"tcp\",\n\"port\": 167,\n\"endPort\": 170\n},\n{\n\"cidr\": \"10.0.1.221/32\",\n\"protocol\": \"udp\",\n\"port\": 371\n},\n{\n\"cidr\": \"2001:db8::32e/128\",\n\"protocol\": \"tcp\",\n\"port\": 237\n},\n{\n\"cidr\": \"10.0.1.92/31\",\n\"protocol\": \"udp\",\n\"port\": 319\n},\n{\n\"cidr\": \"2001:db8::140/128\",\n\"protocol\": \"any\",\n\"port\": 163\n},\n{\n\"cidr\": \"10.0.0.200/30\",\n\"protocol\": \"tcp\",\n\"port\": 25\n},\n{\n\"cidr\": \"2001:db8::15c/128\",\n\"protocol\": \"udp\",\n\"port\": 101,\n\"endPort\": 102\n},\n{\n\"cidr\": \"10.0.1.104/30\",\n\"protocol\": \"any\",\n\"port\": 141\n},\n{\n\"cidr\": \"10.0.1.216/31\",\n\"protocol\": \"udp\",\n\"port\": 275\n},\n{\n\"cidr\": \"10.0.1.14/31\",\n\"protocol\": \"udp\",\n\"port\": 301\n},\n{\n\"cidr\": \"2001:db8::245/128\",\n\"protocol\": \"tcp\",\n\"port\": 135\n},\n{\n\"cidr\": \"10.0.3.184/30\",\n\"protocol\": \"udp\",\n\"port\": 239,\n\"endPort\": 243\n},\n{\n\"cidr\": \"10.0.2.10/32\",\n\"protocol\": \"udp\",\n\"port\": 95,\n\"endPort\": 98\n},\n{\n\"cidr\": \"2001:db8::23e/128\",\n\"protocol\": \"tcp\",\n\"port\": 43\n},\n{\n\"cidr\": \"2001:db8::1aa/128\",\n\"protocol\": \"tcp\",\n\"port\": 361\n},\n{\n\"cidr\": \"10.0.1.81/32\",\n\"protocol\": \"any\",\n\"port\": 83\n},\n{\n\"cidr\": \"10.0.1.92/31\",\n\"protocol\": \"udp\",\n\"port\": 45,\n\"endPort\": 48\n},\n{\n\"cidr\": \"10.0.3.168/30\",\n\"protocol\": \"any\",\n\"port\": 123\n},\n{\n\"cidr\": \"10.0.3.105/32\",\n\"protocol\": \"udp\",\n\"port\": 317\n},\n{\n\"cidr\": \"10.0.1.52/32\",\n\"protocol\": \"tcp\",\n\"port\": 375\n},\n{\n\"cidr\": \"2001:db8::3b1/128\",\n\"protocol\": \"udp\",\n\"port\": 271,\n\"endPort\": 272\n},\n{\n\"cidr\": \"10.0.1.212/30\",\n\"protocol\": \"udp\",\n\"port\": 215,\n\"endPort\": 218\n},\n{\n\"cidr\": \"10.0.1.18/31\",\n\"protocol\": \"tcp\",\n\"port\": 213\n},\n{\n\"cidr\": \"10.0.2.156/31\",\n\"protocol\": \"udp\",\n\"port\": 31\n},\n{\n\"cidr\": \"2001:db8::355/128\",\n\"protocol\": \"any\",\n\"port\": 217\n},\n{\n\"cidr\": \"2001:db8::4a/128\",\n\"protocol\": \"any\",\n\"port\": 333\n},\n{\n\"cidr\": \"10.0.1.10/32\",\n\"protocol\": \"any\",\n\"port\": 19\n},\n{\n\"cidr\": \"10.0.2.208/30\",\n\"protocol\": \"tcp\",\n\"port\": 393\n},\n{\n\"cidr\": \"10.0.1.40/31\",\n\"protocol\": \"udp\",\n\"port\": 159\n},\n{\n\"cidr\": \"10.0.0.230/31\",\n\"protocol\": \"any\",\n\"port\": 387\n},\n{\n\"cidr\": \"2001:db8::143/128\",\n\"protocol\": \"any\",\n\"port\": 201\n},\n{\n\"cidr\": \"10.0.0.172/30\",\n\"protocol\": \"udp\",\n\"port\": 183,\n\"endPort\": 183\n},\n{\n\"cidr\": \"2001:db8::25c/128\",\n\"protocol\": \"udp\",\n\"port\": 29\n},\n{\n\"cidr\": \"2001:db8::20e/128\",\n\"protocol\": \"tcp\",\n\"port\": 323\n},\n{\n\"cidr\": \"10.0.2.182/32\",\n\"protocol\": \"udp\",\n\"port\": 387\n},\n{\n\"cidr\": \"10.0.1.100/30\",\n\"protocol\": \"tcp\",\n\"port\": 275\n},\n{\n\"cidr\": \"10.0.3.248/31\",\n\"protocol\": \"tcp\",\n\"port\": 193\n},\n{\n\"cidr\": \"10.0.2.244/30\",\n\"protocol\": \"tcp\",\n\"port\": 47\n},\n{\n\"cidr\": \"10.0.2.58/31\",\n\"protocol\": \"udp\",\n\"port\": 281\n},\n{\n\"cidr\": \"10.0.0.247/32\",\n\"protocol\": \"any\",\n\"port\": 165\n},\n{\n\"cidr\": \"2001:db8::22d/128\",\n\"protocol\": \"tcp\",\n\"port\": 283\n},\n{\n\"cidr\": \"10.0.0.218/31\",\n\"protocol\": \"udp\",\n\"port\": 203\n},\n{\n\"cidr\": \"2001:db8::63/128\",\n\"protocol\": \"udp\",\n\"port\": 303,\n\"endPort\": 303\n},\n{\n\"cidr\": \"10.0.0.112/32\",\n\"protocol\": \"any\",\n\"port\": 389\n},\n{\n\"cidr\": \"10.0.1.228/30\",\n\"protocol\": \"any\",\n\"port\": 311\n},\n{\n\"cidr\": \"10.0.2.48/32\",\n\"protocol\": \"any\",\n\"port\": 255\n},\n{\n\"cidr\": \"2001:db8::100/128\",\n\"protocol\": \"any\",\n\"port\": 97\n},\n{\n\"cidr\": \"10.0.0.95/32\",\n\"protocol\": \"any\",\n\"port\": 319\n},\n{\n\"cidr\": \"2001:db8::340/128\",\n\"protocol\": \"any\",\n\"port\": 229\n},\n{\n\"cidr\": \"2001:db8::155/128\",\n\"protocol\": \"tcp\",\n\"port\": 333\n},\n{\n\"cidr\": \"10.0.1.104/30\",\n\"protocol\": \"any\",\n\"port\": 341,\n\"endPort\": 342\n},\n{\n\"cidr\": \"10.0.1.235/32\",\n\"protocol\": \"udp\",\n\"port\": 103\n},\n{\n\"cidr\": \"10.0.2.204/30\",\n\"protocol\": \"any\",\n\"port\": 205\n},\n{\n\"cidr\": \"2001:db8::1d7/128\",\n\"protocol\": \"tcp\",\n\"port\": 249,\n\"endPort\": 250\n},\n{\n\"cidr\": \"10.0.1.243/32\",\n\"protocol\": \"tcp\",\n\"port\": 33\n},\n{\n\"cidr\": \"10.0.3.146/31\",\n\"protocol\": \"any\",\n\"port\": 163\n},\n{\n\"cidr\": \"10.0.3.92/30\",\n\"protocol\": \"tcp\",\n\"port\": 167\n},\n{\n\"cidr\": \"10.0.1.14/32\",\n\"protocol\": \"any\",\n\"port\": 325,\n\"endPort\": 328\n},\n{\n\"cidr\": \"2001:db8::16a/128\",\n\"protocol\": \"udp\",\n\"port\": 195,\n\"endPort\": 196\n},\n{\n\"cidr\": \"2001:db8::77/128\",\n\"protocol\": \"any\",\n\"port\": 269\n},\n{\n\"cidr\": \"10.0.2.44/30\",\n\"protocol\": \"udp\",\n\"port\": 279\n},\n{\n\"cidr\": \"10.0.0.246/31\",\n\"protocol\": \"tcp\",\n\"port\": 213\n},\n{\n\"cidr\": \"10.0.2.204/31\",\n\"protocol\": \"udp\",\n\"port\": 395\n},\n{\n\"cidr\": \"2001:db8::5e/128\",\n\"protocol\": \"tcp\",\n\"port\": 117,\n\"endPort\": 117\n},\n{\n\"cidr\": \"10.0.1.76/31\",\n\"protocol\": \"any\",\n\"port\": 331\n},\n{\n\"cidr\": \"2001:db8::3d4/128\",\n\"protocol\": \"tcp\",\n\"port\": 191\n},\n{\n\"cidr\": \"2001:db8::95/128\",\n\"protocol\": \"any\",\n\"port\": 93\n}\n]\n}"}],"expected":{"allow":["10.0.1.0/25"],"deny":["0.0.0.0/2","76.16.197.226/31","::/3:tcp:8-12","::/3:udp","2001:db8::/120","2001:db8::100/128:tcp:97","2001:db8::100/128:udp:97","2001:db8::11a/128:udp:305","2001:db8::120/123","2001:db8::140/122","2001:db8::180/121","2001:db8::200/120","2001:db8::32e/128:tcp:237","2001:db8::340/128:tcp:229","2001:db8::340/128:udp:229","2001:db8::355/128:tcp:217","2001:db8::355/128:udp:217","2001:db8::369/128:tcp:155","2001:db8::369/128:udp:155","2001:db8::36e/128:tcp:239-241","2001:db8::36e/128:udp:239-241","2001:db8::380/121"]}}, + {"name":"random #89","input":[{"text":"{\n\"allow\": [\n{\n\"cidr\": \"0.0.0.0/0\",\n\"protocol\": \"tcp\",\n\"port\": 81\n},\n{\n\"to\": [\n{\n\"cidr\": \"4d60:752c:80c1:1d30:1e45:a4b1:f988:ea12/127\",\n\"except\": []\n},\n{\n\"cidr\": \"2001:db8::3b8/125\",\n\"except\": [\n\"2001:0db8:0000:0000:0000:0000:0000:03be/127\",\n\"2001:db8::3be/127\",\n\"2001:0db8:0000:0000:0000:0000:0.0.3.188/000127\"\n]\n},\n{\n\"cidr\": \"2001:db8::200/119\"\n},\n{\n\"cidr\": \"2001:db8::2c8\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\",\n\"port\": 82\n},\n{\n\"protocol\": \"udp\",\n\"port\": 65535\n}\n]\n},\n{\n\"cidr\": \"10.0.0.96/27\",\n\"protocol\": \"tcp\",\n\"port\": 81,\n\"endPort\": 85\n}\n],\n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0300/125\",\n\"except\": [\n\"2001:db8::307\",\n\"2001:db8::301/128\"\n]\n},\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0100/120\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"tcp\",\n\"port\": 65534\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"128.0.0.0/01\"\n}\n]\n}\n]\n}"}],"expected":{"allow":["0.0.0.0/0:tcp:81","10.0.0.96/27:tcp:82-85","2001:db8::200/119:tcp:82","2001:db8::200/119:udp:82","2001:db8::200/119:udp:65535","4d60:752c:80c1:1d30:1e45:a4b1:f988:ea12/127:tcp:82","4d60:752c:80c1:1d30:1e45:a4b1:f988:ea12/127:udp:82","4d60:752c:80c1:1d30:1e45:a4b1:f988:ea12/127:udp:65535"],"deny":["128.0.0.0/1","2001:db8::100/120:tcp:65534","2001:db8::300/128:tcp:65534","2001:db8::302/127:tcp:65534","2001:db8::304/127:tcp:65534","2001:db8::306/128:tcp:65534"]}}, + {"name":"random #90","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"port\":65532\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":444\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65530\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65531,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.128/27\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.230/31\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.32/255.255.255.224\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.0/255.255.255.254\",\n\t\t\t\"except\":[]\n\t\t},\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":80,\n\t\t\t\t\t\"endPort\":80\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":82\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65534,\n\t\t\t\t\t\"endPort\":65534\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":79,\n\t\t\t\t\t\"endPort\":85\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.0/24\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.80/24\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.122/022\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::198/125\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.0.0/22\",\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:019c/126\",\n\t\t\t\t\t\t\"2001:db8::198/127\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":78\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:DB8::2BC/127\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:DB8::2BC/127\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::190\",\n\t\t\t\"except\":[\n\t\t\t\t\"2001:db8::190/128\"\n\t\t\t],\n\t\t\t\"protocol\":\"tcp\"\n\t\t}\n\t]\n}"}],"expected":{"error":"allow[5].to[3].except[0] must be contained in 2001:db8::198/125"}}, + {"name":"random #91","input":[{"text":"{\"allow\":[],\"deny\":[{\"to\":[{\"cidr\":\"10.0.3.86/26\"}]},{\"to\":[{\"cidr\":\"10.0.3.30/31\"},{\"cidr\":\"c291:3f54:4979:88b:61db:d08f:ade8:591f/64\"},{\"cidr\":\"205.19.0.0/16\"}],\"ports\":[{\"protocol\":\"any\"},{\"protocol\":\"udp\"},{\"protocol\":\"any\",\"port\":65530,\"endPort\":65531}]},{\"cidr\":\"10.0.0.192/26\",\"protocol\":\"udp\"},{\"cidr\":\"10.0.0.32/27\",\"protocol\":\"TCP\"}]}"}],"expected":{"error":"deny[3].protocol must be tcp, udp, icmp, or any"}}, + {"name":"random #92","input":[{"text":"{\"allow\": [], \"deny\": [{\"cidr\": \"2001:db8::f0/000126\", \"protocol\": \"udp\"}, {\"cidr\": \"10.0.0.128/26\", \"except\": [\"10.0.0.189/29\", \"10.0.0.128/27\"], \"protocol\": \"any\", \"port\": 11}]}"}],"expected":{"allow":[],"deny":["10.0.0.160/28:tcp:11","10.0.0.160/28:udp:11","10.0.0.176/29:tcp:11","10.0.0.176/29:udp:11","2001:db8::f0/126:udp"]}}, + {"name":"random #93","input":[{"text":"{\n \"allow\":[\n {\n \"cidr\":\"10.0.0.0/22\",\n \"protocol\":\"any\",\n \"ports\":[]\n }\n ],\n \"deny\":[\n {\n \"cidr\":\"145.253.164.154/31\",\n \"protocol\":\"icmp\"\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.0.168/255.255.255.252\",\n \"except\":[\n \"10.0.0.168/31\",\n \"10.0.0.168/30\",\n \"10.0.0.171/0.0.0.0\"\n ]\n },\n {\n \"cidr\":\"10.0.3.0/25\",\n \"except\":[\n \"10.0.3.12/31\",\n \"10.0.3.0/255.255.255.128\",\n \"10.0.3.64/26\"\n ]\n },\n {\n \"cidr\":\"10.0.0.0/22\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"tcp\"\n },\n {\n \"protocol\":\"any\",\n \"port\":78,\n \"endPort\":79\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"2001:db8::280/121\"\n },\n {\n \"cidr\":\"10.0.2.0/23\"\n },\n {\n \"cidr\":\"767c:759:5355:cd44::/64\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"any\",\n \"port\":444,\n \"endPort\":447\n },\n {\n \"port\":9\n },\n {\n \"protocol\":\"udp\",\n \"port\":442,\n \"endPort\":32197\n }\n ]\n }\n ]\n}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #94","input":[{"text":"{\"allow\":[{\"ports\":[{\"port\":79,\"endPort\":82},{\"protocol\":\"tcp\",\"port\":81,\"endPort\":58590}]},{\"cidr\":\"2001:db8::62/118\",\"except\":[\"2001:db8::/118\"],\"protocol\":\"udp\",\"port\":442,\"endPort\":442},{\"to\":[{\"cidr\":\"10.0.2.0/0.0.0.127\"}]}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.0.160/28\"}],\"ports\":[{\"protocol\":\"any\",\"port\":79},{\"protocol\":\"any\",\"port\":82,\"endPort\":82}]},{\"to\":[{\"cidr\":\"240.161.235.0/24\"},{\"cidr\":\"10.0.3.200/30\"}],\"ports\":[{\"protocol\":\"udp\"},{\"protocol\":\"udp\",\"port\":8,\"endPort\":13},{\"protocol\":\"udp\",\"port\":7}]},{\"cidr\":\"10.0.3.160/27\",\"except\":[\"10.0.3.176/28\",\"10.0.3.184/29\"],\"protocol\":\"udp\",\"port\":11},{\"cidr\":\"2001:db8::200/0120\",\"protocol\":\"udp\",\"port\":2,\"endPort\":4},{\"to\":[{\"cidr\":\"2001:DB8::37C/126\"},{\"cidr\":\"10.0.3.116/30\"},{\"cidr\":\"10.0.3.197/0029\"}],\"ports\":[{},{\"protocol\":\"udp\",\"port\":65531},{\"protocol\":\"udp\",\"port\":7},{\"port\":80,\"endPort\":84}]},{\"cidr\":\"10.0.0.0/28\"},{\"cidr\":\"10.0.0.33/22\",\"except\":[],\"protocol\":\"udp\"},{\"cidr\":\"8396:9384:b06f:f995:1035:f398::/96\",\"protocol\":\"any\",\"port\":78,\"endPort\":80}]}"}],"expected":{"allow":["0.0.0.0/0:tcp:79-58590","0.0.0.0/0:udp:79-82","10.0.2.0/25","::/0:tcp:79-58590","::/0:udp:79-82"],"deny":["10.0.0.0/22:udp","10.0.0.0/28","10.0.0.160/28:tcp:79","10.0.0.160/28:tcp:82","10.0.3.116/30","10.0.3.192/29","240.161.235.0/24:udp","2001:db8::200/120:udp:2-4","2001:db8::37c/126","8396:9384:b06f:f995:1035:f398::/96:tcp:78-80","8396:9384:b06f:f995:1035:f398::/96:udp:78-80"]}}, + {"name":"random #95","input":[{"text":"{\n\"allow\": [\n{\n\"cidr\": \"10.0.2.64/255.255.255.192\", \n\"protocol\": \"tcp\", \n\"port\": 441, \n\"ports\": [\n{\n\"protocol\": \"icmp\", \n\"port\": 1\n}\n]\n}\n], \n\"deny\": []\n}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #96","input":[{"text":"{\"allow\": [{\"ports\": [{\"port\": 82, \"endPort\": 83}, {\"port\": 9, \"endPort\": 14}, {\"protocol\": \"any\", \"port\": 81, \"endPort\": 81}]}, {\"cidr\": \"10.0.1.20/31\", \"protocol\": \"tcp\", \"port\": 3}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0042/127\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:0043\", \"2001:db8::43\"]}, {\"cidr\": \"200.0.0.0/008\", \"except\": [\"200.219.254.158/13\", \"200.0.0.0/8\"]}, {\"cidr\": \"10.0.1.250/27\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 440}, {\"port\": 65530}, {\"protocol\": \"tcp\"}, {\"protocol\": \"any\", \"port\": 3, \"endPort\": 15387}]}, {\"ports\": [{\"protocol\": \"tcp\"}]}], \"deny\": []}"}],"expected":{"allow":["0.0.0.0/0:tcp","0.0.0.0/0:udp:9-14","0.0.0.0/0:udp:81-83","10.0.1.224/27:udp:3-15387","10.0.1.224/27:udp:65530","::/0:tcp","::/0:udp:9-14","::/0:udp:81-83","2001:db8::42/128:udp:3-15387","2001:db8::42/128:udp:65530"],"deny":[]}}, + {"name":"random #97","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"10.0.2.85/23\"},{\"cidr\":\"2001:db8::95/118\",\"except\":[\"2001:0db8:0000:0000:0000:0000:0000:0108/126\",\"2001:db8::1a0/124\",\"2001:db8::ba/125\"]},{\"cidr\":\"2001:db8::260/123\",\"except\":[\"2001:db8::278/125\",\"2001:db8::260/124\",\"2001:db8::267/126\"]},{\"cidr\":\"2001:db8::100/121\",\"except\":[\"2001:db8::160/123\",\"2001:db8::154/127\"]}],\"ports\":[{\"protocol\":\"icmp\"},{\"protocol\":\"udp\",\"port\":443,\"endPort\":447}]},{\"to\":[{\"ci"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #98","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"9c1b:f9f4:ef87:a197:7624:2a0:b280:cf92/104\"},{\"cidr\":\"2001:db8::30/124\"},{\"cidr\":\"10.0.3.0/255.255.255.0\"},{\"cidr\":\"2001:db8::/118\"}],\"ports\":[{\"protocol\":\"any\"}]},{\"cidr\":\"10.0.2.176/28\",\"protocol\":\"tcp\",\"port\":440,\"endPort\":446},{\"protocol\":\"icmp\"}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.2.73/32\"}],\"ports\":[{\"port\":1,\"endPort\":7},{\"protocol\":\"icmp\"}]},{\"to\":[{\"cidr\":\"10.0.3.192/26\",\"except\":[]},{\"cidr\":\"b233:a14e:5048:8ca4:0000:0000:0000:0000/64\",\"except\":[\"b233:a14e:5048:8ca4::/0064\",\"b233:a14e:5048:8ca4:6800::/69\"]},{\"cidr\":\"2001:db8::200/119\"},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.1.226/127\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":65530,\"endPort\":65533},{\"protocol\":\"udp\",\"port\":65532,\"endPort\":65535},{\"protocol\":\"tcp\",\"port\":445,\"endPort\":451}]},{\"to\":[{\"cidr\":\"0.0.0.0/0\",\"except\":[]},{\"cidr\":\"10.0.0.192/26\"}]},{\"to\":[{\"cidr\":\"10.0.2.0/24\"},{\"cidr\":\"10.0.3.29/29\",\"except\":[\"10.0.3.28/31\"]},{\"cidr\":\"10.0.3.192/27\"}],\"ports\":[{\"port\":79,\"endPort\":81},{\"protocol\":\"tcp\",\"port\":65531,\"endPort\":65535}]},{\"to\":[{\"cidr\":\"10.0.2.161/27\"},{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0040/123\"},{\"cidr\":\"10.0.3.209/25\",\"except\":[\"10.0.3.250/31\",\"10.0.3.157/26\"]},{\"cidr\":\"44.147.211.46/255.255.255.254\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":65532,\"endPort\":65533},{\"protocol\":\"udp\"}]},{\"to\":[{\"cidr\":\"c802:c7cc:d8:a487:3515:7fde:219b:1227/3\",\"except\":[]}],\"ports\":[{\"protocol\":\"udp\"}]},{\"to\":[{\"cidr\":\"224.0.0.0/240.0.0.0\",\"except\":[]},{\"cidr\":\"10.0.1.220/26\",\"except\":[\"10.0.1.197\",\"10.0.1.236\",\"10.0.1.208/255.255.255.192\"]},{\"cidr\":\"2001:DB8::300/121\",\"except\":[\"2001:0db8:0000:0000:0000:0000:0000:0320/123\",\"2001:db8::340/122\"]}],\"ports\":[{\"protocol\":\"tcp\",\"port\":10,\"endPort\":14},{\"protocol\":\"any\"},{\"protocol\":\"any\",\"port\":11,\"endPort\":11}]},{}]}"}],"expected":{"error":"allow[2].cidr is required"}}, + {"name":"random #99","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1a0/123\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 9\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"159.221.32.166/24\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.19/30\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.136/28\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"port\": 440\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 443,\n\t\t\t\t\t\"endPort\": 448\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 80\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::40/123\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"40c6:ef4f:34e2:54b2:2871:a0ea:1a00:0/104\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"192.0.0.0/2\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.20/0127\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 65531\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.119/22\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 8\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::320/123\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"b2dc:4245:2daa:9303:d1dd:b79:a68e:9dd7/127\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/22\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.160/28\",\n\t\t\t\t\t\t\"10.0.1.234/26\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.88/29\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.194/31\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.2.195\",\n\t\t\t\t\t\t\"10.0.2.194/32\",\n\t\t\t\t\t\t\"10.0.2.195/32\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 65531\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.2.16/30:tcp:80","10.0.2.16/30:tcp:440","10.0.2.16/30:tcp:443-448","10.0.2.16/30:udp","10.0.2.128/28:tcp:80","10.0.2.128/28:tcp:440","10.0.2.128/28:tcp:443-448","10.0.2.128/28:udp","159.221.32.0/24:tcp:80","159.221.32.0/24:tcp:440","159.221.32.0/24:tcp:443-448","159.221.32.0/24:udp","192.0.0.0/2","2001:db8::14/127","2001:db8::40/123","2001:db8::1a0/123:tcp:9","40c6:ef4f:34e2:54b2:2871:a0ea:1a00:0/104"],"deny":["10.0.0.0/22","2001:db8::320/123","b2dc:4245:2daa:9303:d1dd:b79:a68e:9dd6/127"]}}, + {"name":"random #100","input":[{"text":"{\"allow\": [{\"cidr\": \"232.253.205.217/63.255.255.255\", \"protocol\": \"udp\"}, {\"cidr\": \"2001:db8::a6/127\", \"except\": [\"2001:db8::a7\", \"2001:db8::a6/127\", \"2001:0db8:0000:0000:0000:0000:0.0.0.167/128\"], \"protocol\": \"udp\"}]}"}],"expected":{"allow":["192.0.0.0/2:udp"],"deny":[]}}, + {"name":"random #101","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"216.132.128.102/0032\"}, {\"cidr\": \"2001:db8::3b1/123\"}, {\"cidr\": \"2001:db8::12e/124\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 9}, {\"protocol\": \"udp\", \"port\": 65535, \"endPort\": 65535}, {\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\", \"port\": 65531, \"endPort\": 65532}]}, {\"to\": [{\"cidr\": \"2001:db8::180/122\"}, {\"cidr\": \"4727:c883:3177:b7d6:669e:8d94:bc84:50b8/127\"}, {\"cidr\": \"2001:db8::c0/123\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65535}]}]}"}],"expected":{"allow":[],"deny":["216.132.128.102/32:tcp","216.132.128.102/32:udp:65535","2001:db8::c0/123:tcp:65535","2001:db8::120/124:tcp","2001:db8::120/124:udp:65535","2001:db8::180/122:tcp:65535","2001:db8::3a0/123:tcp","2001:db8::3a0/123:udp:65535","4727:c883:3177:b7d6:669e:8d94:bc84:50b8/127:tcp:65535"]}}, + {"name":"random #102","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.1.1/32\", \"except\": [\"10.0.1.1\"], \"protocol\": \"udp\"}, {\"to\": [{\"cidr\": \"90.215.74.220/2\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 1}]}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0024/122\"}, {\"cidr\": \"171.0.0.0/8\", \"except\": [\"171.0.0.0/0011\", \"171.128.0.0/9\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 443}, {\"protocol\": \"icmp\", \"port\": 6, \"endPort\": 6}, {\"protocol\": \"udp\", \"port\": 2, \"endPort\": 4}]}, {\"cidr\": \"76.137.248.255/31\", \"protocol\": \"tcp\", \"port\": 78, \"endPort\": 81}], \"deny\": []}"}],"expected":{"error":"allow[2].ports[1].port is not supported with icmp"}}, + {"name":"random #103","input":[{"text":"{\"allow\": [], \"allow\": [{\"to\": [{\"cidr\": \"10.0.3.192/29\", \"except\": [\"10.0.3.192/29\"]}, {\"cidr\": \"10.0.0.122/22\"}]}, {\"to\": [{\"cidr\": \"2001:db8::1e/127\"}, {\"cidr\": \"10.0.3.0/255.255.255.0\", \"except\": []}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65534}, {\"protocol\": \"tcp\"}, {\"port\": 65533, \"endPort\": 65535}]}, {\"to\": [{\"cidr\": \"10.0.2.0/24\"}, {\"cidr\": \"5720:ce40:88ca:4d4a:23bf:9ace:32aa:d322/64\"}, {\"cidr\": \"10.0.0.0/255.255.254.0\", \"except\": [\"10.0.1.128/25\", \"10.0.1.177/27\", \"10.0.1.120/0031\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 443, \"endPort\": 449}, {\"protocol\": \"udp\", \"port\": 78}]}, {\"to\": [{\"cidr\": \"118.243.0.0/16\"}, {\"cidr\": \"2001:db8::1bb/0123\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"64.0.0.0/192.0.0.0\"}], \"ports\": [{\"port\": 80}, {\"protocol\": \"tcp\", \"port\": 1}, {\"protocol\": \"tcp\", \"port\": 7, \"endPort\": 9}, {\"protocol\": \"tcp\", \"port\": 78, \"endPort\": 81}]}, {\"to\": [{\"cidr\": \"10.0.0.59/30\"}, {\"cidr\": \"2001:db8::/0118\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:0100/120\"]}], \"ports\": [{\"port\": 65531, \"endPort\": 65535}, {\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"2001:db8::/119\"}, {\"cidr\": \"10.0.0.0/23\"}]}, {\"cidr\": \"10.0.0.128/25\", \"protocol\": \"udp\", \"port\": 442, \"endPort\": 446}, {\"cidr\": \"2001:db8::74/128\", \"protocol\": \"udp\", \"port\": 267, \"endPort\": 268}, {\"cidr\": \"2001:db8::1ed/128\", \"protocol\": \"any\", \"port\": 135}, {\"cidr\": \"2001:db8::31e/128\", \"protocol\": \"udp\", \"port\": 395, \"endPort\": 399}, {\"cidr\": \"2001:db8::226/128\", \"protocol\": \"udp\", \"port\": 179}, {\"cidr\": \"2001:db8::3d5/128\", \"protocol\": \"tcp\", \"port\": 325}, {\"cidr\": \"10.0.1.53/32\", \"protocol\": \"tcp\", \"port\": 201}, {\"cidr\": \"10.0.0.244/30\", \"protocol\": \"udp\", \"port\": 305, \"endPort\": 306}, {\"cidr\": \"10.0.2.48/31\", \"protocol\": \"udp\", \"port\": 263}, {\"cidr\": \"10.0.0.208/31\", \"protocol\": \"udp\", \"port\": 399, \"endPort\": 402}, {\"cidr\": \"2001:db8::74/128\", \"protocol\": \"any\", \"port\": 305}, {\"cidr\": \"10.0.2.126/31\", \"protocol\": \"udp\", \"port\": 339}, {\"cidr\": \"10.0.0.150/31\", \"protocol\": \"any\", \"port\": 209, \"endPort\": 211}, {\"cidr\": \"2001:db8::35d/128\", \"protocol\": \"tcp\", \"port\": 237}, {\"cidr\": \"10.0.0.55/32\", \"protocol\": \"udp\", \"port\": 237}, {\"cidr\": \"2001:db8::1a3/128\", \"protocol\": \"udp\", \"port\": 385}, {\"cidr\": \"2001:db8::384/128\", \"protocol\": \"udp\", \"port\": 279}, {\"cidr\": \"10.0.3.2/32\", \"protocol\": \"udp\", \"port\": 113}, {\"cidr\": \"2001:db8::158/128\", \"protocol\": \"any\", \"port\": 175}, {\"cidr\": \"10.0.0.163/32\", \"protocol\": \"any\", \"port\": 79}, {\"cidr\": \"2001:db8::151/128\", \"protocol\": \"tcp\", \"port\": 11, \"endPort\": 13}, {\"cidr\": \"10.0.2.228/30\", \"protocol\": \"udp\", \"port\": 295}, {\"cidr\": \"10.0.0.178/32\", \"protocol\": \"udp\", \"port\": 379}, {\"cidr\": \"10.0.2.85/32\", \"protocol\": \"udp\", \"port\": 95}, {\"cidr\": \"10.0.1.195/32\", \"protocol\": \"any\", \"port\": 277}, {\"cidr\": \"10.0.3.218/31\", \"protocol\": \"tcp\", \"port\": 385, \"endPort\": 389}, {\"cidr\": \"10.0.0.190/31\", \"protocol\": \"udp\", \"port\": 375, \"endPort\": 375}, {\"cidr\": \"10.0.0.216/31\", \"protocol\": \"udp\", \"port\": 209}, {\"cidr\": \"10.0.0.189/32\", \"protocol\": \"tcp\", \"port\": 5}, {\"cidr\": \"10.0.2.26/31\", \"protocol\": \"any\", \"port\": 73}, {\"cidr\": \"10.0.0.2/31\", \"protocol\": \"udp\", \"port\": 43, \"endPort\": 44}, {\"cidr\": \"10.0.2.48/30\", \"protocol\": \"udp\", \"port\": 341}, {\"cidr\": \"2001:db8::33/128\", \"protocol\": \"any\", \"port\": 67}, {\"cidr\": \"10.0.3.16/31\", \"protocol\": \"tcp\", \"port\": 213}, {\"cidr\": \"10.0.3.222/31\", \"protocol\": \"udp\", \"port\": 25, \"endPort\": 27}, {\"cidr\": \"10.0.0.188/31\", \"protocol\": \"any\", \"port\": 251, \"endPort\": 254}, {\"cidr\": \"10.0.2.80/30\", \"protocol\": \"udp\", \"port\": 213, \"endPort\": 214}, {\"cidr\": \"10.0.0.240/31\", \"protocol\": \"tcp\", \"port\": 69}, {\"cidr\": \"10.0.3.252/30\", \"protocol\": \"any\", \"port\": 313}, {\"cidr\": \"2001:db8::bd/128\", \"protocol\": \"any\", \"port\": 113}, {\"cidr\": \"10.0.2.116/31\", \"protocol\": \"udp\", \"port\": 17, \"endPort\": 19}, {\"cidr\": \"10.0.2.109/32\", \"protocol\": \"tcp\", \"port\": 63}, {\"cidr\": \"10.0.3.107/32\", \"protocol\": \"udp\", \"port\": 381}, {\"cidr\": \"2001:db8::2b4/128\", \"protocol\": \"any\", \"port\": 165}, {\"cidr\": \"10.0.3.192/30\", \"protocol\": \"any\", \"port\": 351}, {\"cidr\": \"10.0.2.16/30\", \"protocol\": \"tcp\", \"port\": 357}, {\"cidr\": \"2001:db8::51/128\", \"protocol\": \"udp\", \"port\": 187}, {\"cidr\": \"10.0.3.191/32\", \"protocol\": \"any\", \"port\": 201}, {\"cidr\": \"2001:db8::389/128\", \"protocol\": \"udp\", \"port\": 385, \"endPort\": 385}, {\"cidr\": \"10.0.3.248/30\", \"protocol\": \"udp\", \"port\": 255}, {\"cidr\": \"2001:db8::21c/128\", \"protocol\": \"udp\", \"port\": 273}, {\"cidr\": \"10.0.3.192/32\", \"protocol\": \"tcp\", \"port\": 335, \"endPort\": 338}, {\"cidr\": \"2001:db8::3fa/128\", \"protocol\": \"tcp\", \"port\": 7}, {\"cidr\": \"2001:db8::3eb/128\", \"protocol\": \"any\", \"port\": 109}, {\"cidr\": \"10.0.0.168/30\", \"protocol\": \"tcp\", \"port\": 175}, {\"cidr\": \"10.0.3.212/30\", \"protocol\": \"udp\", \"port\": 79}, {\"cidr\": \"10.0.2.126/31\", \"protocol\": \"udp\", \"port\": 193, \"endPort\": 193}, {\"cidr\": \"10.0.1.46/31\", \"protocol\": \"any\", \"port\": 369}, {\"cidr\": \"10.0.2.0/31\", \"protocol\": \"tcp\", \"port\": 355, \"endPort\": 357}, {\"cidr\": \"2001:db8::266/128\", \"protocol\": \"tcp\", \"port\": 341}, {\"cidr\": \"10.0.0.138/31\", \"protocol\": \"tcp\", \"port\": 65}, {\"cidr\": \"10.0.2.246/32\", \"protocol\": \"udp\", \"port\": 33}, {\"cidr\": \"10.0.3.84/30\", \"protocol\": \"any\", \"port\": 151}, {\"cidr\": \"2001:db8::12c/128\", \"protocol\": \"tcp\", \"port\": 327}, {\"cidr\": \"10.0.0.18/31\", \"protocol\": \"any\", \"port\": 169}, {\"cidr\": \"2001:db8::100/128\", \"protocol\": \"udp\", \"port\": 99}, {\"cidr\": \"10.0.2.198/31\", \"protocol\": \"tcp\", \"port\": 263}, {\"cidr\": \"10.0.0.7/32\", \"protocol\": \"udp\", \"port\": 85}, {\"cidr\": \"10.0.1.129/32\", \"protocol\": \"any\", \"port\": 375}, {\"cidr\": \"10.0.2.52/31\", \"protocol\": \"any\", \"port\": 5, \"endPort\": 6}, {\"cidr\": \"2001:db8::5d/128\", \"protocol\": \"tcp\", \"port\": 273}, {\"cidr\": \"2001:db8::242/128\", \"protocol\": \"any\", \"port\": 393}, {\"cidr\": \"10.0.3.160/32\", \"protocol\": \"tcp\", \"port\": 59, \"endPort\": 63}, {\"cidr\": \"10.0.1.81/32\", \"protocol\": \"udp\", \"port\": 69}, {\"cidr\": \"10.0.2.68/32\", \"protocol\": \"udp\", \"port\": 67}, {\"cidr\": \"10.0.3.128/30\", \"protocol\": \"udp\", \"port\": 347}, {\"cidr\": \"2001:db8::88/128\", \"protocol\": \"udp\", \"port\": 223}, {\"cidr\": \"10.0.1.52/30\", \"protocol\": \"any\", \"port\": 217, \"endPort\": 217}, {\"cidr\": \"10.0.2.116/31\", \"protocol\": \"any\", \"port\": 27, \"endPort\": 28}, {\"cidr\": \"2001:db8::34b/128\", \"protocol\": \"tcp\", \"port\": 203}, {\"cidr\": \"2001:db8::291/128\", \"protocol\": \"tcp\", \"port\": 65}, {\"cidr\": \"10.0.1.232/30\", \"protocol\": \"any\", \"port\": 263}, {\"cidr\": \"10.0.1.240/31\", \"protocol\": \"any\", \"port\": 53}, {\"cidr\": \"10.0.0.44/30\", \"protocol\": \"any\", \"port\": 185, \"endPort\": 187}, {\"cidr\": \"10.0.3.236/32\", \"protocol\": \"tcp\", \"port\": 369}, {\"cidr\": \"2001:db8::21a/128\", \"protocol\": \"tcp\", \"port\": 13}, {\"cidr\": \"10.0.3.120/30\", \"protocol\": \"any\", \"port\": 9, \"endPort\": 10}, {\"cidr\": \"10.0.1.16/30\", \"protocol\": \"tcp\", \"port\": 55, \"endPort\": 56}, {\"cidr\": \"2001:db8::320/128\", \"protocol\": \"any\", \"port\": 89}, {\"cidr\": \"10.0.3.214/31\", \"protocol\": \"tcp\", \"port\": 263}, {\"cidr\": \"2001:db8::268/128\", \"protocol\": \"any\", \"port\": 237}, {\"cidr\": \"2001:db8::9c/128\", \"protocol\": \"tcp\", \"port\": 397}, {\"cidr\": \"2001:db8::23e/128\", \"protocol\": \"any\", \"port\": 381}, {\"cidr\": \"2001:db8::293/128\", \"protocol\": \"udp\", \"port\": 247, \"endPort\": 248}, {\"cidr\": \"10.0.3.108/30\", \"protocol\": \"tcp\", \"port\": 55, \"endPort\": 59}, {\"cidr\": \"10.0.0.162/31\", \"protocol\": \"any\", \"port\": 81, \"endPort\": 83}, {\"cidr\": \"10.0.0.242/31\", \"protocol\": \"any\", \"port\": 255}, {\"cidr\": \"2001:db8::234/128\", \"protocol\": \"tcp\", \"port\": 313, \"endPort\": 317}, {\"cidr\": \"10.0.3.109/32\", \"protocol\": \"udp\", \"port\": 367, \"endPort\": 371}, {\"cidr\": \"10.0.0.162/31\", \"protocol\": \"udp\", \"port\": 165}, {\"cidr\": \"2001:db8::3a5/128\", \"protocol\": \"any\", \"port\": 5}, {\"cidr\": \"10.0.3.212/31\", \"protocol\": \"any\", \"port\": 91, \"endPort\": 91}, {\"cidr\": \"10.0.0.140/30\", \"protocol\": \"udp\", \"port\": 3}, {\"cidr\": \"10.0.3.180/30\", \"protocol\": \"any\", \"port\": 103, \"endPort\": 107}, {\"cidr\": \"2001:db8::e9/128\", \"protocol\": \"udp\", \"port\": 263, \"endPort\": 264}, {\"cidr\": \"10.0.0.234/32\", \"protocol\": \"tcp\", \"port\": 361}, {\"cidr\": \"2001:db8::394/128\", \"protocol\": \"tcp\", \"port\": 65}, {\"cidr\": \"10.0.3.247/32\", \"protocol\": \"any\", \"port\": 253}, {\"cidr\": \"10.0.0.225/32\", \"protocol\": \"udp\", \"port\": 307}, {\"cidr\": \"2001:db8::41/128\", \"protocol\": \"any\", \"port\": 179}, {\"cidr\": \"10.0.3.112/32\", \"protocol\": \"udp\", \"port\": 295, \"endPort\": 298}, {\"cidr\": \"2001:db8::2b/128\", \"protocol\": \"udp\", \"port\": 231}, {\"cidr\": \"10.0.2.7/32\", \"protocol\": \"tcp\", \"port\": 233}, {\"cidr\": \"10.0.2.64/30\", \"protocol\": \"tcp\", \"port\": 69}, {\"cidr\": \"10.0.3.20/31\", \"protocol\": \"tcp\", \"port\": 193}, {\"cidr\": \"2001:db8::2/128\", \"protocol\": \"udp\", \"port\": 59}, {\"cidr\": \"2001:db8::2cb/128\", \"protocol\": \"tcp\", \"port\": 343}, {\"cidr\": \"10.0.1.176/30\", \"protocol\": \"tcp\", \"port\": 129}, {\"cidr\": \"2001:db8::35d/128\", \"protocol\": \"any\", \"port\": 165}, {\"cidr\": \"10.0.0.138/32\", \"protocol\": \"tcp\", \"port\": 299}, {\"cidr\": \"10.0.0.80/31\", \"protocol\": \"any\", \"port\": 397}, {\"cidr\": \"10.0.0.156/31\", \"protocol\": \"udp\", \"port\": 133, \"endPort\": 134}, {\"cidr\": \"10.0.2.130/31\", \"protocol\": \"any\", \"port\": 253, \"endPort\": 256}, {\"cidr\": \"10.0.3.200/30\", \"protocol\": \"any\", \"port\": 297, \"endPort\": 299}, {\"cidr\": \"10.0.2.156/31\", \"protocol\": \"tcp\", \"port\": 327}, {\"cidr\": \"10.0.0.224/30\", \"protocol\": \"udp\", \"port\": 317}, {\"cidr\": \"10.0.0.10/31\", \"protocol\": \"tcp\", \"port\": 243}, {\"cidr\": \"10.0.0.12/32\", \"protocol\": \"udp\", \"port\": 251}, {\"cidr\": \"2001:db8::3cc/128\", \"protocol\": \"any\", \"port\": 319}, {\"cidr\": \"10.0.3.115/32\", \"protocol\": \"udp\", \"port\": 181}, {\"cidr\": \"10.0.1.160/32\", \"protocol\": \"tcp\", \"port\": 31}, {\"cidr\": \"10.0.2.149/32\", \"protocol\": \"any\", \"port\": 321}, {\"cidr\": \"10.0.2.144/30\", \"protocol\": \"any\", \"port\": 397}, {\"cidr\": \"2001:db8::3ed/128\", \"protocol\": \"udp\", \"port\": 135}, {\"cidr\": \"10.0.3.237/32\", \"protocol\": \"tcp\", \"port\": 345, \"endPort\": 347}, {\"cidr\": \"10.0.2.190/31\", \"protocol\": \"udp\", \"port\": 117}, {\"cidr\": \"10.0.2.22/32\", \"protocol\": \"any\", \"port\": 41}, {\"cidr\": \"10.0.1.242/32\", \"protocol\": \"tcp\", \"port\": 347}, {\"cidr\": \"10.0.1.166/32\", \"protocol\": \"udp\", \"port\": 215}, {\"cidr\": \"10.0.2.116/32\", \"protocol\": \"tcp\", \"port\": 241, \"endPort\": 241}, {\"cidr\": \"10.0.3.10/31\", \"protocol\": \"any\", \"port\": 311, \"endPort\": 311}, {\"cidr\": \"2001:db8::129/128\", \"protocol\": \"udp\", \"port\": 125}, {\"cidr\": \"10.0.0.112/30\", \"protocol\": \"udp\", \"port\": 133}, {\"cidr\": \"2001:db8::1dd/128\", \"protocol\": \"any\", \"port\": 295}, {\"cidr\": \"2001:db8::c3/128\", \"protocol\": \"tcp\", \"port\": 35}, {\"cidr\": \"2001:db8::293/128\", \"protocol\": \"udp\", \"port\": 385}, {\"cidr\": \"10.0.3.170/32\", \"protocol\": \"tcp\", \"port\": 255}, {\"cidr\": \"10.0.2.89/32\", \"protocol\": \"udp\", \"port\": 337, \"endPort\": 337}, {\"cidr\": \"2001:db8::14e/128\", \"protocol\": \"tcp\", \"port\": 193, \"endPort\": 194}, {\"cidr\": \"10.0.0.110/31\", \"protocol\": \"tcp\", \"port\": 305, \"endPort\": 308}, {\"cidr\": \"10.0.2.46/31\", \"protocol\": \"any\", \"port\": 133}, {\"cidr\": \"10.0.0.150/31\", \"protocol\": \"udp\", \"port\": 73, \"endPort\": 76}, {\"cidr\": \"2001:db8::c5/128\", \"protocol\": \"tcp\", \"port\": 115, \"endPort\": 115}, {\"cidr\": \"10.0.1.216/30\", \"protocol\": \"tcp\", \"port\": 253}, {\"cidr\": \"10.0.3.158/31\", \"protocol\": \"udp\", \"port\": 143}, {\"cidr\": \"10.0.1.72/30\", \"protocol\": \"tcp\", \"port\": 49}, {\"cidr\": \"10.0.3.180/30\", \"protocol\": \"any\", \"port\": 135}, {\"cidr\": \"10.0.0.196/30\", \"protocol\": \"any\", \"port\": 13}, {\"cidr\": \"10.0.3.22/31\", \"protocol\": \"udp\", \"port\": 41}, {\"cidr\": \"10.0.2.70/32\", \"protocol\": \"tcp\", \"port\": 135, \"endPort\": 136}, {\"cidr\": \"10.0.2.136/30\", \"protocol\": \"tcp\", \"port\": 169}, {\"cidr\": \"10.0.2.92/30\", \"protocol\": \"tcp\", \"port\": 379}, {\"cidr\": \"10.0.0.64/32\", \"protocol\": \"any\", \"port\": 171, \"endPort\": 174}, {\"cidr\": \"10.0.3.96/30\", \"protocol\": \"tcp\", \"port\": 379, \"endPort\": 380}, {\"cidr\": \"2001:db8::214/128\", \"protocol\": \"tcp\", \"port\": 117, \"endPort\": 118}, {\"cidr\": \"10.0.1.48/30\", \"protocol\": \"any\", \"port\": 27, \"endPort\": 27}, {\"cidr\": \"2001:db8::3c8/128\", \"protocol\": \"tcp\", \"port\": 305}, {\"cidr\": \"2001:db8::208/128\", \"protocol\": \"any\", \"port\": 95}, {\"cidr\": \"10.0.1.173/32\", \"protocol\": \"any\", \"port\": 59}, {\"cidr\": \"10.0.3.88/31\", \"protocol\": \"tcp\", \"port\": 47}, {\"cidr\": \"2001:db8::22b/128\", \"protocol\": \"tcp\", \"port\": 215}, {\"cidr\": \"2001:db8::30b/128\", \"protocol\": \"any\", \"port\": 341, \"endPort\": 345}, {\"cidr\": \"2001:db8::322/128\", \"protocol\": \"any\", \"port\": 257}, {\"cidr\": \"10.0.1.98/31\", \"protocol\": \"any\", \"port\": 31}, {\"cidr\": \"10.0.1.12/30\", \"protocol\": \"udp\", \"port\": 179}, {\"cidr\": \"10.0.2.150/31\", \"protocol\": \"tcp\", \"port\": 39}, {\"cidr\": \"10.0.1.175/32\", \"protocol\": \"udp\", \"port\": 331}, {\"cidr\": \"2001:db8::1ee/128\", \"protocol\": \"any\", \"port\": 333}, {\"cidr\": \"10.0.1.184/31\", \"protocol\": \"tcp\", \"port\": 185}, {\"cidr\": \"2001:db8::87/128\", \"protocol\": \"udp\", \"port\": 183}, {\"cidr\": \"10.0.2.204/30\", \"protocol\": \"tcp\", \"port\": 41}, {\"cidr\": \"10.0.1.76/30\", \"protocol\": \"tcp\", \"port\": 53, \"endPort\": 54}, {\"cidr\": \"10.0.1.65/32\", \"protocol\": \"tcp\", \"port\": 181}, {\"cidr\": \"2001:db8::11f/128\", \"protocol\": \"tcp\", \"port\": 125}, {\"cidr\": \"10.0.3.192/30\", \"protocol\": \"any\", \"port\": 357, \"endPort\": 360}, {\"cidr\": \"10.0.3.8/30\", \"protocol\": \"any\", \"port\": 41}, {\"cidr\": \"10.0.3.0/31\", \"protocol\": \"tcp\", \"port\": 275, \"endPort\": 276}, {\"cidr\": \"10.0.2.27/32\", \"protocol\": \"any\", \"port\": 83}, {\"cidr\": \"2001:db8::169/128\", \"protocol\": \"tcp\", \"port\": 81}, {\"cidr\": \"2001:db8::cc/128\", \"protocol\": \"any\", \"port\": 93}, {\"cidr\": \"10.0.0.32/30\", \"protocol\": \"any\", \"port\": 87, \"endPort\": 87}, {\"cidr\": \"10.0.2.92/30\", \"protocol\": \"tcp\", \"port\": 79}, {\"cidr\": \"10.0.3.144/32\", \"protocol\": \"udp\", \"port\": 93}, {\"cidr\": \"10.0.2.124/30\", \"protocol\": \"any\", \"port\": 173, \"endPort\": 177}, {\"cidr\": \"2001:db8::23d/128\", \"protocol\": \"tcp\", \"port\": 195}, {\"cidr\": \"10.0.3.28/31\", \"protocol\": \"udp\", \"port\": 201}, {\"cidr\": \"10.0.3.84/32\", \"protocol\": \"any\", \"port\": 45}, {\"cidr\": \"10.0.0.151/32\", \"protocol\": \"tcp\", \"port\": 83}, {\"cidr\": \"2001:db8::b5/128\", \"protocol\": \"tcp\", \"port\": 265}, {\"cidr\": \"10.0.1.42/32\", \"protocol\": \"udp\", \"port\": 99, \"endPort\": 99}, {\"cidr\": \"2001:db8::29b/128\", \"protocol\": \"tcp\", \"port\": 313, \"endPort\": 316}, {\"cidr\": \"10.0.0.208/30\", \"protocol\": \"tcp\", \"port\": 247, \"endPort\": 249}, {\"cidr\": \"2001:db8::357/128\", \"protocol\": \"udp\", \"port\": 169, \"endPort\": 173}, {\"cidr\": \"2001:db8::39f/128\", \"protocol\": \"udp\", \"port\": 309}, {\"cidr\": \"2001:db8::149/128\", \"protocol\": \"any\", \"port\": 47}, {\"cidr\": \"10.0.2.92/30\", \"protocol\": \"tcp\", \"port\": 229, \"endPort\": 230}, {\"cidr\": \"10.0.1.160/32\", \"protocol\": \"udp\", \"port\": 299, \"endPort\": 300}, {\"cidr\": \"2001:db8::17a/128\", \"protocol\": \"any\", \"port\": 45}, {\"cidr\": \"2001:db8::1af/128\", \"protocol\": \"any\", \"port\": 35, \"endPort\": 35}, {\"cidr\": \"2001:db8::13f/128\", \"protocol\": \"any\", \"port\": 309}, {\"cidr\": \"10.0.2.194/32\", \"protocol\": \"any\", \"port\": 229}, {\"cidr\": \"10.0.0.228/30\", \"protocol\": \"any\", \"port\": 95}, {\"cidr\": \"10.0.1.181/32\", \"protocol\": \"any\", \"port\": 101}, {\"cidr\": \"10.0.0.86/31\", \"protocol\": \"any\", \"port\": 215}, {\"cidr\": \"10.0.0.196/32\", \"protocol\": \"any\", \"port\": 239}, {\"cidr\": \"10.0.3.123/32\", \"protocol\": \"udp\", \"port\": 281}, {\"cidr\": \"10.0.1.16/30\", \"protocol\": \"udp\", \"port\": 187, \"endPort\": 190}, {\"cidr\": \"10.0.1.136/30\", \"protocol\": \"any\", \"port\": 293}, {\"cidr\": \"2001:db8::203/128\", \"protocol\": \"tcp\", \"port\": 227}, {\"cidr\": \"2001:db8::35d/128\", \"protocol\": \"tcp\", \"port\": 263}, {\"cidr\": \"10.0.3.6/31\", \"protocol\": \"tcp\", \"port\": 355}, {\"cidr\": \"10.0.1.216/32\", \"protocol\": \"tcp\", \"port\": 191, \"endPort\": 191}, {\"cidr\": \"10.0.3.73/32\", \"protocol\": \"any\", \"port\": 299}, {\"cidr\": \"10.0.0.128/30\", \"protocol\": \"tcp\", \"port\": 163}, {\"cidr\": \"10.0.0.124/30\", \"protocol\": \"any\", \"port\": 303}, {\"cidr\": \"10.0.2.86/31\", \"protocol\": \"tcp\", \"port\": 11}, {\"cidr\": \"2001:db8::1bb/128\", \"protocol\": \"tcp\", \"port\": 247}, {\"cidr\": \"10.0.0.178/31\", \"protocol\": \"tcp\", \"port\": 323, \"endPort\": 324}, {\"cidr\": \"10.0.2.134/31\", \"protocol\": \"tcp\", \"port\": 249, \"endPort\": 253}, {\"cidr\": \"2001:db8::2be/128\", \"protocol\": \"tcp\", \"port\": 61}, {\"cidr\": \"10.0.1.40/30\", \"protocol\": \"udp\", \"port\": 69}, {\"cidr\": \"10.0.2.227/32\", \"protocol\": \"udp\", \"port\": 125}, {\"cidr\": \"10.0.0.240/30\", \"protocol\": \"udp\", \"port\": 355}, {\"cidr\": \"10.0.0.238/31\", \"protocol\": \"udp\", \"port\": 37}, {\"cidr\": \"2001:db8::30f/128\", \"protocol\": \"tcp\", \"port\": 165}, {\"cidr\": \"10.0.2.136/30\", \"protocol\": \"udp\", \"port\": 227}, {\"cidr\": \"10.0.0.44/32\", \"protocol\": \"any\", \"port\": 139}, {\"cidr\": \"2001:db8::1cf/128\", \"protocol\": \"tcp\", \"port\": 329, \"endPort\": 329}, {\"cidr\": \"2001:db8::9d/128\", \"protocol\": \"any\", \"port\": 339}, {\"cidr\": \"10.0.2.76/31\", \"protocol\": \"udp\", \"port\": 275}, {\"cidr\": \"2001:db8::77/128\", \"protocol\": \"any\", \"port\": 85}, {\"cidr\": \"10.0.1.212/30\", \"protocol\": \"udp\", \"port\": 167}, {\"cidr\": \"2001:db8::287/128\", \"protocol\": \"tcp\", \"port\": 115}, {\"cidr\": \"10.0.1.73/32\", \"protocol\": \"tcp\", \"port\": 93, \"endPort\": 93}, {\"cidr\": \"2001:db8::331/128\", \"protocol\": \"any\", \"port\": 209}, {\"cidr\": \"10.0.1.112/32\", \"protocol\": \"udp\", \"port\": 73}, {\"cidr\": \"2001:db8::85/128\", \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"10.0.1.72/32\", \"protocol\": \"udp\", \"port\": 205, \"endPort\": 208}, {\"cidr\": \"10.0.0.46/31\", \"protocol\": \"any\", \"port\": 201}, {\"cidr\": \"10.0.0.189/32\", \"protocol\": \"tcp\", \"port\": 223}, {\"cidr\": \"10.0.0.240/30\", \"protocol\": \"tcp\", \"port\": 15}, {\"cidr\": \"10.0.0.101/32\", \"protocol\": \"any\", \"port\": 9}, {\"cidr\": \"2001:db8::184/128\", \"protocol\": \"udp\", \"port\": 255, \"endPort\": 258}, {\"cidr\": \"10.0.1.51/32\", \"protocol\": \"any\", \"port\": 343, \"endPort\": 346}, {\"cidr\": \"10.0.0.220/30\", \"protocol\": \"tcp\", \"port\": 163}, {\"cidr\": \"10.0.0.190/32\", \"protocol\": \"tcp\", \"port\": 393}, {\"cidr\": \"10.0.1.144/30\", \"protocol\": \"any\", \"port\": 257}, {\"cidr\": \"2001:db8::3fc/128\", \"protocol\": \"udp\", \"port\": 193}, {\"cidr\": \"10.0.0.72/30\", \"protocol\": \"udp\", \"port\": 285}, {\"cidr\": \"2001:db8::3cc/128\", \"protocol\": \"any\", \"port\": 387}, {\"cidr\": \"10.0.1.116/30\", \"protocol\": \"any\", \"port\": 373, \"endPort\": 374}, {\"cidr\": \"2001:db8::3c7/128\", \"protocol\": \"tcp\", \"port\": 41}, {\"cidr\": \"10.0.1.150/32\", \"protocol\": \"tcp\", \"port\": 215}, {\"cidr\": \"10.0.2.68/32\", \"protocol\": \"udp\", \"port\": 259}, {\"cidr\": \"10.0.2.184/30\", \"protocol\": \"tcp\", \"port\": 75}, {\"cidr\": \"2001:db8::1f9/128\", \"protocol\": \"udp\", \"port\": 109}, {\"cidr\": \"10.0.3.190/32\", \"protocol\": \"udp\", \"port\": 323, \"endPort\": 327}, {\"cidr\": \"2001:db8::1d5/128\", \"protocol\": \"any\", \"port\": 5}, {\"cidr\": \"10.0.1.95/32\", \"protocol\": \"udp\", \"port\": 95, \"endPort\": 96}, {\"cidr\": \"10.0.3.172/30\", \"protocol\": \"any\", \"port\": 31}, {\"cidr\": \"10.0.2.232/31\", \"protocol\": \"tcp\", \"port\": 129}, {\"cidr\": \"10.0.3.66/31\", \"protocol\": \"tcp\", \"port\": 69}, {\"cidr\": \"10.0.3.255/32\", \"protocol\": \"tcp\", \"port\": 303, \"endPort\": 306}, {\"cidr\": \"10.0.0.84/31\", \"protocol\": \"any\", \"port\": 293}, {\"cidr\": \"10.0.1.152/30\", \"protocol\": \"udp\", \"port\": 373, \"endPort\": 373}, {\"cidr\": \"2001:db8::39b/128\", \"protocol\": \"udp\", \"port\": 231, \"endPort\": 234}, {\"cidr\": \"2001:db8::313/128\", \"protocol\": \"tcp\", \"port\": 43, \"endPort\": 43}]}"}],"expected":{"error":"duplicate JSON property: allow"}}, + {"name":"random #104","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::2e7/00121\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.80/28\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 11\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.205/25\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 79\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::204/126\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"4934:b28:63bd:8bc5:8ea8:6ad:1600:0/104\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"4934:b28:63bd:8bc5:8ea8:6ad:16b9:0/112\",\n\t\t\t\t\t\t\"4934:b28:63bd:8bc5:8ea8:6ad:1610:0/108\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.64/00027\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.64/026\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 6\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 441\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 78\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 80,\n\t\t\t\t\t\"endPort\": 84\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 10\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.0/23\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::300/121\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::320/00123\",\n\t\t\t\t\t\t\"10.0.1.224/27\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.0/24\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.32/24\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 444,\n\t\t\t\"endPort\": 445\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.0/23\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[3].to[1].except[1] must be contained in 2001:db8::300/121"}}, + {"name":"random #105","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.1.128/025\"}, {\"cidr\": \"10.0.0.0/22\"}], \"ports\": [{\"port\": 441}, {\"port\": 11, \"endPort\": 14}, {\"protocol\": \"any\"}]}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:036a/127\"}, {\"cidr\": \"2001:db8::13f/123\"}, {\"cidr\": \"2001:db8::3f/120\"}, {\"cidr\": \"10.0.0.0/22\", \"except\": [\"10.0.0.17/24\"]}], \"ports\": [{\"protocol\": \"any\", \"port\": 5, \"endPort\": 20751}, {\"protocol\": \"tcp\", \"port\": 65531, \"endPort\": 65532}, {\"protocol\": \"any\"}]}], \"deny\": []}"}],"expected":{"allow":["10.0.0.0/22","2001:db8::/120","2001:db8::120/123","2001:db8::36a/127"],"deny":[]}}, + {"name":"random #106","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/23\"}, {\"cidr\": \"176.189.184.144/24\"}], \"ports\": [{\"port\": 3, \"endPort\": 7}, {\"protocol\": \"tcp\", \"port\": 78}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"10.0.0.0/22\"}, {\"cidr\": \"10.0.0.122/31\"}, {\"cidr\": \"FAF4:22E2:7E57:64EA:900A:F6A5:6182:8AD0/120\"}]}, {\"cidr\": \"10.0.3.162/29\", \"protocol\": \"udp\", \"port\": 443, \"endPort\": 447}, {\"to\": [{\"cidr\": \"2001:db8::240/124\"}, {\"cidr\": \"10.0.2.88/29\"}, {\"cidr\": \"10.0.3.57/24\", \"except\": []}], \"ports\": [{\"protocol\": \"udp\", \"port\": 444, \"endPort\": 446}, {\"protocol\": \"tcp\", \"port\": 440}]}, {\"cidr\": \"10.0.1.170/255.255.255.254\", \"except\": [\"10.0.1.171\"], \"protocol\": \"udp\"}, {}, {\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.0/124\"}, {\"cidr\": \"2001:db8::3f1\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65535, \"endPort\": 65535}]}, {\"to\": [{\"cidr\": \"3.150.135.185/1\"}]}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"random #107","input":[{"text":"{\n \"allow\": [],\n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.0/22\"\n },\n {\n \"cidr\": \"10.0.0.237/32\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"any\",\n \"port\": NaN\n },\n {\n \"protocol\": \"tcp\",\n \"port\": 80,\n \"endPort\": 85\n },\n {\n \"protocol\": \"tcp\"\n },\n {\n \"protocol\": \"tcp\"\n }\n ]\n },\n {\n \"cidr\": \"eb79:61c6:c624:ae92::/064\",\n \"protocol\": \"any\",\n \"port\": 65530\n },\n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::300/120\",\n \"except\": [\n \"2001:0db8:0000:0000:0000:0000:0000:0300/121\"\n ]\n }\n ]\n }\n ]\n}"}],"expected":{"error":"deny[0].ports[0].port must be an integer"}}, + {"name":"random #108","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.64/00026\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.3.93/29\",\n\t\t\t\t\t\t\"10.0.3.64/27\",\n\t\t\t\t\t\t\"10.0.3.89/32\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"8670:f370:b3ec:ee0d:9211:6b4f:0000:0000/96\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 78,\n\t\t\t\t\t\"endPort\": 45206\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"::ffff:0.0.0.0/096\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.192/26\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 442,\n\t\t\t\t\t\"endPort\": 443\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.0/27\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"848a:9271:2d98:c528:c70a:4828:ac56:b9e4/127\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"848a:9271:2d98:c528:c70a:4828:ac56:b9e4/127\",\n\t\t\t\t\t\t\"848a:9271:2d98:c528:c70a:4828:ac56:b9e4/127\",\n\t\t\t\t\t\t\"848a:9271:2d98:c528:c70a:4828:ac56:b9e4\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0225/126\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"4bba:36b9:8765:8425:9266:6f08:96e9:620e/1\",\n\t\t\t\"protocol\": \"icmp\"\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.3.96/27:tcp:78-45206","10.0.3.96/27:udp:78-45206","8670:f370:b3ec:ee0d:9211:6b4f::/96:tcp:78-45206","8670:f370:b3ec:ee0d:9211:6b4f::/96:udp:78-45206"],"deny":["10.0.2.0/27","10.0.3.192/26:tcp:442-443","10.0.3.192/26:udp:442-443","::/1:icmp","::ffff:0.0.0.0/96:tcp:442-443","::ffff:0.0.0.0/96:udp:442-443","2001:db8::224/126"]}}, + {"name":"random #109","input":[{"text":"{\n\"allow\":[\n{\n\"to\":[\n{\n\"cidr\":\"192.0.2.0/24\",\n\"except\":[\n\"10.0.0.0/8\"\n]\n}\n],\n\"ports\":[\n{\n\"protocol\":\"udp\",\n\"port\":65531\n}\n]\n}\n]\n}"}],"expected":{"error":"allow[0].to[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"random #110","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::d8/126\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":7\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/0022\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65532\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":81\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.168/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":12,\n\t\t\t\"endPort\":15\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"43.0.0.0/8\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"73.213.75.193/12\",\n\t\t\t\"except\":[\n\t\t\t\t\"73.219.0.0/16\"\n\t\t\t],\n\t\t\t\"protocol\":\"any\"\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/255.255.252.0\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::200/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.128/25\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":80\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"port\":78\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"port\":65533,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65535\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::ffff:0.0.0.0/096\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"::ffff:148.0.0.0/102\",\n\t\t\t\t\t\t\"::ffff:250.70.249.87/98\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65533,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":80\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"cb51:c873:70a3:df87:2f6b:e7a7:b365:9cb/32\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::ffff:125.0.0.0/104\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"::ffff:125.196.0.0/110\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"225.242.96.226/8\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::a7/121\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":8\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::d9/119\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::fb/120\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:02cd/123\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":445\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::352/127\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"9a0:4eca:a840:9594:654e:2357::/96\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"9a0:4eca:a840:9594:654e:2357:e000:0/101\",\n\t\t\t\t\t\t\"9a0:4eca:a840:9594:654e:2357:bfc3:d768/97\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":80,\n\t\t\t\t\t\"endPort\":81\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::177/126\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:03c7/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/1\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":81,\n\t\t\t\t\t\"endPort\":83\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.20\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.40/31\",\n\t\t\t\"except\":[],\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":65532\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::1e0/123\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.0/24\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":441,\n\t\t\t\t\t\"endPort\":445\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/2\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"7c7d:1206:3dec:f736:c839:fff2:5a2a:3c7f/96\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"7c7d:1206:3dec:f736:c839:fff2:4000:0/100\",\n\t\t\t\t\t\t\"7c7d:1206:3dec:f736:c839:fff2:6a00:0000/103\",\n\t\t\t\t\t\t\"7C7D:1206:3DEC:F736:C839:FFF2:6C14:63AF/98\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::350/0124\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"port\":12\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65535,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":7,\n\t\t\t\t\t\"endPort\":12\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::c0/125\",\n\t\t\t\"except\":[\n\t\t\t\t\"2001:db8::c1/128\",\n\t\t\t\t\"2001:db8::c0/00126\",\n\t\t\t\t\"2001:db8::c0/125\"\n\t\t\t],\n\t\t\t\"protocol\":\"tcp\"\n\t\t},\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"0.0.0.0/240.0.0.0\",\n\t\t\t\"protocol\":\"any\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::a8/128\",\n\t\t\t\"except\":[\n\t\t\t\t\"2001:db8::a8/128\",\n\t\t\t\t\"2001:db8::a8\"\n\t\t\t],\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":65535\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.192/27\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":445,\n\t\t\t\"endPort\":451\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.103/255.255.255.248\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.1.98/31\",\n\t\t\t\t\t\t\"10.0.1.102/31\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.0/23\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":11\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"9933:97dd:821d:1335:3f0c:af1f:eef:985/128\",\n\t\t\t\"protocol\":\"udp\"\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::/64\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65535,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":78,\n\t\t\t\t\t\"endPort\":79\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":3,\n\t\t\t\t\t\"endPort\":5\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.0/23\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.29/23\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.1.22/255.255.255.248\",\n\t\t\t\t\t\t\"10.0.1.45/0.0.0.1\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"a6c8:8fef:0f06:4e44:d440:d4de:0100:0000/104\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"51.101.1.0/24\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":5\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.0/23\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.155/0029\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\":[\n\t\t{}\n\t]\n}"}],"expected":{"allow":["0.0.0.0/0","::/0:icmp","::/0:tcp","::/0:udp","::ffff:125.0.0.0/105","::ffff:125.128.0.0/106","::ffff:125.192.0.0/110","::ffff:125.200.0.0/109","::ffff:125.208.0.0/108","::ffff:125.224.0.0/107","a6c8:8fef:f06:4e44:d440:d4de:100:0/104","cb51:c873::/32"],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"random #111","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.75/32\"\n }, \n {\n \"cidr\": \"2001:db8::22e/118\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 78, \n \"endPort\": 82\n }, \n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 65535, \n \"endPort\": 65535\n }\n ]\n }, \n {\n \"cidr\": \"86.246.226.227\", \n \"protocol\": \"tcp\", \n \"port\": 441, \n \"endPort\": 445\n }\n ]\n}"}],"expected":{"allow":["10.0.3.75/32:icmp","10.0.3.75/32:tcp:78-82","10.0.3.75/32:udp:65535","86.246.226.227/32:tcp:441-445","2001:db8::/118:icmp","2001:db8::/118:tcp:78-82","2001:db8::/118:udp:65535"],"deny":[]}}, + {"name":"random #112","input":[{"text":"{\n \"allow\": [\n {}, \n {\n \"ports\": [\n {\n \"protocol\": \"any\"\n }, \n {\n \"protocol\": \"udp\"\n }, \n {\n \"protocol\": \"tcp\"\n }\n ]\n }, \n {\n \"cidr\": \"2001:db8::37/120\", \n \"protocol\": \"icmp\"\n }\n ], \n \"deny\": [\n {\n \"cidr\": \"2001:db8::28b/124\", \n \"except\": [\n \"2001:db8::289/124\", \n \"2001:db8::287/125\"\n ], \n \"protocol\": \"any\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.2.190/31\"\n }, \n {\n \"cidr\": \"10.0.3.48/28\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\"\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["10.0.2.190/31:tcp","10.0.3.48/28:tcp"]}}, + {"name":"random #113","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"842e:f963:4f0d:465e::/64\", \n\t\t\t\"protocol\": \"icmp\"\n\t\t}, \n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.0.160/255.255.255.224\", \n\t\t\t\"protocol\": \"any\"\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::194/128\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::16f/119\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/22\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.2.152/29\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.12/31\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 445\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.147/26\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.3.140/31\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.160/28\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/22\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.28/30\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"fa98:d637::/32\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 4, \n\t\t\t\t\t\"endPort\": 9\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 440\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"unknown\": 1\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.17/27\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.144/30\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.64/0.0.0.63\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 65532\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"allow[4] has unknown field 'unknown'"}}, + {"name":"random #114","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0180/121\"}, {\"cidr\": \"10.0.2.128/26\"}, {\"cidr\": \"10.0.1.47/255.255.255.224\", \"except\": [\"10.0.1.62/29\"]}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"udp\"}, {\"protocol\": \"icmp\"}]}, {\"cidr\": \"2001:db8::200/121\", \"protocol\": \"udp\", \"port\": 80, \"endPort\": 84}], \"deny\": [{\"to\": [{\"cidr\": \"8b82:0446:ef00:8ab4:0000:0000:0000:0000/0064\"}], \"ports\": [{\"port\": 65534, \"endPort\": 65535}]}, {\"cidr\": \"10.0.0.107/28\", \"protocol\": \"udp\", \"port\": 82}, {\"to\": [{\"cidr\": \"0.0.0.0/1\"}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\"}]}]}"}],"expected":{"allow":["10.0.1.32/28:icmp","10.0.1.32/28:udp","10.0.1.48/29:icmp","10.0.1.48/29:udp","10.0.2.128/26:icmp","10.0.2.128/26:udp","2001:db8::180/121:icmp","2001:db8::180/121:udp","2001:db8::200/121:udp:80-84"],"deny":["0.0.0.0/1:tcp","10.0.0.96/28:udp:82","8b82:446:ef00:8ab4::/64:tcp:65534-65535","8b82:446:ef00:8ab4::/64:udp:65534-65535"]}}, + {"name":"random #115","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::a1/123\"\n }, \n {\n \"cidr\": \"189.16.129.215/4\"\n }, \n {\n \"cidr\": \"10.0.1.0/00025\", \n \"except\": [\n \"10.0.1.84/31\", \n \"10.0.1.0/25\"\n ]\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["176.0.0.0/4","2001:db8::a0/123"],"deny":[]}}, + {"name":"random #116","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::19f/128\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"::/1\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 10\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 445\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.3.128/25\", \n\t\t\t\"protocol\": \"tcp\", \n\t\t\t\"port\": 4, \n\t\t\t\"endPort\": 37064\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.2/31\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"43f9:238e::/32\", \n\t\t\t\"protocol\": \"any\"\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.1.247/255.255.254.0\", \n\t\t\t\"except\": []\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"193.204.123.212/32\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.2.0/127\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t], \n\t\"deny\": [\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.0/255.255.254.0\", \n\t\t\t\"protocol\": \"udp\"\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"205.164.89.57/4\", \n\t\t\t\"protocol\": \"udp\", \n\t\t\t\"port\": 80, \n\t\t\t\"endPort\": 86\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"183.0.0.0/8\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01be/124\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.96/27\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/22\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 11, \n\t\t\t\t\t\"endPort\": 15\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"port\": 65532, \n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 5\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"2001:db8::23/124\", \n\t\t\t\"protocol\": \"tcp\", \n\t\t\t\"port\": 82\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:DB8::390/125\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.3.124/30\", \n\t\t\t\t\t\t\"2001:db8::392/000128\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"::/3\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"120.141.5.198/31\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.44/32\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 3, \n\t\t\t\t\t\"endPort\": 6\n\t\t\t\t}, \n\t\t\t\t{}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[4].to[0].except[0] must be contained in 2001:db8::390/125"}}, + {"name":"random #117","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.245/22\"\n }\n ]\n },\n {\n \"cidr\": \"10.0.0.113\",\n \"except\": [],\n \"protocol\": \"tcp\",\n \"port\": 65531,\n \"endPort\": 65533\n },\n {\n \"cidr\": \"56.255.0.0/16\"\n },\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.90/127\",\n \"protocol\": \"icmp\"\n },\n {\n \"cidr\": \"2001:db8::/118\",\n \"except\": [\n \"2001:db8::/121\"\n ],\n \"protocol\": \"udp\",\n \"port\": 82,\n \"endPort\": 85\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.160/27\",\n \"except\": [\n \"10.0.0.160/31\",\n \"10.0.0.168/0.0.0.0\",\n \"10.0.0.181/32\"\n ]\n },\n {\n \"cidr\": \"0.0.0.0/1\",\n \"except\": []\n },\n {\n \"cidr\": \"10.0.0.0/00022\"\n },\n {\n \"cidr\": \"2001:db8::98/127\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"udp\",\n \"port\": 11,\n \"endPort\": 55205\n },\n {\n \"protocol\": \"any\"\n },\n {\n \"port\": 65534\n }\n ]\n },\n {\n \"cidr\": \"2001:db8::196/127\",\n \"protocol\": \"udp\",\n \"port\": 442\n },\n {}\n ]\n}"}],"expected":{"error":"allow[5].to[0].except[1] must be contained in 10.0.0.160/27"}}, + {"name":"random #118","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.0/255.255.252.0\",\n\t\t\t\"except\":[\n\t\t\t\t\"10.0.2.0/23\"\n\t\t\t],\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":7\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"80.0.0.0/4\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":78,\n\t\t\t\t\t\"endPort\":79\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"16.3.149.192/31\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/24\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:03d0/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"ec1a:62ea:d900:6520:d038:7f57:a800:0000/104\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"port\":65534,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65531,\n\t\t\t\t\t\"endPort\":65533\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65531\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::338/125\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"port\":5,\n\t\t\t\t\t\"endPort\":10\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/002\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"138.10.95.187/63.255.255.255\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"152.0.0.0/6\",\n\t\t\t\t\t\t\"137.201.104.135/2\",\n\t\t\t\t\t\t\"128.0.0.0/4\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/255.255.252.0\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":78,\n\t\t\t\t\t\"endPort\":79\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.64/255.255.255.248\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0090/125\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":440,\n\t\t\t\t\t\"endPort\":445\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.144/0030\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.96/255.255.255.248\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"232.165.205.15/1\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"128.0.0.0/1\",\n\t\t\t\t\t\t\"2001:db8::/118\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"240.0.0.0/240.0.0.0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::ffff:204.24.92.156/96\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"::ffff:0.0.0.0/96\",\n\t\t\t\t\t\t\"::ffff:0.0.0.0/99\",\n\t\t\t\t\t\t\"::FFFF:0.0.0.0/97\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.192/26\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\":[\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.180/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":82,\n\t\t\t\"endPort\":85\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/23\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.224/29\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":78\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/01\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"78.81.41.194/5\",\n\t\t\t\t\t\t\"64.0.0.0/3\",\n\t\t\t\t\t\t\"70.8.101.243/2\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":445\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":81,\n\t\t\t\t\t\"endPort\":87\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:DB8::240/123\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::1a0/126\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:db8::1a0/128\",\n\t\t\t\t\t\t\"2001:db8::1a0/126\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.204/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":79\n\t\t},\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":442\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/0\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"128.0.0.0/1\",\n\t\t\t\t\t\t\"116.0.0.0/6\",\n\t\t\t\t\t\t\"0.0.0.0/0\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":443\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":65531\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":7\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":79\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:DB8::300/120\",\n\t\t\t\"except\":[\n\t\t\t\t\"2001:db8::300/121\",\n\t\t\t\t\"2001:db8::340/122\",\n\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:037e/124\"\n\t\t\t],\n\t\t\t\"protocol\":\"udp\"\n\t\t}\n\t]\n}"}],"expected":{"error":"allow[6].to[2].except[1] must be contained in 128.0.0.0/1"}}, + {"name":"random #119","input":[{"text":"{\"allow\":[],\"deny\":[{\"cidr\":\"10.0.0.224/27\",\"protocol\":\"udp\",\"port\":78,\"endPort\":84},{\"cidr\":\"10.0.3.88/29\",\"protocol\":\"udp\",\"port\":444,\"endPort\":38486}]}"}],"expected":{"allow":[],"deny":["10.0.0.224/27:udp:78-84","10.0.3.88/29:udp:444-38486"]}}, + {"name":"random #120","input":[{"text":"{\"deny\": [{\"to\": [{\"cidr\": \"c70b:a38:761e:c36f:6ca4:b19e:aa0:b00/120\"}, {\"cidr\": \"10.0.2.64/031\", \"except\": [\"10.0.2.65\", \"10.0.2.65\", \"10.0.2.64/31\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 80, \"endPort\": 80}, {\"protocol\": \"tcp\", \"port\": 82, \"endPort\": 86}, {\"protocol\": \"icmp\"}]}]}"}],"expected":{"allow":[],"deny":["c70b:a38:761e:c36f:6ca4:b19e:aa0:b00/120:icmp","c70b:a38:761e:c36f:6ca4:b19e:aa0:b00/120:tcp:82-86","c70b:a38:761e:c36f:6ca4:b19e:aa0:b00/120:udp:80"]}}, + {"name":"random #121","input":[{"text":"{\n\t\"allow\": [], \n\t\"deny\": []\n}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #122","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::24/126\",\n \"except\": []\n },\n {\n \"cidr\": \"0.24.198.179/004\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }\n ]\n }\n ],\n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"2001:DB8::280/121\"\n },\n {\n \"cidr\": \"10.0.0.144/28\",\n \"except\": [\n \"10.0.0.153/255.255.255.254\",\n \"10.0.0.148/31\",\n \"10.0.0.159/255.255.255.254\"\n ]\n },\n {\n \"cidr\": \"1549:d01a:3e62:b620:b907:9f02:98ff:5c98/127\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"icmp\"\n },\n {\n \"protocol\": \"tcp\",\n \"port\": 82,\n \"endPort\": 82\n },\n {\n \"protocol\": \"icmp\"\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"48B9:6D55:9EF0:1B64:F56B:5A57:E9D0:A741/001\"\n },\n {\n \"cidr\": \"10.0.1.65\"\n },\n {\n \"cidr\": \"11.84.142.53/4\",\n \"except\": [\n \"5.0.0.0/8\",\n \"12.129.131.112/8\",\n \"0.0.0.0/4\"\n ]\n },\n {\n \"cidr\": \"10.0.3.128/26\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"udp\"\n },\n {\n \"protocol\": \"tcp\",\n \"port\": 65533\n },\n {\n \"port\": 5,\n \"endPort\": 7\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["0.0.0.0/4:icmp","2001:db8::24/126:icmp"],"deny":["10.0.0.144/30:icmp","10.0.0.144/30:tcp:82","10.0.0.150/31:icmp","10.0.0.150/31:tcp:82","10.0.0.154/31:icmp","10.0.0.154/31:tcp:82","10.0.0.156/31:icmp","10.0.0.156/31:tcp:82","10.0.1.65/32:tcp:5-7","10.0.1.65/32:tcp:65533","10.0.1.65/32:udp","10.0.3.128/26:tcp:5-7","10.0.3.128/26:tcp:65533","10.0.3.128/26:udp","::/1:tcp:5-7","::/1:tcp:65533","::/1:udp","1549:d01a:3e62:b620:b907:9f02:98ff:5c98/127:icmp","1549:d01a:3e62:b620:b907:9f02:98ff:5c98/127:tcp:82","2001:db8::280/121:icmp","2001:db8::280/121:tcp:82"]}}, + {"name":"random #123","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::200/120\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.14/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 353,\n\t\t\t\"endPort\": 353\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.122/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 77\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.44/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 259\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.76/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 259\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::12a/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 179,\n\t\t\t\"endPort\": 180\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.93/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 255\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.190/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 303\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.12/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 263\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1d1/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 347,\n\t\t\t\"endPort\": 347\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.32/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 335\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::143/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 277\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::31/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 95\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2c/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 51\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.64/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 381\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.153/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 45\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::97/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 17\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::47/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 215,\n\t\t\t\"endPort\": 219\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.178/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 341\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3be/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 333,\n\t\t\t\"endPort\": 335\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::ff/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 187\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.241/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 69\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::41/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 117\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.200/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 235\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.248/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 203\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.248/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 345\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.92/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 323,\n\t\t\t\"endPort\": 326\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.114/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 351\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.32/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 9\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.238/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 371\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::80/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 325\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.44/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 193\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.56/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 357,\n\t\t\t\"endPort\": 359\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1ed/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 55,\n\t\t\t\"endPort\": 58\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::180/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 381,\n\t\t\t\"endPort\": 381\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.48/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 189\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.222/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 399,\n\t\t\t\"endPort\": 401\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.4/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 237\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.64/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 397,\n\t\t\t\"endPort\": 397\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::254/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 123,\n\t\t\t\"endPort\": 127\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.158/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 91\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.27/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 195\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.189/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 215,\n\t\t\t\"endPort\": 216\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.52/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 335,\n\t\t\t\"endPort\": 335\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.218/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 129\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.80/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 397\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::d4/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 111,\n\t\t\t\"endPort\": 115\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.244/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 107,\n\t\t\t\"endPort\": 111\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.4/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 215\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::394/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 275\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::185/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 173\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::289/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 141\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::154/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 329,\n\t\t\t\"endPort\": 332\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.212/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 379\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.164/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 259\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::65/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 219\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.12/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 399\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.194/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 271\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.118/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 109\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.194/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 233,\n\t\t\t\"endPort\": 237\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.92/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 47\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.108/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 115,\n\t\t\t\"endPort\": 115\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::29b/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 85\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::23f/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 23\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.212/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 101,\n\t\t\t\"endPort\": 103\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::234/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 201,\n\t\t\t\"endPort\": 202\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::241/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 139,\n\t\t\t\"endPort\": 141\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::381/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 27,\n\t\t\t\"endPort\": 30\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.228/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 397,\n\t\t\t\"endPort\": 399\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.191/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 199\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.136/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 135,\n\t\t\t\"endPort\": 135\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::301/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 219\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1f6/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 249,\n\t\t\t\"endPort\": 250\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.69/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 379\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.6/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 65,\n\t\t\t\"endPort\": 66\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.74/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 265\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.168/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 279\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.198/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 325\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.168/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 113\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.9/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 93\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::cb/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 23\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.154/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 195\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.176/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 365,\n\t\t\t\"endPort\": 369\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.24/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 111,\n\t\t\t\"endPort\": 115\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.49/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 239,\n\t\t\t\"endPort\": 240\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::146/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 175,\n\t\t\t\"endPort\": 178\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::3c6/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 251\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.108/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 11,\n\t\t\t\"endPort\": 11\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.54/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 297\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.159/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 181,\n\t\t\t\"endPort\": 183\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.192/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 299\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.148/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 89,\n\t\t\t\"endPort\": 93\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.176/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 121,\n\t\t\t\"endPort\": 124\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2a4/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 303,\n\t\t\t\"endPort\": 303\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.243/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 229\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.52/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 181\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.184/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 259,\n\t\t\t\"endPort\": 261\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::34a/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 91\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.214/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 139\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.80/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 65\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.126/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 303,\n\t\t\t\"endPort\": 305\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.226/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 49\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.32/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 139,\n\t\t\t\"endPort\": 140\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.128/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 329\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.16/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 141\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.22/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 127,\n\t\t\t\"endPort\": 131\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::327/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 129\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1de/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 239\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.132/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 223\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.182/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 157\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::85/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 309,\n\t\t\t\"endPort\": 309\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::6d/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 189,\n\t\t\t\"endPort\": 192\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.40/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 205\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::f1/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 187\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.227/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 355\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.167/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 183\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.96/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 163,\n\t\t\t\"endPort\": 165\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::a1/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 39\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.144/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 83,\n\t\t\t\"endPort\": 87\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::18e/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 235\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::ed/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 307\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.179/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 283\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.105/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 73,\n\t\t\t\"endPort\": 77\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::352/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 21,\n\t\t\t\"endPort\": 23\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.0/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 99,\n\t\t\t\"endPort\": 100\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.52/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 231\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.236/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 271,\n\t\t\t\"endPort\": 273\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.72/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 339\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.150/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 143\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::f5/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 129\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.30/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 233\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.44/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 247,\n\t\t\t\"endPort\": 247\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.214/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 235\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::2c7/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 65\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.238/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 21,\n\t\t\t\"endPort\": 25\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.44/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 265,\n\t\t\t\"endPort\": 267\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.88/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 185\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.184/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 347\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.134/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 399\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.174/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 179,\n\t\t\t\"endPort\": 182\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.142/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 283\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.36/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 155\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.10/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 125\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.14/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 261\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.188/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 117\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.140/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 31\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.174/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 175\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::fd/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 141,\n\t\t\t\"endPort\": 145\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::70/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 325\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::12d/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 137\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::159/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 285,\n\t\t\t\"endPort\": 285\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.67/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 389,\n\t\t\t\"endPort\": 391\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.176/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 29\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.8/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 221,\n\t\t\t\"endPort\": 221\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.42/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 251\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::119/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 93\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.97/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 327\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.0.126/31\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 215\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::21d/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 361\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.54/31\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 179\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.60/32\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 5\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.80/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 337\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.192/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 135\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.176/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 91\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.201/32\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 71\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::388/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 201\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::8e/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 327\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.143/32\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 59\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.0/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 239\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::12d/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 297,\n\t\t\t\"endPort\": 299\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::73/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 307\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.10/31\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 95\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::e0/128\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 139\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::353/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 167,\n\t\t\t\"endPort\": 168\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.120/30\",\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 139,\n\t\t\t\"endPort\": 139\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1e4/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 35\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.12/30\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 209,\n\t\t\t\"endPort\": 210\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.8/30\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 275,\n\t\t\t\"endPort\": 279\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::f2/128\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 145\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::338/128\",\n\t\t\t\"protocol\": \"udp\",\n\t\t\t\"port\": 391\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.128/027\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.195/22\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 65534,\n\t\t\t\t\t\"endPort\": 65534\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.0/118\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.32/27\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.0.8/31:tcp:221","10.0.0.10/32:tcp:125","10.0.0.10/32:udp:125","10.0.0.14/32:udp:261","10.0.0.14/32:udp:353","10.0.0.22/31:tcp:127-131","10.0.0.22/31:udp:127-131","10.0.0.32/30:udp:335","10.0.0.44/30:udp:265-267","10.0.0.52/30:tcp:181","10.0.0.52/30:udp:181","10.0.0.52/31:tcp:231","10.0.0.54/32:tcp:297","10.0.0.54/32:udp:297","10.0.0.74/32:tcp:265","10.0.0.74/32:udp:265","10.0.0.80/30:tcp:397","10.0.0.108/32:tcp:11","10.0.0.126/31:udp:215","10.0.0.150/31:tcp:143","10.0.0.153/32:tcp:45","10.0.0.153/32:udp:45","10.0.0.158/32:udp:91","10.0.0.168/30:udp:113","10.0.0.174/31:tcp:179-182","10.0.0.174/31:udp:179-182","10.0.0.174/32:tcp:175","10.0.0.176/30:tcp:121-124","10.0.0.176/30:udp:29","10.0.0.176/30:udp:121-124","10.0.0.179/32:udp:283","10.0.0.194/32:udp:271","10.0.0.200/31:tcp:235","10.0.0.200/31:udp:235","10.0.0.212/30:tcp:101-103","10.0.0.212/32:tcp:379","10.0.0.212/32:udp:379","10.0.0.218/32:udp:129","10.0.0.226/31:tcp:49","10.0.1.0/30:udp:99-100","10.0.1.10/31:tcp:95","10.0.1.16/32:tcp:141","10.0.1.16/32:udp:141","10.0.1.24/30:tcp:111-115","10.0.1.24/30:udp:111-115","10.0.1.42/31:udp:251","10.0.1.44/30:tcp:193","10.0.1.44/30:udp:193","10.0.1.48/32:tcp:189","10.0.1.48/32:udp:189","10.0.1.60/32:udp:5","10.0.1.72/30:udp:339","10.0.1.114/32:tcp:351","10.0.1.132/31:udp:223","10.0.1.134/32:udp:399","10.0.1.140/30:udp:31","10.0.1.142/31:tcp:283","10.0.1.142/31:udp:283","10.0.1.144/30:udp:83-87","10.0.1.148/31:tcp:89-93","10.0.1.182/32:udp:157","10.0.1.184/31:tcp:347","10.0.1.184/31:udp:347","10.0.1.191/32:tcp:199","10.0.1.192/31:tcp:135","10.0.1.192/32:tcp:299","10.0.1.201/32:tcp:71","10.0.1.201/32:udp:71","10.0.1.227/32:udp:355","10.0.1.241/32:udp:69","10.0.1.244/31:tcp:107-111","10.0.2.0/30:tcp:239","10.0.2.6/32:udp:65-66","10.0.2.12/30:udp:399","10.0.2.12/32:tcp:263","10.0.2.12/32:udp:263","10.0.2.27/32:tcp:195","10.0.2.30/31:tcp:233","10.0.2.32/32:tcp:139-140","10.0.2.32/32:udp:139-140","10.0.2.49/32:tcp:239-240","10.0.2.49/32:udp:239-240","10.0.2.54/31:tcp:179","10.0.2.54/31:udp:179","10.0.2.56/30:udp:357-359","10.0.2.64/31:tcp:397","10.0.2.80/30:tcp:337","10.0.2.80/30:udp:337","10.0.2.93/32:tcp:255","10.0.2.97/32:tcp:327","10.0.2.105/32:tcp:73-77","10.0.2.108/32:tcp:115","10.0.2.108/32:udp:115","10.0.2.118/31:tcp:109","10.0.2.120/30:tcp:139","10.0.2.120/30:udp:139","10.0.2.143/32:tcp:59","10.0.2.154/32:udp:195","10.0.2.159/32:udp:181-183","10.0.2.164/30:udp:259","10.0.2.168/30:tcp:279","10.0.2.178/31:tcp:341","10.0.2.178/31:udp:341","10.0.2.184/31:tcp:259-261","10.0.2.214/31:tcp:139","10.0.2.222/32:tcp:399-401","10.0.2.228/32:tcp:397-399","10.0.2.228/32:udp:397-399","10.0.2.236/32:tcp:271-273","10.0.2.238/32:tcp:371","10.0.2.238/32:udp:371","10.0.2.243/32:tcp:229","10.0.2.248/30:tcp:203","10.0.3.4/30:tcp:237","10.0.3.4/30:udp:237","10.0.3.4/31:udp:215","10.0.3.8/30:udp:275-279","10.0.3.9/32:tcp:93","10.0.3.9/32:udp:93","10.0.3.12/30:tcp:209-210","10.0.3.32/31:tcp:9","10.0.3.36/31:tcp:155","10.0.3.36/31:udp:155","10.0.3.40/32:tcp:205","10.0.3.40/32:udp:205","10.0.3.44/31:tcp:259","10.0.3.44/31:udp:247","10.0.3.52/31:tcp:335","10.0.3.64/30:udp:381","10.0.3.67/32:tcp:389-391","10.0.3.67/32:udp:389-391","10.0.3.69/32:udp:379","10.0.3.76/30:tcp:259","10.0.3.80/32:udp:65","10.0.3.88/30:tcp:185","10.0.3.88/30:udp:185","10.0.3.92/30:tcp:323-326","10.0.3.92/30:udp:323-326","10.0.3.92/31:tcp:47","10.0.3.92/31:udp:47","10.0.3.96/30:udp:163-165","10.0.3.122/32:tcp:77","10.0.3.126/32:tcp:303-305","10.0.3.126/32:udp:303-305","10.0.3.128/31:udp:329","10.0.3.136/31:tcp:135","10.0.3.136/31:udp:135","10.0.3.167/32:udp:183","10.0.3.176/30:tcp:91","10.0.3.176/30:udp:91","10.0.3.176/31:tcp:365-369","10.0.3.188/31:tcp:117","10.0.3.189/32:tcp:215-216","10.0.3.189/32:udp:215-216","10.0.3.190/31:tcp:303","10.0.3.190/31:udp:303","10.0.3.194/31:tcp:233-237","10.0.3.194/31:udp:233-237","10.0.3.198/32:tcp:325","10.0.3.198/32:udp:325","10.0.3.214/31:udp:235","10.0.3.238/32:tcp:21-25","10.0.3.248/31:tcp:345","10.0.3.248/31:udp:345","2001:db8::2c/128:tcp:51","2001:db8::31/128:tcp:95","2001:db8::31/128:udp:95","2001:db8::41/128:udp:117","2001:db8::47/128:tcp:215-219","2001:db8::65/128:udp:219","2001:db8::6d/128:udp:189-192","2001:db8::70/128:udp:325","2001:db8::73/128:tcp:307","2001:db8::80/128:tcp:325","2001:db8::85/128:udp:309","2001:db8::8e/128:udp:327","2001:db8::97/128:udp:17","2001:db8::a1/128:tcp:39","2001:db8::cb/128:udp:23","2001:db8::d4/128:tcp:111-115","2001:db8::d4/128:udp:111-115","2001:db8::e0/128:tcp:139","2001:db8::e0/128:udp:139","2001:db8::ed/128:tcp:307","2001:db8::f1/128:udp:187","2001:db8::f2/128:tcp:145","2001:db8::f5/128:tcp:129","2001:db8::f5/128:udp:129","2001:db8::fd/128:tcp:141-145","2001:db8::ff/128:udp:187","2001:db8::119/128:udp:93","2001:db8::12a/128:tcp:179-180","2001:db8::12d/128:tcp:297-299","2001:db8::12d/128:udp:137","2001:db8::12d/128:udp:297-299","2001:db8::143/128:udp:277","2001:db8::146/128:tcp:175-178","2001:db8::146/128:udp:175-178","2001:db8::154/128:tcp:329-332","2001:db8::154/128:udp:329-332","2001:db8::159/128:tcp:285","2001:db8::159/128:udp:285","2001:db8::180/128:tcp:381","2001:db8::185/128:tcp:173","2001:db8::18e/128:udp:235","2001:db8::1d1/128:tcp:347","2001:db8::1de/128:tcp:239","2001:db8::1de/128:udp:239","2001:db8::1e4/128:tcp:35","2001:db8::1ed/128:tcp:55-58","2001:db8::1ed/128:udp:55-58","2001:db8::1f6/128:tcp:249-250","2001:db8::200/120","2001:db8::301/128:udp:219","2001:db8::327/128:tcp:129","2001:db8::327/128:udp:129","2001:db8::338/128:udp:391","2001:db8::34a/128:tcp:91","2001:db8::34a/128:udp:91","2001:db8::352/128:tcp:21-23","2001:db8::353/128:udp:167-168","2001:db8::381/128:udp:27-30","2001:db8::388/128:tcp:201","2001:db8::388/128:udp:201","2001:db8::394/128:udp:275","2001:db8::3be/128:udp:333-335","2001:db8::3c6/128:tcp:251"],"deny":["10.0.0.0/22:tcp:2","10.0.0.0/22:tcp:65534","10.0.1.32/27","2001:db8::/118"]}}, + {"name":"random #124","input":[{"text":"{"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #125","input":[{"text":"{\"allow\": [], \"deny\": [{\"to\": [{\"cidr\": \"10.0.0.36/31\"}, {\"cidr\": \"10.0.0.102/0.0.0.127\"}, {\"cidr\": \"10.0.0.80/29\"}]}]}"}],"expected":{"allow":[],"deny":["10.0.0.0/25"]}}, + {"name":"random #126","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #127","input":[{"text":"{\"deny\": []}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #128","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"1286:2A2A:62E1:EC1C:F009:22E1:C571:403/3\"}, {\"cidr\": \"52.159.199.254/12\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 80, \"endPort\": 81}, {\"protocol\": \"udp\"}]}, {\"cidr\": \"10.0.1.212/30\", \"protocol\": \"tcp\", \"port\": 12, \"endPort\": 18}, {\"to\": [{\"cidr\": \"21ff:1592:4d4d:bf8c::/64\"}, {\"cidr\": \"10.0.2.192/26\"}, {\"cidr\": \"10.0.0.220/255.255.255.252\", \"except\": [\"10.0.0.222/31\", \"10.0.0.220/30\", \"10.0.0.221/32\"]}, {\"cidr\": \"10.0.2.32/27\"}], \"ports\": [{\"port\": 79}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"2001:db8::249/119\", \"except\": []}, {\"cidr\": \"10.0.3.0/24\"}, {\"cidr\": \"10.0.3.0/255.255.255.0\"}, {\"cidr\": \"219.16.0.0/16\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65535, \"endPort\": 65535}]}, {\"cidr\": \"21.48.29.119\", \"except\": [\"21.48.29.119\", \"21.48.29.119/32\", \"21.48.29.119\"], \"protocol\": \"tcp\"}, {\"to\": [{\"cidr\": \"76.127.0.0/16\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"2001:db8::1c0/122\"}, {\"cidr\": \"bb1:d489:ec25:33f2:683e:37ed:cac8:7e06/127\"}, {\"cidr\": \"2001:db8::1c8/125\"}]}, {\"to\": [{\"cidr\": \"10.0.0.0/22\", \"except\": [\"10.0.2.0/24\", \"10.0.0.192/28\"]}, {\"cidr\": \"2001:db8::353/127\"}, {\"cidr\": \"::/0\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 79, \"endPort\": 83}, {\"protocol\": \"udp\", \"port\": 81}, {\"protocol\": \"tcp\", \"port\": 65535, \"endPort\": 65535}, {\"protocol\": \"tcp\", \"port\": 78, \"endPort\": 79}]}, {\"to\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"10.0.0.0/8\"]}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"tcp\"}]}, {\"cidr\": \"10.0.1.222/255.255.252.0\", \"protocol\": \"tcp\", \"port\": 82, \"endPort\": 85}, {\"to\": [{\"cidr\": \"10.0.2.0/24\"}, {\"cidr\": \"2001:db8::200/120\"}, {\"cidr\": \"10.0.0.0/25\"}, {\"cidr\": \"10.0.2.192/26\"}], \"ports\": [{}, {\"protocol\": \"tcp\", \"port\": 2}, {\"protocol\": \"any\", \"port\": 82}]}, {\"to\": [{\"cidr\": \"2001:db8::340/126\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65535}, {\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\", \"port\": 65534, \"endPort\": 65535}, {\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"10.0.1.224/27\"}, {\"cidr\": \"130.112.0.0/0.15.255.255\", \"except\": [\"130.126.68.135/18\", \"130.127.64.130/17\", \"130.114.195.186/255.255.0.0\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/121\"}], \"ports\": [{\"protocol\": \"tcp\"}]}, {\"cidr\": \"0.0.0.0/0\", \"protocol\": \"tcp\"}]}"}],"expected":{"error":"deny[2].to[0].except[0] must be contained in 192.0.2.0/24"}}, + {"name":"random #129","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.2.128/25\"}, {\"cidr\": \"2001:db8::228/125\", \"except\": [\"2001:db8::228/0125\"]}, {\"cidr\": \"234.233.242.109/32\", \"except\": []}]}], \"deny\": [{\"to\": [{\"cidr\": \"4c2d:a949:f33a:cd0d:878e:ef5:b400:0/104\"}]}, {\"ports\": [{\"port\": 445}]}, {\"cidr\": \"10.0.0.0/22\", \"except\": [\"10.0.3.0/26\", \"10.0.2.12/25\"], \"protocol\": \"udp\"}, {\"cidr\": \"10.0.3.244/31\", \"protocol\": \"udp\", \"port\": 5, \"endPort\": 7}, {\"cidr\": \"10.0.1.254/31\", \"protocol\": \"udp\", \"port\": 269}, {\"cidr\": \"2001:db8::2f3/128\", \"protocol\": \"tcp\", \"port\": 187, \"endPort\": 189}, {\"cidr\": \"10.0.1.184/31\", \"protocol\": \"tcp\", \"port\": 35}, {\"cidr\": \"10.0.2.248/30\", \"protocol\": \"udp\", \"port\": 137}, {\"cidr\": \"2001:db8::19f/128\", \"protocol\": \"any\", \"port\": 327, \"endPort\": 329}, {\"cidr\": \"10.0.1.128/30\", \"protocol\": \"tcp\", \"port\": 27}, {\"cidr\": \"10.0.3.112/30\", \"protocol\": \"any\", \"port\": 239, \"endPort\": 242}, {\"cidr\": \"10.0.2.74/32\", \"protocol\": \"any\", \"port\": 361}, {\"cidr\": \"10.0.2.65/32\", \"protocol\": \"any\", \"port\": 289}, {\"cidr\": \"10.0.2.172/32\", \"protocol\": \"any\", \"port\": 237, \"endPort\": 239}, {\"cidr\": \"10.0.0.234/31\", \"protocol\": \"udp\", \"port\": 223}, {\"cidr\": \"10.0.1.54/31\", \"protocol\": \"udp\", \"port\": 167}, {\"cidr\": \"10.0.1.244/31\", \"protocol\": \"tcp\", \"port\": 169}, {\"cidr\": \"2001:db8::d4/128\", \"protocol\": \"any\", \"port\": 283, \"endPort\": 284}, {\"cidr\": \"10.0.3.72/31\", \"protocol\": \"any\", \"port\": 315}, {\"cidr\": \"10.0.0.4/30\", \"protocol\": \"udp\", \"port\": 229}, {\"cidr\": \"10.0.0.38/31\", \"protocol\": \"tcp\", \"port\": 339}, {\"cidr\": \"10.0.0.152/30\", \"protocol\": \"tcp\", \"port\": 281, \"endPort\": 281}, {\"cidr\": \"10.0.1.14/31\", \"protocol\": \"tcp\", \"port\": 223, \"endPort\": 226}, {\"cidr\": \"10.0.1.42/31\", \"protocol\": \"udp\", \"port\": 29}, {\"cidr\": \"10.0.1.20/32\", \"protocol\": \"udp\", \"port\": 125}, {\"cidr\": \"10.0.2.119/32\", \"protocol\": \"udp\", \"port\": 51}, {\"cidr\": \"10.0.2.156/30\", \"protocol\": \"udp\", \"port\": 179}, {\"cidr\": \"2001:db8::c5/128\", \"protocol\": \"any\", \"port\": 155}, {\"cidr\": \"10.0.2.168/30\", \"protocol\": \"any\", \"port\": 5, \"endPort\": 6}, {\"cidr\": \"10.0.1.88/32\", \"protocol\": \"udp\", \"port\": 137, \"endPort\": 141}, {\"cidr\": \"10.0.3.32/30\", \"protocol\": \"tcp\", \"port\": 167}, {\"cidr\": \"2001:db8::135/128\", \"protocol\": \"udp\", \"port\": 51}, {\"cidr\": \"10.0.2.180/32\", \"protocol\": \"udp\", \"port\": 381}, {\"cidr\": \"10.0.3.98/31\", \"protocol\": \"tcp\", \"port\": 121}, {\"cidr\": \"10.0.2.216/30\", \"protocol\": \"tcp\", \"port\": 293}, {\"cidr\": \"10.0.2.108/32\", \"protocol\": \"any\", \"port\": 387}, {\"cidr\": \"2001:db8::2b5/128\", \"protocol\": \"tcp\", \"port\": 265, \"endPort\": 266}, {\"cidr\": \"2001:db8::332/128\", \"protocol\": \"any\", \"port\": 57, \"endPort\": 58}, {\"cidr\": \"10.0.3.32/30\", \"protocol\": \"udp\", \"port\": 83}, {\"cidr\": \"10.0.2.250/31\", \"protocol\": \"any\", \"port\": 303, \"endPort\": 305}, {\"cidr\": \"10.0.3.66/32\", \"protocol\": \"any\", \"port\": 177, \"endPort\": 181}, {\"cidr\": \"10.0.2.244/30\", \"protocol\": \"tcp\", \"port\": 339}, {\"cidr\": \"10.0.3.60/30\", \"protocol\": \"any\", \"port\": 215, \"endPort\": 216}, {\"cidr\": \"10.0.3.41/32\", \"protocol\": \"tcp\", \"port\": 57, \"endPort\": 59}, {\"cidr\": \"10.0.2.151/32\", \"protocol\": \"any\", \"port\": 207}, {\"cidr\": \"10.0.0.144/32\", \"protocol\": \"udp\", \"port\": 183, \"endPort\": 185}, {\"cidr\": \"10.0.1.144/31\", \"protocol\": \"any\", \"port\": 197, \"endPort\": 198}, {\"cidr\": \"10.0.2.143/32\", \"protocol\": \"tcp\", \"port\": 213, \"endPort\": 217}, {\"cidr\": \"10.0.0.126/32\", \"protocol\": \"tcp\", \"port\": 305}, {\"cidr\": \"2001:db8::368/128\", \"protocol\": \"tcp\", \"port\": 331, \"endPort\": 333}, {\"cidr\": \"2001:db8::33/128\", \"protocol\": \"any\", \"port\": 171}, {\"cidr\": \"2001:db8::283/128\", \"protocol\": \"any\", \"port\": 355}, {\"cidr\": \"10.0.2.150/31\", \"protocol\": \"tcp\", \"port\": 329}, {\"cidr\": \"10.0.1.178/31\", \"protocol\": \"any\", \"port\": 375, \"endPort\": 378}, {\"cidr\": \"2001:db8::2f5/128\", \"protocol\": \"udp\", \"port\": 311, \"endPort\": 313}, {\"cidr\": \"2001:db8::291/128\", \"protocol\": \"any\", \"port\": 65, \"endPort\": 66}, {\"cidr\": \"10.0.1.144/30\", \"protocol\": \"tcp\", \"port\": 229}, {\"cidr\": \"10.0.2.228/32\", \"protocol\": \"udp\", \"port\": 43, \"endPort\": 43}, {\"cidr\": \"10.0.2.120/30\", \"protocol\": \"tcp\", \"port\": 225}, {\"cidr\": \"2001:db8::3b/128\", \"protocol\": \"any\", \"port\": 329, \"endPort\": 330}, {\"cidr\": \"10.0.0.252/30\", \"protocol\": \"udp\", \"port\": 95}, {\"cidr\": \"2001:db8::e6/128\", \"protocol\": \"tcp\", \"port\": 253}, {\"cidr\": \"10.0.2.160/30\", \"protocol\": \"any\", \"port\": 1}, {\"cidr\": \"10.0.2.106/31\", \"protocol\": \"any\", \"port\": 297}, {\"cidr\": \"2001:db8::1cf/128\", \"protocol\": \"any\", \"port\": 171}, {\"cidr\": \"10.0.0.244/31\", \"protocol\": \"tcp\", \"port\": 305}, {\"cidr\": \"2001:db8::8/128\", \"protocol\": \"any\", \"port\": 343}, {\"cidr\": \"10.0.3.112/31\", \"protocol\": \"any\", \"port\": 133}, {\"cidr\": \"2001:db8::1b8/128\", \"protocol\": \"any\", \"port\": 291}, {\"cidr\": \"10.0.2.198/32\", \"protocol\": \"tcp\", \"port\": 275, \"endPort\": 279}, {\"cidr\": \"10.0.1.180/31\", \"protocol\": \"tcp\", \"port\": 43, \"endPort\": 46}, {\"cidr\": \"10.0.0.164/30\", \"protocol\": \"tcp\", \"port\": 265}, {\"cidr\": \"2001:db8::3fe/128\", \"protocol\": \"any\", \"port\": 143}, {\"cidr\": \"2001:db8::2fb/128\", \"protocol\": \"udp\", \"port\": 41}, {\"cidr\": \"10.0.1.134/32\", \"protocol\": \"any\", \"port\": 107}, {\"cidr\": \"10.0.1.128/31\", \"protocol\": \"tcp\", \"port\": 259}, {\"cidr\": \"2001:db8::2ac/128\", \"protocol\": \"tcp\", \"port\": 71}, {\"cidr\": \"2001:db8::295/128\", \"protocol\": \"udp\", \"port\": 151, \"endPort\": 153}, {\"cidr\": \"10.0.1.176/31\", \"protocol\": \"any\", \"port\": 331}, {\"cidr\": \"10.0.2.144/31\", \"protocol\": \"udp\", \"port\": 95, \"endPort\": 96}, {\"cidr\": \"2001:db8::399/128\", \"protocol\": \"tcp\", \"port\": 357}, {\"cidr\": \"10.0.1.154/32\", \"protocol\": \"udp\", \"port\": 73}, {\"cidr\": \"10.0.3.153/32\", \"protocol\": \"any\", \"port\": 47, \"endPort\": 48}, {\"cidr\": \"2001:db8::8c/128\", \"protocol\": \"any\", \"port\": 91}, {\"cidr\": \"10.0.3.60/30\", \"protocol\": \"tcp\", \"port\": 225}, {\"cidr\": \"10.0.1.46/31\", \"protocol\": \"tcp\", \"port\": 5, \"endPort\": 8}, {\"cidr\": \"10.0.3.204/30\", \"protocol\": \"udp\", \"port\": 199, \"endPort\": 203}, {\"cidr\": \"10.0.2.52/32\", \"protocol\": \"any\", \"port\": 325}, {\"cidr\": \"10.0.1.104/31\", \"protocol\": \"tcp\", \"port\": 389}, {\"cidr\": \"10.0.1.252/30\", \"protocol\": \"udp\", \"port\": 109, \"endPort\": 112}, {\"cidr\": \"10.0.0.6/31\", \"protocol\": \"udp\", \"port\": 265, \"endPort\": 266}, {\"cidr\": \"10.0.2.154/31\", \"protocol\": \"any\", \"port\": 389}, {\"cidr\": \"10.0.2.146/31\", \"protocol\": \"any\", \"port\": 379}, {\"cidr\": \"10.0.2.42/31\", \"protocol\": \"udp\", \"port\": 1}, {\"cidr\": \"2001:db8::e3/128\", \"protocol\": \"udp\", \"port\": 75}, {\"cidr\": \"10.0.0.2/31\", \"protocol\": \"tcp\", \"port\": 295}, {\"cidr\": \"2001:db8::214/128\", \"protocol\": \"any\", \"port\": 71}, {\"cidr\": \"10.0.0.136/30\", \"protocol\": \"udp\", \"port\": 45}, {\"cidr\": \"10.0.1.12/30\", \"protocol\": \"tcp\", \"port\": 345, \"endPort\": 345}, {\"cidr\": \"2001:db8::7b/128\", \"protocol\": \"tcp\", \"port\": 231}, {\"cidr\": \"2001:db8::38a/128\", \"protocol\": \"udp\", \"port\": 15, \"endPort\": 16}, {\"cidr\": \"10.0.3.0/31\", \"protocol\": \"udp\", \"port\": 293, \"endPort\": 294}, {\"cidr\": \"10.0.3.173/32\", \"protocol\": \"any\", \"port\": 87, \"endPort\": 89}, {\"cidr\": \"10.0.1.12/30\", \"protocol\": \"any\", \"port\": 169}, {\"cidr\": \"10.0.1.220/32\", \"protocol\": \"any\", \"port\": 159, \"endPort\": 162}, {\"cidr\": \"10.0.2.60/30\", \"protocol\": \"tcp\", \"port\": 275, \"endPort\": 276}, {\"cidr\": \"10.0.1.12/31\", \"protocol\": \"udp\", \"port\": 41, \"endPort\": 41}, {\"cidr\": \"10.0.2.83/32\", \"protocol\": \"udp\", \"port\": 53}, {\"cidr\": \"10.0.0.133/32\", \"protocol\": \"tcp\", \"port\": 127, \"endPort\": 131}, {\"cidr\": \"2001:db8::1f1/128\", \"protocol\": \"any\", \"port\": 111, \"endPort\": 113}, {\"cidr\": \"10.0.1.52/30\", \"protocol\": \"any\", \"port\": 377}, {\"cidr\": \"2001:db8::118/128\", \"protocol\": \"tcp\", \"port\": 335}, {\"cidr\": \"10.0.0.124/30\", \"protocol\": \"udp\", \"port\": 325}, {\"cidr\": \"2001:db8::1e7/128\", \"protocol\": \"udp\", \"port\": 5, \"endPort\": 8}, {\"cidr\": \"10.0.3.120/32\", \"protocol\": \"tcp\", \"port\": 359}, {\"cidr\": \"10.0.2.44/30\", \"protocol\": \"udp\", \"port\": 239}, {\"cidr\": \"2001:db8::12b/128\", \"protocol\": \"tcp\", \"port\": 1, \"endPort\": 5}, {\"cidr\": \"10.0.2.64/31\", \"protocol\": \"tcp\", \"port\": 195}, {\"cidr\": \"10.0.0.20/30\", \"protocol\": \"any\", \"port\": 377}, {\"cidr\": \"2001:db8::3d6/128\", \"protocol\": \"any\", \"port\": 347}, {\"cidr\": \"2001:db8::34a/128\", \"protocol\": \"udp\", \"port\": 59}, {\"cidr\": \"2001:db8::242/128\", \"protocol\": \"any\", \"port\": 145}, {\"cidr\": \"2001:db8::b5/128\", \"protocol\": \"udp\", \"port\": 347}, {\"cidr\": \"10.0.3.124/30\", \"protocol\": \"tcp\", \"port\": 123}, {\"cidr\": \"10.0.2.180/31\", \"protocol\": \"tcp\", \"port\": 297}, {\"cidr\": \"10.0.0.56/32\", \"protocol\": \"udp\", \"port\": 247}, {\"cidr\": \"10.0.1.152/30\", \"protocol\": \"tcp\", \"port\": 367}, {\"cidr\": \"10.0.1.52/32\", \"protocol\": \"any\", \"port\": 121}, {\"cidr\": \"2001:db8::20c/128\", \"protocol\": \"any\", \"port\": 103}, {\"cidr\": \"2001:db8::115/128\", \"protocol\": \"udp\", \"port\": 341, \"endPort\": 342}, {\"cidr\": \"10.0.1.84/30\", \"protocol\": \"udp\", \"port\": 27, \"endPort\": 31}, {\"cidr\": \"10.0.3.94/32\", \"protocol\": \"any\", \"port\": 303}, {\"cidr\": \"10.0.0.60/30\", \"protocol\": \"udp\", \"port\": 75}, {\"cidr\": \"2001:db8::e1/128\", \"protocol\": \"tcp\", \"port\": 389}, {\"cidr\": \"2001:db8::1ad/128\", \"protocol\": \"any\", \"port\": 161}, {\"cidr\": \"10.0.1.99/32\", \"protocol\": \"tcp\", \"port\": 87}, {\"cidr\": \"10.0.1.124/30\", \"protocol\": \"udp\", \"port\": 227}, {\"cidr\": \"10.0.1.28/30\", \"protocol\": \"udp\", \"port\": 203, \"endPort\": 207}, {\"cidr\": \"10.0.3.20/30\", \"protocol\": \"any\", \"port\": 383}, {\"cidr\": \"2001:db8::2db/128\", \"protocol\": \"tcp\", \"port\": 15}, {\"cidr\": \"2001:db8::3d3/128\", \"protocol\": \"any\", \"port\": 75, \"endPort\": 76}, {\"cidr\": \"2001:db8::e1/128\", \"protocol\": \"tcp\", \"port\": 371, \"endPort\": 375}, {\"cidr\": \"10.0.0.186/32\", \"protocol\": \"any\", \"port\": 211, \"endPort\": 214}, {\"cidr\": \"10.0.3.157/32\", \"protocol\": \"udp\", \"port\": 243, \"endPort\": 244}, {\"cidr\": \"2001:db8::152/128\", \"protocol\": \"tcp\", \"port\": 221, \"endPort\": 225}, {\"cidr\": \"2001:db8::203/128\", \"protocol\": \"any\", \"port\": 359}, {\"cidr\": \"10.0.2.109/32\", \"protocol\": \"tcp\", \"port\": 7}, {\"cidr\": \"10.0.1.164/31\", \"protocol\": \"tcp\", \"port\": 51}, {\"cidr\": \"10.0.3.156/32\", \"protocol\": \"tcp\", \"port\": 123}, {\"cidr\": \"10.0.3.228/30\", \"protocol\": \"udp\", \"port\": 321}, {\"cidr\": \"2001:db8::4/128\", \"protocol\": \"tcp\", \"port\": 161}, {\"cidr\": \"10.0.3.156/30\", \"protocol\": \"any\", \"port\": 243}, {\"cidr\": \"10.0.3.162/32\", \"protocol\": \"udp\", \"port\": 395, \"endPort\": 398}, {\"cidr\": \"10.0.1.28/30\", \"protocol\": \"tcp\", \"port\": 375}, {\"cidr\": \"10.0.3.50/32\", \"protocol\": \"any\", \"port\": 223, \"endPort\": 226}, {\"cidr\": \"10.0.2.171/32\", \"protocol\": \"tcp\", \"port\": 145}, {\"cidr\": \"10.0.2.42/32\", \"protocol\": \"tcp\", \"port\": 11, \"endPort\": 15}, {\"cidr\": \"10.0.1.200/32\", \"protocol\": \"udp\", \"port\": 73}, {\"cidr\": \"10.0.0.211/32\", \"protocol\": \"tcp\", \"port\": 345}, {\"cidr\": \"10.0.1.212/30\", \"protocol\": \"udp\", \"port\": 357}, {\"cidr\": \"10.0.3.214/31\", \"protocol\": \"tcp\", \"port\": 141, \"endPort\": 145}, {\"cidr\": \"10.0.3.242/31\", \"protocol\": \"any\", \"port\": 41}, {\"cidr\": \"2001:db8::361/128\", \"protocol\": \"tcp\", \"port\": 5, \"endPort\": 9}, {\"cidr\": \"10.0.2.70/32\", \"protocol\": \"tcp\", \"port\": 155, \"endPort\": 156}, {\"cidr\": \"2001:db8::285/128\", \"protocol\": \"udp\", \"port\": 341, \"endPort\": 343}, {\"cidr\": \"2001:db8::1f2/128\", \"protocol\": \"any\", \"port\": 131}, {\"cidr\": \"10.0.3.184/30\", \"protocol\": \"udp\", \"port\": 393}, {\"cidr\": \"2001:db8::25c/128\", \"protocol\": \"any\", \"port\": 33}, {\"cidr\": \"2001:db8::7c/128\", \"protocol\": \"udp\", \"port\": 135}, {\"cidr\": \"2001:db8::80/128\", \"protocol\": \"udp\", \"port\": 391}, {\"cidr\": \"10.0.0.53/32\", \"protocol\": \"tcp\", \"port\": 351}, {\"cidr\": \"10.0.2.20/30\", \"protocol\": \"tcp\", \"port\": 77}, {\"cidr\": \"10.0.3.212/31\", \"protocol\": \"any\", \"port\": 379}, {\"cidr\": \"10.0.1.210/31\", \"protocol\": \"udp\", \"port\": 141}, {\"cidr\": \"2001:db8::3a2/128\", \"protocol\": \"udp\", \"port\": 365}, {\"cidr\": \"10.0.3.124/30\", \"protocol\": \"any\", \"port\": 43}, {\"cidr\": \"2001:db8::125/128\", \"protocol\": \"udp\", \"port\": 315, \"endPort\": 319}, {\"cidr\": \"10.0.1.44/30\", \"protocol\": \"any\", \"port\": 159}, {\"cidr\": \"10.0.1.224/32\", \"protocol\": \"any\", \"port\": 379}, {\"cidr\": \"10.0.0.196/31\", \"protocol\": \"tcp\", \"port\": 113, \"endPort\": 115}, {\"cidr\": \"2001:db8::d7/128\", \"protocol\": \"any\", \"port\": 21}, {\"cidr\": \"10.0.0.136/30\", \"protocol\": \"any\", \"port\": 335, \"endPort\": 339}, {\"cidr\": \"10.0.1.224/30\", \"protocol\": \"tcp\", \"port\": 171, \"endPort\": 173}, {\"cidr\": \"10.0.3.13/32\", \"protocol\": \"udp\", \"port\": 203}, {\"cidr\": \"2001:db8::238/128\", \"protocol\": \"udp\", \"port\": 311, \"endPort\": 315}, {\"cidr\": \"2001:db8::129/128\", \"protocol\": \"any\", \"port\": 391}, {\"cidr\": \"10.0.1.72/31\", \"protocol\": \"udp\", \"port\": 105, \"endPort\": 106}, {\"cidr\": \"10.0.0.123/32\", \"protocol\": \"any\", \"port\": 369}, {\"cidr\": \"10.0.1.252/31\", \"protocol\": \"any\", \"port\": 377}, {\"cidr\": \"10.0.2.224/31\", \"protocol\": \"any\", \"port\": 49, \"endPort\": 49}, {\"cidr\": \"10.0.0.114/31\", \"protocol\": \"tcp\", \"port\": 197}, {\"cidr\": \"10.0.0.104/30\", \"protocol\": \"tcp\", \"port\": 215}, {\"cidr\": \"10.0.3.28/30\", \"protocol\": \"udp\", \"port\": 103}, {\"cidr\": \"2001:db8::3f8/128\", \"protocol\": \"udp\", \"port\": 69}, {\"cidr\": \"10.0.3.233/32\", \"protocol\": \"any\", \"port\": 311}, {\"cidr\": \"2001:db8::1d8/128\", \"protocol\": \"any\", \"port\": 133}, {\"cidr\": \"2001:db8::3e7/128\", \"protocol\": \"tcp\", \"port\": 201, \"endPort\": 201}, {\"cidr\": \"10.0.2.122/32\", \"protocol\": \"any\", \"port\": 93}, {\"cidr\": \"10.0.2.156/30\", \"protocol\": \"any\", \"port\": 91}, {\"cidr\": \"2001:db8::249/128\", \"protocol\": \"udp\", \"port\": 9}, {\"cidr\": \"2001:db8::36c/128\", \"protocol\": \"tcp\", \"port\": 283, \"endPort\": 286}, {\"cidr\": \"10.0.1.148/31\", \"protocol\": \"udp\", \"port\": 119, \"endPort\": 122}, {\"cidr\": \"10.0.1.20/30\", \"protocol\": \"any\", \"port\": 169, \"endPort\": 172}, {\"cidr\": \"10.0.2.156/31\", \"protocol\": \"any\", \"port\": 163}, {\"cidr\": \"10.0.3.59/32\", \"protocol\": \"udp\", \"port\": 121, \"endPort\": 125}, {\"cidr\": \"10.0.2.88/30\", \"protocol\": \"tcp\", \"port\": 237}, {\"cidr\": \"2001:db8::3d/128\", \"protocol\": \"tcp\", \"port\": 273}, {\"cidr\": \"10.0.3.234/31\", \"protocol\": \"udp\", \"port\": 83}, {\"cidr\": \"10.0.2.82/31\", \"protocol\": \"tcp\", \"port\": 151}, {\"cidr\": \"10.0.2.251/32\", \"protocol\": \"tcp\", \"port\": 187}, {\"cidr\": \"10.0.0.26/31\", \"protocol\": \"any\", \"port\": 49}, {\"cidr\": \"10.0.1.212/31\", \"protocol\": \"tcp\", \"port\": 311}, {\"cidr\": \"10.0.3.179/32\", \"protocol\": \"udp\", \"port\": 101}, {\"cidr\": \"10.0.1.235/32\", \"protocol\": \"udp\", \"port\": 139}, {\"cidr\": \"10.0.2.66/31\", \"protocol\": \"any\", \"port\": 55, \"endPort\": 57}, {\"cidr\": \"10.0.3.228/32\", \"protocol\": \"any\", \"port\": 95}, {\"cidr\": \"10.0.3.176/30\", \"protocol\": \"udp\", \"port\": 137, \"endPort\": 140}, {\"cidr\": \"2001:db8::2d7/128\", \"protocol\": \"tcp\", \"port\": 23}, {\"cidr\": \"10.0.0.142/31\", \"protocol\": \"tcp\", \"port\": 345}, {\"cidr\": \"10.0.2.78/31\", \"protocol\": \"udp\", \"port\": 335}, {\"cidr\": \"10.0.3.49/32\", \"protocol\": \"udp\", \"port\": 323}, {\"cidr\": \"10.0.2.76/30\", \"protocol\": \"tcp\", \"port\": 341}, {\"cidr\": \"2001:db8::9a/128\", \"protocol\": \"udp\", \"port\": 101}, {\"cidr\": \"10.0.3.104/30\", \"protocol\": \"udp\", \"port\": 27}, {\"cidr\": \"10.0.1.92/31\", \"protocol\": \"tcp\", \"port\": 331}, {\"cidr\": \"10.0.2.128/30\", \"protocol\": \"tcp\", \"port\": 213, \"endPort\": 215}, {\"cidr\": \"2001:db8::25/128\", \"protocol\": \"udp\", \"port\": 85}, {\"cidr\": \"2001:db8::58/128\", \"protocol\": \"any\", \"port\": 351, \"endPort\": 351}, {\"cidr\": \"10.0.2.232/31\", \"protocol\": \"udp\", \"port\": 185}, {\"cidr\": \"10.0.2.238/31\", \"protocol\": \"tcp\", \"port\": 307}, {\"cidr\": \"10.0.1.228/31\", \"protocol\": \"udp\", \"port\": 151}, {\"cidr\": \"2001:db8::167/128\", \"protocol\": \"any\", \"port\": 353}, {\"cidr\": \"10.0.2.216/31\", \"protocol\": \"tcp\", \"port\": 23, \"endPort\": 26}, {\"cidr\": \"10.0.2.12/30\", \"protocol\": \"any\", \"port\": 79, \"endPort\": 82}, {\"cidr\": \"10.0.1.248/32\", \"protocol\": \"any\", \"port\": 99, \"endPort\": 99}, {\"cidr\": \"10.0.0.21/32\", \"protocol\": \"any\", \"port\": 313}, {\"cidr\": \"10.0.0.74/31\", \"protocol\": \"udp\", \"port\": 91, \"endPort\": 91}, {\"cidr\": \"2001:db8::372/128\", \"protocol\": \"tcp\", \"port\": 95}, {\"cidr\": \"2001:db8::18b/128\", \"protocol\": \"udp\", \"port\": 145}, {\"cidr\": \"10.0.2.94/31\", \"protocol\": \"any\", \"port\": 341, \"endPort\": 342}, {\"cidr\": \"10.0.3.52/30\", \"protocol\": \"udp\", \"port\": 285, \"endPort\": 285}, {\"cidr\": \"10.0.0.150/32\", \"protocol\": \"tcp\", \"port\": 15, \"endPort\": 15}, {\"cidr\": \"10.0.2.248/31\", \"protocol\": \"udp\", \"port\": 197}, {\"cidr\": \"10.0.2.56/30\", \"protocol\": \"any\", \"port\": 387}, {\"cidr\": \"10.0.2.39/32\", \"protocol\": \"tcp\", \"port\": 221, \"endPort\": 221}, {\"cidr\": \"10.0.3.180/30\", \"protocol\": \"tcp\", \"port\": 125}, {\"cidr\": \"10.0.0.66/31\", \"protocol\": \"tcp\", \"port\": 121}, {\"cidr\": \"10.0.0.212/30\", \"protocol\": \"any\", \"port\": 349}, {\"cidr\": \"2001:db8::3fe/128\", \"protocol\": \"udp\", \"port\": 31}, {\"cidr\": \"2001:db8::38/128\", \"protocol\": \"udp\", \"port\": 83, \"endPort\": 87}, {\"cidr\": \"10.0.0.196/31\", \"protocol\": \"tcp\", \"port\": 83, \"endPort\": 87}, {\"cidr\": \"10.0.1.80/32\", \"protocol\": \"any\", \"port\": 285, \"endPort\": 286}, {\"cidr\": \"2001:db8::293/128\", \"protocol\": \"tcp\", \"port\": 179}, {\"cidr\": \"10.0.2.104/30\", \"protocol\": \"udp\", \"port\": 341, \"endPort\": 342}, {\"cidr\": \"2001:db8::126/128\", \"protocol\": \"tcp\", \"port\": 123}, {\"cidr\": \"10.0.3.212/30\", \"protocol\": \"any\", \"port\": 361, \"endPort\": 363}, {\"cidr\": \"2001:db8::12/128\", \"protocol\": \"any\", \"port\": 11, \"endPort\": 11}, {\"cidr\": \"10.0.1.240/31\", \"protocol\": \"any\", \"port\": 119, \"endPort\": 121}, {\"cidr\": \"10.0.0.118/31\", \"protocol\": \"tcp\", \"port\": 227}, {\"cidr\": \"2001:db8::19c/128\", \"protocol\": \"udp\", \"port\": 31, \"endPort\": 33}, {\"cidr\": \"10.0.1.236/32\", \"protocol\": \"any\", \"port\": 47}, {\"cidr\": \"2001:db8::10b/128\", \"protocol\": \"udp\", \"port\": 37}, {\"cidr\": \"10.0.1.64/31\", \"protocol\": \"any\", \"port\": 87}, {\"cidr\": \"10.0.0.172/30\", \"protocol\": \"any\", \"port\": 277}, {\"cidr\": \"2001:db8::9/128\", \"protocol\": \"udp\", \"port\": 7, \"endPort\": 9}, {\"cidr\": \"2001:db8::67/128\", \"protocol\": \"any\", \"port\": 369, \"endPort\": 373}, {\"cidr\": \"2001:db8::24f/128\", \"protocol\": \"tcp\", \"port\": 143}, {\"cidr\": \"10.0.3.68/30\", \"protocol\": \"tcp\", \"port\": 277, \"endPort\": 281}, {\"cidr\": \"2001:db8::2b/128\", \"protocol\": \"tcp\", \"port\": 67}, {\"cidr\": \"10.0.1.98/31\", \"protocol\": \"any\", \"port\": 397}, {\"cidr\": \"10.0.3.208/30\", \"protocol\": \"any\", \"port\": 161, \"endPort\": 162}, {\"cidr\": \"10.0.3.39/32\", \"protocol\": \"udp\", \"port\": 227}, {\"cidr\": \"2001:db8::28d/128\", \"protocol\": \"tcp\", \"port\": 143}, {\"cidr\": \"2001:db8::224/128\", \"protocol\": \"any\", \"port\": 211}, {\"cidr\": \"10.0.3.217/32\", \"protocol\": \"udp\", \"port\": 261, \"endPort\": 262}, {\"cidr\": \"10.0.2.8/31\", \"protocol\": \"any\", \"port\": 351}, {\"cidr\": \"2001:db8::19e/128\", \"protocol\": \"udp\", \"port\": 351, \"endPort\": 354}, {\"cidr\": \"2001:db8::3ed/128\", \"protocol\": \"udp\", \"port\": 161}, {\"cidr\": \"2001:db8::39f/128\", \"protocol\": \"tcp\", \"port\": 389, \"endPort\": 392}, {\"cidr\": \"10.0.2.236/30\", \"protocol\": \"udp\", \"port\": 235}, {\"cidr\": \"10.0.0.74/31\", \"protocol\": \"any\", \"port\": 69}, {\"cidr\": \"2001:db8::3a4/128\", \"protocol\": \"udp\", \"port\": 239}, {\"cidr\": \"10.0.0.118/32\", \"protocol\": \"udp\", \"port\": 167}, {\"cidr\": \"2001:db8::38d/128\", \"protocol\": \"udp\", \"port\": 361}, {\"cidr\": \"10.0.2.202/32\", \"protocol\": \"any\", \"port\": 3}, {\"cidr\": \"2001:db8::d1/128\", \"protocol\": \"tcp\", \"port\": 229}, {\"cidr\": \"10.0.3.112/30\", \"protocol\": \"tcp\", \"port\": 219}, {\"cidr\": \"10.0.0.124/30\", \"protocol\": \"any\", \"port\": 115}, {\"cidr\": \"10.0.3.252/30\", \"protocol\": \"any\", \"port\": 11, \"endPort\": 11}, {\"cidr\": \"2001:db8::219/128\", \"protocol\": \"tcp\", \"port\": 351, \"endPort\": 355}, {\"cidr\": \"10.0.1.120/30\", \"protocol\": \"any\", \"port\": 379}]}"}],"expected":{"error":"deny emits at most 256 native rules"}}, + {"name":"random #130","input":[{"text":"{\n\"allow\": [\n{\n\"cidr\": \"::/32\", \n\"protocol\": \"tcp\", \n\"port\": 9999999999999999999999999999999999999999999999999999999999999999\n}\n], \n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::d0/124\"\n}\n]\n}, \n{\n\"cidr\": \"10.0.0.161/27\", \n\"except\": [], \n\"protocol\": \"udp\", \n\"port\": 65534\n}, \n{\n\"cidr\": \"10.0.3.144/28\", \n\"except\": [\n\"10.0.3.155/30\"\n], \n\"protocol\": \"udp\"\n}, \n{\n\"cidr\": \"2001:db8::160/000125\"\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65532\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 3\n}, \n{\n\"port\": 80\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.0/26\"\n}, \n{\n\"cidr\": \"6000::/3\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"any\", \n\"port\": 11, \n\"endPort\": 56135\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.18/31\"\n}, \n{\n\"cidr\": \"10.0.1.64/29\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 65532\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"227.154.134.106/32\"\n}, \n{\n\"cidr\": \"6705:926e:d13a:c278:415e:40e6:bd21:4711\", \n\"except\": []\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 2, \n\"endPort\": 5\n}\n]\n}, \n{\n\"cidr\": \"10.0.2.64/26\", \n\"protocol\": \"any\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.36/031\"\n}, \n{\n\"cidr\": \"10.0.2.184/31\"\n}, \n{\n\"cidr\": \"10.0.0.56/0029\"\n}, \n{\n\"cidr\": \"2001:db8::80/121\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.128/28\"\n}, \n{\n\"cidr\": \"::/64\", \n\"except\": [\n\"::2D92:ADEA:4EC1:AE7A/0070\", \n\"::E000:0:0:0/67\"\n]\n}\n]\n}, \n{\n\"cidr\": \"95bf:f070:3ea0:63b9:af10:8188:1ea3:2427\", \n\"protocol\": \"any\", \n\"port\": 1\n}, \n{\n\"cidr\": \"216.47.0.0/16\", \n\"except\": [], \n\"protocol\": \"udp\", \n\"port\": 80, \n\"endPort\": 86\n}, \n{\n\"cidr\": \"2001:db8::145/120\", \n\"protocol\": \"udp\", \n\"port\": 65534, \n\"endPort\": 65535\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.141/24\"\n}, \n{\n\"cidr\": \"2001:db8::100/120\"\n}, \n{\n\"cidr\": \"222.27.212.0/24\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"any\", \n\"port\": 65532, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 79, \n\"endPort\": 80\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/255.255.252.0\", \n\"except\": [\n\"10.0.0.0/22\", \n\"10.0.0.223/0.0.0.63\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 65535, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"icmp\", \n\"port\": 5\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"c000:0000:0000:0000:0000:0000:0000:0000/3\"\n}, \n{\n\"cidr\": \"442d:98cc:b895:c2a:a934:6fca:aab3:ba37/0\"\n}, \n{\n\"cidr\": \"10.0.0.76/30\", \n\"except\": [\n\"10.0.0.79/255.255.255.255\", \n\"10.0.0.77/31\"\n]\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::2a0/123\", \n\"except\": [\n\"2001:0db8:0000:0000:0000:0000:0000:02b0/125\", \n\"2001:db8::2b4/127\", \n\"2001:db8::2be/125\"\n]\n}, \n{\n\"cidr\": \"10.0.0.0/23\"\n}, \n{\n\"cidr\": \"2B61:597F:9846:5528:B88A:E217:6600:0/104\", \n\"except\": [\n\"2b61:597f:9846:5528:b88a:e217:66f0:0/109\"\n]\n}, \n{\n\"cidr\": \"10.0.2.0/23\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 65535, \n\"endPort\": 65535\n}\n]\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:00e0/124\", \n\"protocol\": \"tcp\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.6/255.255.255.192\"\n}, \n{\n\"cidr\": \"69.0.0.0/8\"\n}, \n{\n\"cidr\": \"10.0.0.0/22\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 3, \n\"endPort\": 9\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 7\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 5\n}, \n{\n\"protocol\": \"any\", \n\"port\": 440\n}\n]\n}, \n{\n\"cidr\": \"b:3838:f9ee:49f4:d149:b0e:5847:f4fe\", \n\"protocol\": \"tcp\", \n\"port\": 65531, \n\"endPort\": 65533\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::300/120\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"tcp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/24\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 65534\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"64.44.20.136\", \n\"except\": [\n\"64.44.20.136\"\n]\n}, \n{\n\"cidr\": \"10.0.2.64/0.0.0.31\", \n\"except\": [\n\"10.0.2.64/29\"\n]\n}\n]\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65533\n}\n]\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 10, \n\"endPort\": 14\n}, \n{\n\"protocol\": \"any\", \n\"port\": 65534, \n\"endPort\": 65535\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/22\"\n}, \n{\n\"cidr\": \"8000::/1\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 82, \n\"endPort\": 84\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 4, \n\"endPort\": 9\n}\n]\n}, \n{\n\"cidr\": \"2001:db8::309/120\", \n\"protocol\": \"tcp\", \n\"port\": 441\n}, \n{\n\"cidr\": \"10.0.1.146/24\", \n\"protocol\": \"tcp\", \n\"port\": 65534, \n\"endPort\": 65535\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::3e0/124\"\n}, \n{\n\"cidr\": \"f8b5:723:4796:ed65:cedf:2eab:6e26:a125/64\", \n\"except\": [\n\"f8b5:0723:4796:ed65:0000:0000:0.0.0.0/65\", \n\"f8b5:0723:4796:ed65:0000:0000:0.0.0.0/064\"\n]\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.3.32/123\", \n\"except\": [\n\"2001:db8::330/124\"\n]\n}, \n{\n\"cidr\": \"0.0.0.0/0\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 441, \n\"endPort\": 445\n}, \n{\n\"protocol\": \"any\", \n\"port\": 65535\n}, \n{\n\"protocol\": \"any\", \n\"port\": 2, \n\"endPort\": 7\n}, \n{\n\"protocol\": \"any\", \n\"port\": 65530\n}\n]\n}, \n{\n\"cidr\": \"2001:DB8::/120\", \n\"protocol\": \"icmp\"\n}, \n{\n\"cidr\": \"240.0.0.0/4\", \n\"except\": [\n\"241.169.26.67/5\"\n], \n\"protocol\": \"tcp\"\n}, \n{\n\"to\": \"192.0.2.0/24\", \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 78, \n\"endPort\": 37796\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65530\n}, \n{\n\"protocol\": \"any\", \n\"port\": 443\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"149.193.0.0/16\"\n}, \n{\n\"cidr\": \"e5a4:b32f:0000:0000:0000:0000:0.0.0.0/32\"\n}, \n{\n\"cidr\": \"d4fe:b275::/32\", \n\"except\": [\n\"d4fe:b275:3000:0000:0000:0000:0.0.0.0/038\", \n\"d4fe:b275:b000::/36\"\n]\n}, \n{\n\"cidr\": \"2001:db8::300/121\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"192.0.0.0/2\"\n}, \n{\n\"cidr\": \"10.0.0.165/32\"\n}, \n{\n\"cidr\": \"2001:db8::17c/123\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.50/30\"\n}, \n{\n\"cidr\": \"::ffff:247.203.249.0/120\", \n\"except\": [\n\"::ffff:247.203.249.128/121\", \n\"0000:0000:0000:0000:0000:ffff:247.203.249.221/124\"\n]\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01cc/124\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 8, \n\"endPort\": 14\n}, \n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"any\", \n\"port\": 5, \n\"endPort\": 34444\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65534, \n\"endPort\": 65534\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"136.0.0.0/8\"\n}, \n{\n\"cidr\": \"10.0.3.224/255.255.255.224\"\n}, \n{\n\"cidr\": \"10.0.3.0/24\"\n}, \n{\n\"cidr\": \"10.0.3.0/24\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 80, \n\"endPort\": 83\n}, \n{\n\"protocol\": \"udp\"\n}\n]\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.0/118\"\n}, \n{\n\"cidr\": \"e6be:5c8a:6310:f068:15b4:0f5d:166a:04cb/120\", \n\"except\": [\n\"e6be:5c8a:6310:f068:15b4:0f5d:166a:0440/124\"\n], \n\"protocol\": \"tcp\", \n\"port\": 65532\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::/119\"\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/118\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 440\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 445\n}, \n{\n\"protocol\": \"any\", \n\"port\": 65530\n}, \n{\n\"protocol\": \"tcp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::c0/123\"\n}, \n{\n\"cidr\": \"10.0.2.27/24\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 6\n}, \n{\n\"protocol\": \"any\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"20.24.61.91/1\"\n}, \n{\n\"cidr\": \"168.218.114.146/2\"\n}, \n{\n\"cidr\": \"10.0.0.64/26\"\n}, \n{\n\"cidr\": \"2001:db8::1b4/126\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.192/26\"\n}, \n{\n\"cidr\": \"::FFFF:215.0.0.0/104\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.160/255.255.255.248\"\n}, \n{\n\"cidr\": \"83.54.227.214/31\", \n\"except\": [\n\"83.54.227.214/31\"\n]\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.1.14/123\", \n\"except\": []\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 441\n}\n]\n}, \n{\n\"cidr\": \"10.0.3.68/31\", \n\"protocol\": \"udp\", \n\"port\": 79\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.128/25\"\n}, \n{\n\"cidr\": \"10.0.3.199/31\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 78\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"cidr\": \"2001:db8::200/119\", \n\"protocol\": \"udp\", \n\"port\": 7\n}, \n{\n\"cidr\": \"212.238.161.60/32\", \n\"protocol\": \"icmp\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::2ff/125\"\n}, \n{\n\"cidr\": \"2001:db8::/118\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 81, \n\"endPort\": 87\n}, \n{\n\"protocol\": \"any\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.54/24\"\n}, \n{\n\"cidr\": \"10.0.3.128/25\"\n}, \n{\n\"cidr\": \"2001:db8::380/119\", \n\"except\": [\n\"2001:db8::356/127\", \n\"2001:DB8::3B6/124\"\n]\n}\n], \n\"ports\": [\n{\n\"port\": 65532\n}, \n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"cidr\": \"2001:DB8::/000118\", \n\"protocol\": \"udp\", \n\"port\": 7\n}, \n{\n\"to\": [\n{\n\"cidr\": \"200.39.243.117/192.0.0.0\", \n\"except\": [\n\"224.0.0.0/240.0.0.0\", \n\"220.240.16.26/224.0.0.0\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 443, \n\"endPort\": 26229\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65530\n}, \n{\n\"port\": 65532, \n\"endPort\": 65534\n}\n]\n}\n]\n}"}],"expected":{"error":"allow[0].port must be between 1 and 65535"}}, + {"name":"random #131","input":[{"text":"{\"allow\":[{\"cidr\":\"2001:db8::/118\",\"except\":[\"2001:DB8::3F0/124\"]},{\"to\":[{\"cidr\":\"10.0.2.93/255.255.255.0\"},{\"cidr\":\"8000:0000:0000:0000:0000:0000:0000:0000/01\"},{\"cidr\":\"10.0.1.128/26\",\"except\":[\"10.0.1.154/255.255.255.224\"]}]},{\"cidr\":\"2001:db8::5c/126\",\"protocol\":\"any\",\"port\":2,\"endPort\":5},{\"to\":[{\"cidr\":\"10.0.0.151/26\",\"except\":[\"10.0.0.128/28\",\"10.0.0.146/00031\",\"10.0.0.174/31\"]},{\"cidr\":\"10.0.3.40/27\"},{\"cidr\":\"190.204.69.173\"}],\"ports\":[{\"protocol\":\"udp\"}]},{\"to\":[{\"cidr\":\"2001:db8::395/123\"}],\"ports\":[{\"protocol\":\"any\",\"port\":1}]},{\"cidr\":\"10.0.0.183/24\",\"protocol\":\"icmp\"}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.3.238/22\"},{\"cidr\":\"10.0.2.29/0.0.0.0\"},{\"cidr\":\"2001:db8::/0118\"},{\"cidr\":\"165.103.215.145/255.0.0.0\",\"except\":[]}]}]}"}],"expected":{"allow":["10.0.0.0/24:icmp","10.0.0.144/31:udp","10.0.0.148/30:udp","10.0.0.152/29:udp","10.0.0.160/29:udp","10.0.0.168/30:udp","10.0.0.172/31:udp","10.0.0.176/28:udp","10.0.1.160/27","10.0.2.0/24","10.0.3.32/27:udp","190.204.69.173/32:udp","2001:db8::/119","2001:db8::200/120","2001:db8::300/121","2001:db8::380/122","2001:db8::3c0/123","2001:db8::3e0/124","8000::/1"],"deny":["0.0.0.0/0","2001:db8::/118"]}}, + {"name":"random #132","input":[{"text":"{\"allow\":[{},{\"cidr\":\"2001:db8::1a0/124\",\"except\":[\"2001:db8::1a8/125\",\"2001:db8::1a0/124\",\"2001:db8::1a3/126\"]},{\"to\":[{\"cidr\":\"2001:db8::300/000121\",\"except\":[\"2001:db8::30a\"]}]},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0040/123\"}]},{\"cidr\":\"10.0.0.160/28\",\"except\":[\"10.0.0.164/30\"],\"protocol\":\"udp\"},{\"to\":[{\"cidr\":\"2001:db8::180/123\"},{\"cidr\":\"224.0.0.0/4\"}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"tcp\",\"port\":444}]}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.2.128/26\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":65531,\"endPort\":65534},{\"protocol\":\"any\",\"port\":65534,\"endPort\":65535},{\"protocol\":\"tcp\",\"port\":65530}]},{\"to\":[{\"cidr\":\"10.0.0.0/0.0.3.255\"},{\"cidr\":\"2001:db8::100/123\",\"except\":[\"2001:0db8:0000:0000:0000:0000:0000:0100/123\",\"2001:db8::10c/126\",\"2001:0db8:0000:0000:0000:0000:0000:0110/00125\"]}],\"ports\":[{\"protocol\":\"udp\"},{\"protocol\":\"udp\"},{\"protocol\":\"any\",\"port\":65532,\"endPort\":65532},{\"protocol\":\"udp\",\"port\":78}]},{\"ports\":[{\"protocol\":\"tcp\",\"port\":79,\"endPort\":79},{\"protocol\":\"tcp\"},{\"protocol\":\"tcp\"}]},{\"cidr\":\"2001:db8::259/119\"}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["0.0.0.0/0:tcp","10.0.0.0/22:udp","::/0:tcp","2001:db8::200/119"]}}, + {"name":"random #133","input":[{"text":"{\n\"allow\":[\n{\n\"to\":[\n{\n\"cidr\":\"10.0.0.246/255.255.255.252\"\n},\n{\n\"cidr\":\"7A15:BB1D:3806:843B:5B2D:70F1:7B00:0/00104\"\n},\n{\n\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:03c4/126\"\n},\n{\n\"cidr\":\"10.0.0.64/28\"\n}\n],\n\"ports\":[\n{\n\"protocol\":\"udp\"\n},\n{\n\"protocol\":\"any\",\n\"port\":445,\n\"endPort\":446\n},\n{\n\"protocol\":\"udp\",\n\"port\":10\n},\n{\n\"protocol\":\"tcp\",\n\"port\":65534,\n\"endPort\":65535\n}\n]\n}\n],\n\"deny\":[\n{\n\"to\":[\n{\n\"cidr\":\"10.0.3.214/255.255.255.192\",\n\"except\":[]\n},\n{\n\"cidr\":\"2001:DB8::60/124\"\n},\n{\n\"cidr\":\"10.0.1.15/255.255.255.192\"\n},\n{\n\"cidr\":\"10.0.3.224/0027\"\n}\n],\n\"ports\":[\n{\n\"protocol\":\"icmp\"\n},\n{\n\"protocol\":\"udp\",\n\"port\":442\n},\n{\n\"protocol\":\"tcp\",\n\"port\":65532\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"2001:db8::8f/125\"\n}\n],\n\"ports\":[\n{\n\"protocol\":\"tcp\",\n\"port\":440\n},\n{\n\"protocol\":\"udp\",\n\"port\":65533\n},\n{\n\"protocol\":\"tcp\",\n\"port\":6,\n\"endPort\":12\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"2001:db8::14f/125\",\n\"except\":[\n\"2001:0db8:0000:0000:0000:0000:0000:014c/126\"\n]\n},\n{\n\"cidr\":\"9073:f12:5fb4:22a6:e06b:5246:468:f3f6/128\",\n\"except\":[]\n},\n{\n\"cidr\":\"2001:db8::28c/123\"\n},\n{\n\"cidr\":\"10.0.3.192/26\",\n\"except\":[\n\"10.0.3.200/29\",\n\"10.0.3.224/29\",\n\"10.0.3.212/31\"\n]\n}\n]\n},\n{\n\"to\":[\n{\n\"cidr\":\"2001:db8::/124\"\n},\n{\n\"cidr\":\"0.0.0.0/1\",\n\"except\":[\n\"120.0.0.0/9\",\n\"6.0.0.0/8\",\n\"96.0.0.0/3\"\n]\n}\n],\n\"ports\":[\n{\n\"protocol\":\"any\",\n\"port\":82,\n\"endPort\":88\n}\n]\n},\n{\n\"cidr\":\"142.203.39.93\",\n\"protocol\":\"tcp\"\n},\n{\n\"to\":[\n{\n\"cidr\":\"10.0.2.0/23\"\n}\n],\n\"ports\":[\n{\n\"protocol\":\"tcp\",\n\"port\":443\n},\n{\n\"port\":441,\n\"endPort\":444\n},\n{\n\"protocol\":\"tcp\"\n}\n]\n},\n{\n\"cidr\":\"2001:db8::37c/127\",\n\"protocol\":\"any\"\n},\n{\n\"to\":[\n{\n\"cidr\":\"10.0.0.0/22\"\n}\n],\n\"ports\":[\n{\n\"protocol\":\"icmp\"\n},\n{\n\"protocol\":\"tcp\",\n\"port\":443,\n\"endPort\":447\n},\n{\n\"protocol\":\"tcp\",\n\"port\":5,\n\"endPort\":7\n},\n{\n\"protocol\":\"udp\",\n\"port\":440,\n\"endPort\":440\n}\n]\n}\n]\n}"}],"expected":{"allow":["10.0.0.64/28:tcp:445-446","10.0.0.64/28:tcp:65534-65535","10.0.0.64/28:udp","10.0.0.244/30:tcp:445-446","10.0.0.244/30:tcp:65534-65535","10.0.0.244/30:udp","2001:db8::3c4/126:tcp:445-446","2001:db8::3c4/126:tcp:65534-65535","2001:db8::3c4/126:udp","7a15:bb1d:3806:843b:5b2d:70f1:7b00:0/104:tcp:445-446","7a15:bb1d:3806:843b:5b2d:70f1:7b00:0/104:tcp:65534-65535","7a15:bb1d:3806:843b:5b2d:70f1:7b00:0/104:udp"],"deny":["0.0.0.0/6:tcp:82-88","0.0.0.0/6:udp:82-88","4.0.0.0/7:tcp:82-88","4.0.0.0/7:udp:82-88","7.0.0.0/8:tcp:82-88","7.0.0.0/8:udp:82-88","8.0.0.0/5:tcp:82-88","8.0.0.0/5:udp:82-88","10.0.0.0/22:icmp","10.0.0.0/22:tcp:5-7","10.0.0.0/22:tcp:443-447","10.0.0.0/22:udp:440","10.0.1.0/26:tcp:65532","10.0.1.0/26:udp:442","10.0.2.0/23:tcp","10.0.2.0/23:udp:441-444","10.0.3.192/29","10.0.3.208/30","10.0.3.214/31","10.0.3.216/29","10.0.3.232/29","10.0.3.240/28","16.0.0.0/4:tcp:82-88","16.0.0.0/4:udp:82-88","32.0.0.0/3:tcp:82-88","32.0.0.0/3:udp:82-88","64.0.0.0/3:tcp:82-88","64.0.0.0/3:udp:82-88","142.203.39.93/32:tcp","2001:db8::/124:tcp:82-88","2001:db8::/124:udp:82-88","2001:db8::60/124:icmp","2001:db8::60/124:tcp:65532","2001:db8::60/124:udp:442","2001:db8::88/125:tcp:6-12","2001:db8::88/125:tcp:440","2001:db8::88/125:udp:65533","2001:db8::148/126","2001:db8::280/123","2001:db8::37c/127","9073:f12:5fb4:22a6:e06b:5246:468:f3f6/128"]}}, + {"name":"random #134","input":[{"text":"{\n \"allow\": [\n {\n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 1e400, \n \"endPort\": 12159\n }\n ]\n }, \n {\n \"cidr\": \"2001:db8::2d0/125\", \n \"protocol\": \"tcp\", \n \"port\": 65532\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.1.216/29\"\n }, \n {\n \"cidr\": \"10.0.1.16/29\"\n }, \n {\n \"cidr\": \"10.0.1.0/24\", \n \"except\": [\n \"10.0.1.128/26\", \n \"10.0.1.37/255.255.255.0\"\n ]\n }, \n {\n \"cidr\": \"10.0.3.216/30\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 5, \n \"endPort\": 6\n }, \n {\n \"port\": 1\n }, \n {\n \"protocol\": \"any\", \n \"port\": 444\n }\n ]\n }\n ], \n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.80/28\"\n }, \n {\n \"cidr\": \"10.0.3.232/31\", \n \"except\": []\n }, \n {\n \"cidr\": \"64.0.0.0/4\", \n \"except\": [\n \"77.179.29.136/10\", \n \"74.225.121.163/0011\"\n ]\n }, \n {\n \"cidr\": \"2001:db8::300/121\", \n \"except\": [\n \"2001:0db8:0000:0000:0000:0000:0000:0300/123\", \n \"2001:db8::32d/125\"\n ]\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::261/120\"\n }, \n {\n \"cidr\": \"2001:db8::301/127\"\n }, \n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0015/124\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"cidr\": \"10.0.3.215/26\", \n \"protocol\": \"tcp\", \n \"port\": 65531, \n \"endPort\": 65535\n }, \n {\n \"cidr\": \"2001:db8::28/123\", \n \"except\": [\n \"2001:0db8:0000:0000:0000:0000:0.0.0.47/123\", \n \"2001:db8::3a\", \n \"2001:0db8:0000:0000:0000:0000:0000:0027/124\"\n ], \n \"protocol\": \"icmp\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.0/23\", \n \"except\": [\n \"10.0.0.128/26\", \n \"10.0.1.237/0026\", \n \"10.0.1.128/25\"\n ]\n }, \n {\n \"cidr\": \"10.0.2.55/0.0.0.3\"\n }, \n {\n \"cidr\": \"2001:DB8::/119\"\n }, \n {\n \"cidr\": \"2001:db8::260/123\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:DB8::2B8/118\", \n \"except\": []\n }, \n {\n \"cidr\": \"10.0.1.0/0024\"\n }, \n {\n \"cidr\": \"2001:db8::1c0/123\", \n \"except\": [\n \"10.0.1.169/26\", \n \"2001:0db8:0000:0000:0000:0000:0000:01c0/123\", \n \"2001:0db8:0000:0000:0000:0000:0.0.1.192/123\"\n ]\n }, \n {\n \"cidr\": \"ad6d:3cad:1118:5dea:ae5c:e18e:253.14.162.96/127\"\n }\n ]\n }, \n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/120\", \n \"except\": [\n \"2001:0db8:0000:0000:0000:0000:0000:00d9\", \n \"2001:db8::10/124\", \n \"2001:db8::e9/126\"\n ], \n \"protocol\": \"any\", \n \"port\": 440, \n \"endPort\": 445\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"any\"\n }, \n {\n \"protocol\": \"icmp\"\n }, \n {\n \"port\": 440\n }\n ]\n }\n ]\n}"}],"expected":{"error":"allow[0].ports[0].port must be an integer"}}, + {"name":"random #135","input":[{"text":"{\n \"allow\":[\n {\n \"to\":[\n {\n \"cidr\":\"230.98.33.78\"\n },\n {\n \"cidr\":\"10.0.1.252/32\"\n }\n ],\n \"ports\":[\n {\n \"port\":81\n },\n {\n \"protocol\":\"any\",\n \"port\":440,\n \"endPort\":445\n },\n {\n \"protocol\":\"tcp\",\n \"port\":8\n },\n {\n \"protocol\":\"any\",\n \"port\":10\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.2.48/29\"\n },\n {\n \"cidr\":\"10.0.1.241/31\",\n \"except\":[\n \"10.0.1.240/31\"\n ]\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.3.64/28\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"udp\"\n },\n {\n \"protocol\":\"any\"\n },\n {\n \"protocol\":\"tcp\",\n \"port\":81\n }\n ]\n },\n {\n \"ports\":[\n {\n \"protocol\":\"udp\",\n \"port\":65533,\n \"endPort\":65535\n },\n {\n \"protocol\":\"any\",\n \"port\":65531\n },\n {\n \"protocol\":\"tcp\",\n \"port\":5,\n \"endPort\":9\n },\n {\n \"protocol\":\"icmp\"\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"2001:DB8::378/127\"\n },\n {\n \"cidr\":\"10.0.2.30/00032\"\n },\n {\n \"cidr\":\"10.0.1.236/31\"\n },\n {\n \"cidr\":\"10.0.1.128/25\"\n }\n ],\n \"ports\":[\n {\n \"protocol\":\"udp\",\n \"port\":3\n },\n {\n \"protocol\":\"tcp\"\n },\n {\n \"protocol\":\"udp\",\n \"port\":11,\n \"endPort\":12\n }\n ]\n },\n {\n \"cidr\":\"10.0.0.128/255.255.255.128\",\n \"protocol\":\"any\",\n \"port\":440,\n \"endPort\":445\n }\n ],\n \"deny\":[\n {\n \"to\":[\n {\n \"cidr\":\"10.0.0.0/0.0.1.255\"\n },\n {\n \"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0300/120\",\n \"except\":[\n \"2001:db8::33e/127\",\n \"2001:db8::39d\",\n \"2001:db8::3c0/124\"\n ]\n }\n ]\n },\n {\n \"to\":[\n {\n \"cidr\":\"10.0.3.76/30\"\n },\n {\n \"cidr\":\"10.0.0.182/32\"\n },\n {\n \"cidr\":\"2001:db8::80/122\"\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:5-9","0.0.0.0/0:tcp:65531","0.0.0.0/0:udp:65531","0.0.0.0/0:udp:65533-65535","10.0.0.128/25:tcp:440-445","10.0.0.128/25:udp:440-445","10.0.1.128/25:tcp","10.0.1.128/25:udp:3","10.0.1.128/25:udp:11-12","10.0.1.252/32:udp:10-12","10.0.1.252/32:udp:81","10.0.1.252/32:udp:440-445","10.0.2.30/32:tcp","10.0.2.30/32:udp:3","10.0.2.30/32:udp:11-12","10.0.2.48/29","10.0.3.64/28","230.98.33.78/32:tcp:10","230.98.33.78/32:tcp:81","230.98.33.78/32:tcp:440-445","230.98.33.78/32:udp:10","230.98.33.78/32:udp:81","230.98.33.78/32:udp:440-445","::/0:icmp","::/0:tcp:5-9","::/0:tcp:65531","::/0:udp:65531","::/0:udp:65533-65535","2001:db8::378/127:tcp","2001:db8::378/127:udp:3","2001:db8::378/127:udp:11-12"],"deny":["10.0.0.0/23","10.0.3.76/30","2001:db8::80/122","2001:db8::300/123","2001:db8::320/124","2001:db8::330/125","2001:db8::338/126","2001:db8::33c/127","2001:db8::340/122","2001:db8::380/124","2001:db8::390/125","2001:db8::398/126","2001:db8::39c/128","2001:db8::39e/127","2001:db8::3a0/123","2001:db8::3d0/124","2001:db8::3e0/123"]}}, + {"name":"random #136","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::200/120\"\n},\n{\n\"cidr\": \"10.0.0.187/29\"\n},\n{\n\"cidr\": \"121.37.116.20/1\",\n\"except\": [\n\"84.0.0.0/3.255.255.255\",\n\"32.0.0.0/0003\",\n\"0.0.0.0/1\"\n]\n},\n{\n\"cidr\": \"10.0.1.192/28\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\",\n\"port\": 6,\n\"endPort\": 6\n},\n{\n\"protocol\": \"tcp\",\n\"port\": 65533\n},\n{\n\"protocol\": \"udp\",\n\"port\": 441,\n\"endPort\": 443\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.10/32\"\n},\n{\n\"cidr\": \"235.175.230.156/0.0.0.1\"\n},\n{\n\"cidr\": \"2001:db8::3e7/127\"\n},\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.3.96/0127\",\n\"except\": [\n\"2001:db8::360/127\",\n\"2001:db8::361\",\n\"2001:db8::360\"\n]\n}\n]\n},\n{\n\"cidr\": \"10.0.0.60/30\",\n\"protocol\": \"udp\",\n\"port\": 65535,\n\"endPort\": 65535\n}\n],\n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"160.0.0.0/4\"\n},\n{\n\"cidr\": \"10.0.2.24/255.255.255.252\",\n\"except\": [\n\"10.0.2.25/32\",\n\"10.0.2.24/255.255.255.252\",\n\"10.0.2.24/255.255.255.252\"\n]\n},\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/120\"\n},\n{\n\"cidr\": \"10.0.0.0/022\"\n}\n],\n\"ports\": [\n{\n\"port\": 7,\n\"endPort\": 27348\n},\n{}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::/120\"\n},\n{\n\"cidr\": \"::/00\",\n\"except\": [\n\"a000::/7\",\n\"8000::/1\",\n\"d895:78b8:48bc:1d2e:b22:cc1d:2850:d2d8/8\"\n]\n},\n{\n\"cidr\": \"10.0.0.0/25\"\n},\n{\n\"cidr\": \"64.0.0.0/2\"\n}\n],\n\"ports\": [\n{\n\"port\": 65530,\n\"endPort\": 65535\n},\n{\n\"protocol\": \"udp\",\n\"port\": 81\n}\n],\n\"cidr\": \"192.0.2.0/24\"\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.89/27\",\n\"except\": [\n\"10.0.3.87/30\",\n\"10.0.3.86/0.0.0.1\",\n\"10.0.3.68\"\n]\n},\n{\n\"cidr\": \"10.0.1.128/25\"\n},\n{\n\"cidr\": \"2001:db8::11c/122\",\n\"except\": [\n\"2001:0db8:0000:0000:0000:0000:0000:010c\",\n\"2001:db8::11b/127\"\n]\n},\n{\n\"cidr\": \"10.0.3.64/26\"\n}\n]\n}\n]\n}"}],"expected":{"error":"deny[1] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #137","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.16/28\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::13d/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":78,\n\t\t\t\"endPort\":83\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/02\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0050/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::/3\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::199/121\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":80,\n\t\t\t\t\t\"endPort\":85\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65532,\n\t\t\t\t\t\"endPort\":65534\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::300/120\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.19/0.0.1.255\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"e515:bd82:4b3f:8659:a392:74af:2957:abce/127\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.128/26\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65534,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":5,\n\t\t\t\t\t\"endPort\":8\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":442,\n\t\t\t\t\t\"endPort\":444\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::160/124\",\n\t\t\t\"protocol\":\"icmp\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"192.0.0.0/2\",\n\t\t\t\"protocol\":\"tcp\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"232.65.66.70\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":445\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:DB8::/118\",\n\t\t\t\"protocol\":\"udp\"\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.50/32\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.8/255.255.255.248\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/23\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"3d30:149d:4ad5:0bde:9da8:db21:2500:0000/104\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"3d30:149d:4ad5:bde:9da8:db21:2500:0/106\",\n\t\t\t\t\t\t\"3d30:149d:4ad5:bde:9da8:db21:2560:9495/110\",\n\t\t\t\t\t\t\"3d30:149d:4ad5:bde:9da8:db21:2547:4bac/105\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.0/29\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.180/26\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::295/119\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65533,\n\t\t\t\t\t\"endPort\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::164/128\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::19a/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::380/122\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:db8::380/123\",\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:03b0/124\",\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:0380/122\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.0/24\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":65534,\n\t\t\t\"endPort\":65535\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::3c0/122\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::c1/119\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"39.134.14.87/0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.160/255.255.255.224\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/0000\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.160/27\",\n\t\t\t\"protocol\":\"udp\"\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::1c4/127\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":65530,\n\t\t\t\t\t\"endPort\":65532\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"4000::/0003\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:03a0/123\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::139/122\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"208.0.0.0/8\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":82,\n\t\t\t\t\t\"endPort\":87\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"cef4:e6ce:9bd5:2da7:0000:0000:0000:0000/0064\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"13.227.35.80/24\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::ffff:0.0.0.0/96\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"::ffff:0.0.0.0/96\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::200/120\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::191/120\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"128.0.0.0/1\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":78\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::37c/127\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.39/26\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:DB8::240/122\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":78\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"bc97:a452::/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":65535,\n\t\t\t\"endPort\":65535\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::236/126\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"9997:7ebe:c06f:7ac8:8d37:df38:5932:3000/120\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::/000118\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.140/30\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":79\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"port\":1,\n\t\t\t\t\t\"endPort\":1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":6\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.16/0.0.0.3\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"::ffff:54.14.205.118/000127\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":4\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::f0/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.128/00025\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.0.160/027\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::3c0/123\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":65530\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":78,\n\t\t\t\t\t\"endPort\":83\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"110.0.0.0/8\",\n\t\t\t\"protocol\":\"tcp\"\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.0/255.255.255.192\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.138/31\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.2.138/31\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/23\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"128.0.0.0/2\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":5,\n\t\t\t\t\t\"endPort\":7\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.0/23\",\n\t\t\t\"except\":[],\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":65534,\n\t\t\t\"endPort\":65535\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/24\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.232/30\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.128/255.255.255.128\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":80,\n\t\t\t\t\t\"endPort\":80\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":444\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":441,\n\t\t\t\t\t\"endPort\":446\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"7902:65A0:A833:4B7F:B61D:4BB1:BF60:630B/64\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"91.69.191.0/24\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"91.69.191.24/29\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.80/28\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::180/120\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:0100/000122\",\n\t\t\t\t\t\t\"2001:db8::17c/121\",\n\t\t\t\t\t\t\"2001:db8::130/124\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65533\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":6,\n\t\t\t\t\t\"endPort\":10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":78,\n\t\t\t\t\t\"endPort\":36596\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::200/119\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":440\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["10.0.3.80/28:icmp","10.0.3.80/28:tcp:6-10","10.0.3.80/28:tcp:78-36596","10.0.3.80/28:tcp:65533","91.69.191.0/28:icmp","91.69.191.0/28:tcp:6-10","91.69.191.0/28:tcp:78-36596","91.69.191.0/28:tcp:65533","91.69.191.16/29:icmp","91.69.191.16/29:tcp:6-10","91.69.191.16/29:tcp:78-36596","91.69.191.16/29:tcp:65533","91.69.191.32/27:icmp","91.69.191.32/27:tcp:6-10","91.69.191.32/27:tcp:78-36596","91.69.191.32/27:tcp:65533","91.69.191.64/26:icmp","91.69.191.64/26:tcp:6-10","91.69.191.64/26:tcp:78-36596","91.69.191.64/26:tcp:65533","91.69.191.128/25:icmp","91.69.191.128/25:tcp:6-10","91.69.191.128/25:tcp:78-36596","91.69.191.128/25:tcp:65533","2001:db8::180/121:icmp","2001:db8::180/121:tcp:6-10","2001:db8::180/121:tcp:78-36596","2001:db8::180/121:tcp:65533","2001:db8::200/119:icmp","2001:db8::200/119:tcp:440","7902:65a0:a833:4b7f::/64:icmp","7902:65a0:a833:4b7f::/64:tcp:6-10","7902:65a0:a833:4b7f::/64:tcp:78-36596","7902:65a0:a833:4b7f::/64:tcp:65533"]}}, + {"name":"random #138","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:db8::/121\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 445}]}, {\"to\": [{\"cidr\": \"2001:db8::380/121\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 65532}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 1}]}, {\"cidr\": \"2001:DB8::3F0/125\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:03f0/00128\", \"2001:db8::3f0/128\", \"2001:db8::3f4/000126\"], \"protocol\": \"any\", \"port\": 443}, {\"to\": [{\"cidr\": \"2001:db8::380/121\"}, {\"cidr\": \"2001:DB8::282/127\"}, {\"cidr\": \"10.0.1.97/28\", \"except\": [\"10.0.1.101/31\", \"10.0.1.98/32\", \"10.0.1.96/28\"]}, {\"cidr\": \"115.181.251.142/31\"}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"any\", \"port\": 65533, \"endPort\": 65534}, {\"protocol\": \"udp\", \"port\": 79}]}], \"deny\": [{\"cidr\": \"ac8b:1a18:1239:8105:d767:1885:802f:4700/120\", \"protocol\": \"udp\", \"port\": 445}, {\"to\": [{\"cidr\": \"10.0.3.0/26\", \"except\": []}], \"ports\": [{\"protocol\": \"udp\", \"port\": 78, \"endPort\": 80}]}, {\"to\": [{\"cidr\": \"2001:db8::/000119\", \"except\": [\"2001:DB8::100/120\"]}, {\"cidr\": \"10.0.2.228/31\"}], \"ports\": [{\"protocol\": \"any\", \"port\": 5, \"endPort\": 10}]}, {\"ports\": [{\"protocol\": \"udp\", \"port\": 81}]}, {\"cidr\": \"2001:DB8::2A0/123\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:02a0/125\", \"2001:db8::2b8/125\"], \"protocol\": \"any\", \"port\": 10, \"endPort\": 12}, {\"cidr\": \"2001:db8::224/126\", \"except\": [\"2001:db8::224/126\", \"2001:db8::226/127\", \"2001:db8::226/127\"], \"protocol\": \"udp\", \"port\": 445, \"endPort\": 451}, {\"to\": [{\"cidr\": \"10.0.0.136/29\"}, {\"cidr\": \"10.0.3.0/255.255.255.0\"}, {\"cidr\": \"89c7:d136:e435:6985:ce1a:b28d:424a:ef00/120\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 81}, {\"protocol\": \"udp\", \"port\": 79}]}, {\"cidr\": \"10.0.2.0/23\", \"protocol\": \"udp\", \"port\": 440}]}"}],"expected":{"allow":["115.181.251.142/31:tcp","115.181.251.142/31:udp:79","115.181.251.142/31:udp:65533-65534","2001:db8::/121:udp:445","2001:db8::282/127:tcp","2001:db8::282/127:udp:79","2001:db8::282/127:udp:65533-65534","2001:db8::380/121:tcp","2001:db8::380/121:udp"],"deny":["0.0.0.0/0:udp:81","10.0.0.136/29:tcp:81","10.0.0.136/29:udp:79","10.0.2.0/23:udp:440","10.0.2.228/31:tcp:5-10","10.0.2.228/31:udp:5-10","10.0.3.0/24:tcp:81","10.0.3.0/24:udp:79","10.0.3.0/26:udp:78-81","::/0:udp:81","2001:db8::/120:tcp:5-10","2001:db8::/120:udp:5-10","2001:db8::2a8/125:tcp:10-12","2001:db8::2a8/125:udp:10-12","2001:db8::2b0/125:tcp:10-12","2001:db8::2b0/125:udp:10-12","89c7:d136:e435:6985:ce1a:b28d:424a:ef00/120:tcp:81","89c7:d136:e435:6985:ce1a:b28d:424a:ef00/120:udp:79","ac8b:1a18:1239:8105:d767:1885:802f:4700/120:udp:445"]}}, + {"name":"random #139","input":[{"text":"{\n \"allow\": [\n {\n \"cidr\": \"2001:db8::/118\"\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 80, \n \"endPort\": 53977\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 82, \n \"endPort\": 88\n }\n ]\n }\n ], \n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"30.107.0.0/255.255.0.0\"\n }, \n {\n \"cidr\": \"81.92.35.235/32\", \n \"except\": [\n \"81.92.35.235\"\n ]\n }, \n {\n \"cidr\": \"10.0.3.128/26\"\n }, \n {\n \"cidr\": \"10.0.0.0/22\"\n }\n ]\n }, \n {\n \"cidr\": \"19.80.0.0/012\", \n \"protocol\": \"tcp\", \n \"port\": 65534\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.70/0.0.0.1\"\n }, \n {\n \"cidr\": \"10.0.0.128/00025\"\n }, \n {\n \"cidr\": \"10.0.3.0/25\", \n \"except\": [\n \"10.0.3.0/00025\", \n \"10.0.3.0/26\", \n \"10.0.3.40/0.0.0.3\"\n ]\n }, \n {\n \"cidr\": \"192.179.178.0/00024\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\", \n \"port\": 80\n }, \n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"any\", \n \"port\": 10, \n \"endPort\": 15\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"ports\": [\n {}, \n {\n \"protocol\": \"tcp\", \n \"port\": 441, \n \"endPort\": 443\n }, \n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 8\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:80-53977","0.0.0.0/0:udp:82-88","::/0:icmp","::/0:tcp:80-53977","::/0:udp:82-88","2001:db8::/118"],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"random #140","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"10.0.3.96/27\",\n\t\t\t\"protocol\": \"tcp\"\n\t\t}\n\t],\n\t\"deny\": []\n}"}],"expected":{"allow":["10.0.3.96/27:tcp"],"deny":[]}}, + {"name":"random #141","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:03bb/118\"}, {\"to\": [{\"cidr\": \"10.0.2.0/23\", \"except\": [\"10.0.3.60/31\", \"10.0.2.0/24\"]}, {\"cidr\": \"2001:db8::3e7/120\"}, {\"cidr\": \"10.0.0.0/022\", \"except\": [\"10.0.3.98/30\", \"10.0.0.0/22\", \"10.0.3.64/0.0.0.31\"]}, {\"cidr\": \"192.0.0.0/15.255.255.255\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 9, \"endPort\": 9}, {\"protocol\": \"tcp\", \"port\": 65531, \"endPort\": 65534}, {\"protocol\": \"tcp\", \"port\": 441, \"endPort\": 441}]}], \"deny\": [{\"cidr\": \"0.0.0.0/01\", \"protocol\": \"tcp\", \"port\": 79, \"endPort\": 84}, {\"ports\": [{\"protocol\": \"udp\", \"port\": 78, \"endPort\": 82}, {\"protocol\": \"tcp\", \"port\": 440}]}]}"}],"expected":{"allow":["10.0.3.0/27:tcp:441","10.0.3.0/27:tcp:65531-65534","10.0.3.0/27:udp:9","10.0.3.32/28:tcp:441","10.0.3.32/28:tcp:65531-65534","10.0.3.32/28:udp:9","10.0.3.48/29:tcp:441","10.0.3.48/29:tcp:65531-65534","10.0.3.48/29:udp:9","10.0.3.56/30:tcp:441","10.0.3.56/30:tcp:65531-65534","10.0.3.56/30:udp:9","10.0.3.62/31:tcp:441","10.0.3.62/31:tcp:65531-65534","10.0.3.62/31:udp:9","10.0.3.64/26:tcp:441","10.0.3.64/26:tcp:65531-65534","10.0.3.64/26:udp:9","10.0.3.128/25:tcp:441","10.0.3.128/25:tcp:65531-65534","10.0.3.128/25:udp:9","192.0.0.0/4:tcp:441","192.0.0.0/4:tcp:65531-65534","192.0.0.0/4:udp:9","2001:db8::/118"],"deny":["0.0.0.0/0:tcp:440","0.0.0.0/0:udp:78-82","0.0.0.0/1:tcp:79-84","::/0:tcp:440","::/0:udp:78-82"]}}, + {"name":"random #142","input":[{"text":"{\"allow\":[{\"cidr\":\"::/1\",\"protocol\":\"tcp\",\"port\":65530,\"endPort\":65530},{\"to\":[{\"cidr\":\"2001:db8::242/119\"},{\"cidr\":\"10.0.1.0/24\"}],\"ports\":[{\"protocol\":\"any\",\"port\":443,\"endPort\":445},{\"protocol\":\"tcp\",\"port\":65535,\"endPort\":65535}]},{\"cidr\":\"2001:DB8::90/125\",\"except\":[\"2001:db8::92/127\",\"2001:db8::94/126\"],\"protocol\":\"udp\"},{\"cidr\":\"2001:db8::201/128\",\"protocol\":\"tcp\",\"port\":441}],\"deny\":[{\"cidr\":\"10.0.1.177/27\",\"protocol\":\"tcp\",\"port\":65535,\"endPort\":65535},{\"cidr\":\"::/0001\",\"protocol\":\"tcp\",\"port\":443,\"endPort\":449},{\"to\":[{\"cidr\":\"8000::/0001\"},{\"cidr\":\"2001:db8::/118\",\"except\":[]},{\"cidr\":\"10.0.0.0/22\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":81,\"endPort\":85},{\"protocol\":\"tcp\",\"port\":10},{\"protocol\":\"icmp\"},{\"protocol\":\"any\"}]},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:03c0/122\"},{\"cidr\":\"10.0.1.216/29\",\"except\":[\"10.0.1.221/32\",\"10.0.1.218\"]}],\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"icmp\"}]},{\"cidr\":\"10.0.0.56/30\",\"protocol\":\"udp\",\"port\":275,\"endPort\":275},{\"cidr\":\"10.0.2.207/32\",\"protocol\":\"tcp\",\"port\":251},{\"cidr\":\"2001:db8::2aa/128\",\"protocol\":\"any\",\"port\":149},{\"cidr\":\"10.0.1.4/30\",\"protocol\":\"udp\",\"port\":141},{\"cidr\":\"10.0.0.170/32\",\"protocol\":\"udp\",\"port\":115},{\"cidr\":\"10.0.2.107/32\",\"protocol\":\"tcp\",\"port\":103},{\"cidr\":\"2001:db8::1c8/128\",\"protocol\":\"any\",\"port\":315},{\"cidr\":\"10.0.2.63/32\",\"protocol\":\"any\",\"port\":215},{\"cidr\":\"10.0.3.166/31\",\"protocol\":\"udp\",\"port\":77},{\"cidr\":\"2001:db8::1c4/128\",\"protocol\":\"any\",\"port\":143,\"endPort\":143},{\"cidr\":\"10.0.2.55/32\",\"protocol\":\"any\",\"port\":397},{\"cidr\":\"10.0.1.97/32\",\"protocol\":\"udp\",\"port\":121},{\"cidr\":\"10.0.3.231/32\",\"protocol\":\"any\",\"port\":273,\"endPort\":274},{\"cidr\":\"10.0.1.113/32\",\"protocol\":\"udp\",\"port\":45,\"endPort\":46},{\"cidr\":\"10.0.2.96/32\",\"protocol\":\"tcp\",\"port\":359,\"endPort\":361},{\"cidr\":\"10.0.3.32/32\",\"protocol\":\"tcp\",\"port\":65},{\"cidr\":\"10.0.3.31/32\",\"protocol\":\"tcp\",\"port\":11,\"endPort\":13},{\"cidr\":\"10.0.0.102/31\",\"protocol\":\"udp\",\"port\":289},{\"cidr\":\"10.0.3.218/32\",\"protocol\":\"udp\",\"port\":217},{\"cidr\":\"2001:db8::3f6/128\",\"protocol\":\"udp\",\"port\":81,\"endPort\":81},{\"cidr\":\"2001:db8::10d/128\",\"protocol\":\"any\",\"port\":157},{\"cidr\":\"10.0.3.216/30\",\"protocol\":\"tcp\",\"port\":73,\"endPort\":73},{\"cidr\":\"2001:db8::250/128\",\"protocol\":\"tcp\",\"port\":303},{\"cidr\":\"2001:db8::34f/128\",\"protocol\":\"tcp\",\"port\":231},{\"cidr\":\"10.0.0.225/32\",\"protocol\":\"tcp\",\"port\":279},{\"cidr\":\"10.0.1.180/30\",\"protocol\":\"udp\",\"port\":131},{\"cidr\":\"2001:db8::b7/128\",\"protocol\":\"udp\",\"port\":241},{\"cidr\":\"10.0.0.116/30\",\"protocol\":\"udp\",\"port\":33},{\"cidr\":\"10.0.3.160/30\",\"protocol\":\"udp\",\"port\":257},{\"cidr\":\"10.0.0.48/30\",\"protocol\":\"any\",\"port\":205,\"endPort\":206},{\"cidr\":\"10.0.0.12/31\",\"protocol\":\"udp\",\"port\":289},{\"cidr\":\"2001:db8::283/128\",\"protocol\":\"udp\",\"port\":9},{\"cidr\":\"2001:db8::3a8/128\",\"protocol\":\"tcp\",\"port\":95},{\"cidr\":\"10.0.3.46/31\",\"protocol\":\"any\",\"port\":5,\"endPort\":9},{\"cidr\":\"10.0.0.148/30\",\"protocol\":\"udp\",\"port\":27,\"endPort\":27},{\"cidr\":\"2001:db8::379/128\",\"protocol\":\"tcp\",\"port\":81},{\"cidr\":\"10.0.2.20/30\",\"protocol\":\"any\",\"port\":341},{\"cidr\":\"10.0.0.48/30\",\"protocol\":\"tcp\",\"port\":239,\"endPort\":240},{\"cidr\":\"10.0.0.168/32\",\"protocol\":\"tcp\",\"port\":205,\"endPort\":206},{\"cidr\":\"10.0.3.0/30\",\"protocol\":\"udp\",\"port\":231},{\"cidr\":\"10.0.2.120/31\",\"protocol\":\"tcp\",\"port\":273},{\"cidr\":\"10.0.3.92/30\",\"protocol\":\"any\",\"port\":251,\"endPort\":252},{\"cidr\":\"10.0.2.24/30\",\"protocol\":\"tcp\",\"port\":261},{\"cidr\":\"10.0.1.80/30\",\"protocol\":\"tcp\",\"port\":287},{\"cidr\":\"10.0.1.0/30\",\"protocol\":\"any\",\"port\":333,\"endPort\":335},{\"cidr\":\"10.0.1.208/31\",\"protocol\":\"tcp\",\"port\":115,\"endPort\":117},{\"cidr\":\"10.0.3.114/31\",\"protocol\":\"tcp\",\"port\":19,\"endPort\":23},{\"cidr\":\"10.0.0.112/30\",\"protocol\":\"udp\",\"port\":69},{\"cidr\":\"10.0.0.136/30\",\"protocol\":\"tcp\",\"port\":185},{\"cidr\":\"2001:db8::30d/128\",\"protocol\":\"any\",\"port\":391,\"endPort\":394},{\"cidr\":\"2001:db8::11a/128\",\"protocol\":\"udp\",\"port\":321},{\"cidr\":\"10.0.1.72/30\",\"protocol\":\"tcp\",\"port\":75,\"endPort\":78},{\"cidr\":\"10.0.1.108/30\",\"protocol\":\"any\",\"port\":391,\"endPort\":395},{\"cidr\":\"2001:db8::398/128\",\"protocol\":\"udp\",\"port\":85,\"endPort\":85},{\"cidr\":\"10.0.1.168/32\",\"protocol\":\"any\",\"port\":261},{\"cidr\":\"10.0.2.155/32\",\"protocol\":\"tcp\",\"port\":257,\"endPort\":257},{\"cidr\":\"10.0.3.152/31\",\"protocol\":\"udp\",\"port\":383},{\"cidr\":\"10.0.3.104/31\",\"protocol\":\"udp\",\"port\":217},{\"cidr\":\"2001:db8::22c/128\",\"protocol\":\"tcp\",\"port\":325},{\"cidr\":\"2001:db8::36e/128\",\"protocol\":\"any\",\"port\":255},{\"cidr\":\"2001:db8::33c/128\",\"protocol\":\"any\",\"port\":281,\"endPort\":282},{\"cidr\":\"2001:db8::3f9/128\",\"protocol\":\"any\",\"port\":285},{\"cidr\":\"2001:db8::2db/128\",\"protocol\":\"udp\",\"port\":275},{\"cidr\":\"10.0.1.4/30\",\"protocol\":\"any\",\"port\":327},{\"cidr\":\"10.0.3.176/30\",\"protocol\":\"tcp\",\"port\":95},{\"cidr\":\"10.0.3.42/31\",\"protocol\":\"tcp\",\"port\":167},{\"cidr\":\"2001:db8::158/128\",\"protocol\":\"udp\",\"port\":69},{\"cidr\":\"10.0.2.176/31\",\"protocol\":\"tcp\",\"port\":237,\"endPort\":237},{\"cidr\":\"10.0.3.176/30\",\"protocol\":\"tcp\",\"port\":377},{\"cidr\":\"10.0.0.7/32\",\"protocol\":\"any\",\"port\":175},{\"cidr\":\"10.0.0.72/30\",\"protocol\":\"udp\",\"port\":265},{\"cidr\":\"10.0.0.190/32\",\"protocol\":\"tcp\",\"port\":381},{\"cidr\":\"2001:db8::289/128\",\"protocol\":\"udp\",\"port\":263},{\"cidr\":\"10.0.1.212/30\",\"protocol\":\"any\",\"port\":211},{\"cidr\":\"10.0.0.218/31\",\"protocol\":\"any\",\"port\":179},{\"cidr\":\"10.0.0.244/30\",\"protocol\":\"udp\",\"port\":331},{\"cidr\":\"10.0.0.16/30\",\"protocol\":\"tcp\",\"port\":191},{\"cidr\":\"2001:db8::267/128\",\"protocol\":\"any\",\"port\":193,\"endPort\":195},{\"cidr\":\"10.0.2.24/30\",\"protocol\":\"udp\",\"port\":193,\"endPort\":197},{\"cidr\":\"10.0.1.83/32\",\"protocol\":\"tcp\",\"port\":177},{\"cidr\":\"10.0.1.188/32\",\"protocol\":\"any\",\"port\":367,\"endPort\":370},{\"cidr\":\"2001:db8::1dd/128\",\"protocol\":\"tcp\",\"port\":307},{\"cidr\":\"10.0.2.17/32\",\"protocol\":\"udp\",\"port\":55},{\"cidr\":\"10.0.0.129/32\",\"protocol\":\"udp\",\"port\":17},{\"cidr\":\"10.0.2.148/30\",\"protocol\":\"any\",\"port\":111},{\"cidr\":\"2001:db8::2a4/128\",\"protocol\":\"tcp\",\"port\":379},{\"cidr\":\"10.0.1.108/30\",\"protocol\":\"any\",\"port\":335},{\"cidr\":\"2001:db8::101/128\",\"protocol\":\"udp\",\"port\":343},{\"cidr\":\"10.0.1.32/30\",\"protocol\":\"any\",\"port\":275},{\"cidr\":\"10.0.0.68/30\",\"protocol\":\"any\",\"port\":81,\"endPort\":81},{\"cidr\":\"10.0.1.3/32\",\"protocol\":\"tcp\",\"port\":151},{\"cidr\":\"10.0.3.108/30\",\"protocol\":\"any\",\"port\":43},{\"cidr\":\"10.0.2.143/32\",\"protocol\":\"tcp\",\"port\":301},{\"cidr\":\"2001:db8::21d/128\",\"protocol\":\"tcp\",\"port\":381,\"endPort\":381},{\"cidr\":\"10.0.0.190/31\",\"protocol\":\"udp\",\"port\":199},{\"cidr\":\"10.0.0.244/31\",\"protocol\":\"udp\",\"port\":215,\"endPort\":217},{\"cidr\":\"10.0.0.104/31\",\"protocol\":\"udp\",\"port\":149},{\"cidr\":\"10.0.3.157/32\",\"protocol\":\"udp\",\"port\":5},{\"cidr\":\"10.0.2.96/32\",\"protocol\":\"tcp\",\"port\":211},{\"cidr\":\"10.0.0.221/32\",\"protocol\":\"tcp\",\"port\":57,\"endPort\":60},{\"cidr\":\"10.0.2.0/31\",\"protocol\":\"any\",\"port\":67},{\"cidr\":\"10.0.1.0/30\",\"protocol\":\"any\",\"port\":389},{\"cidr\":\"2001:db8::158/128\",\"protocol\":\"tcp\",\"port\":291,\"endPort\":292},{\"cidr\":\"2001:db8::167/128\",\"protocol\":\"tcp\",\"port\":77},{\"cidr\":\"10.0.1.136/30\",\"protocol\":\"udp\",\"port\":277},{\"cidr\":\"10.0.3.128/31\",\"protocol\":\"tcp\",\"port\":127},{\"cidr\":\"2001:db8::328/128\",\"protocol\":\"any\",\"port\":227},{\"cidr\":\"10.0.3.176/32\",\"protocol\":\"tcp\",\"port\":355},{\"cidr\":\"10.0.0.162/31\",\"protocol\":\"any\",\"port\":173},{\"cidr\":\"10.0.1.72/31\",\"protocol\":\"tcp\",\"port\":87},{\"cidr\":\"10.0.0.60/30\",\"protocol\":\"any\",\"port\":285},{\"cidr\":\"10.0.1.24/30\",\"protocol\":\"udp\",\"port\":121,\"endPort\":123},{\"cidr\":\"2001:db8::251/128\",\"protocol\":\"tcp\",\"port\":229,\"endPort\":233},{\"cidr\":\"10.0.1.250/32\",\"protocol\":\"tcp\",\"port\":247},{\"cidr\":\"10.0.2.152/31\",\"protocol\":\"udp\",\"port\":7},{\"cidr\":\"10.0.1.193/32\",\"protocol\":\"tcp\",\"port\":1},{\"cidr\":\"2001:db8::6c/128\",\"protocol\":\"udp\",\"port\":157,\"endPort\":159},{\"cidr\":\"2001:db8::296/128\",\"protocol\":\"any\",\"port\":313,\"endPort\":317},{\"cidr\":\"10.0.1.170/32\",\"protocol\":\"tcp\",\"port\":273},{\"cidr\":\"10.0.3.254/32\",\"protocol\":\"tcp\",\"port\":249},{\"cidr\":\"10.0.0.10/31\",\"protocol\":\"any\",\"port\":85},{\"cidr\":\"10.0.2.16/32\",\"protocol\":\"tcp\",\"port\":361},{\"cidr\":\"10.0.2.188/30\",\"protocol\":\"any\",\"port\":159},{\"cidr\":\"10.0.3.168/32\",\"protocol\":\"udp\",\"port\":243},{\"cidr\":\"10.0.0.120/30\",\"protocol\":\"tcp\",\"port\":301},{\"cidr\":\"10.0.3.216/30\",\"protocol\":\"udp\",\"port\":389},{\"cidr\":\"10.0.1.219/32\",\"protocol\":\"any\",\"port\":143},{\"cidr\":\"10.0.0.144/30\",\"protocol\":\"udp\",\"port\":367},{\"cidr\":\"10.0.2.124/30\",\"protocol\":\"tcp\",\"port\":219,\"endPort\":222},{\"cidr\":\"10.0.0.208/32\",\"protocol\":\"tcp\",\"port\":323},{\"cidr\":\"2001:db8::49/128\",\"protocol\":\"any\",\"port\":25},{\"cidr\":\"2001:db8::32e/128\",\"protocol\":\"udp\",\"port\":349,\"endPort\":350},{\"cidr\":\"10.0.3.184/30\",\"protocol\":\"tcp\",\"port\":209},{\"cidr\":\"10.0.3.108/30\",\"protocol\":\"any\",\"port\":197},{\"cidr\":\"2001:db8::28c/128\",\"protocol\":\"any\",\"port\":true},{\"cidr\":\"2001:db8::c8/128\",\"protocol\":\"tcp\",\"port\":263},{\"cidr\":\"10.0.1.204/30\",\"protocol\":\"tcp\",\"port\":109},{\"cidr\":\"2001:db8::47/128\",\"protocol\":\"tcp\",\"port\":231},{\"cidr\":\"2001:db8::1cd/128\",\"protocol\":\"any\",\"port\":65},{\"cidr\":\"10.0.3.36/30\",\"protocol\":\"any\",\"port\":211,\"endPort\":211},{\"cidr\":\"10.0.0.68/30\",\"protocol\":\"tcp\",\"port\":19},{\"cidr\":\"2001:db8::23c/128\",\"protocol\":\"any\",\"port\":11},{\"cidr\":\"10.0.0.232/31\",\"protocol\":\"any\",\"port\":73},{\"cidr\":\"2001:db8::1ed/128\",\"protocol\":\"any\",\"port\":383},{\"cidr\":\"10.0.0.77/32\",\"protocol\":\"udp\",\"port\":247,\"endPort\":249},{\"cidr\":\"10.0.0.235/32\",\"protocol\":\"any\",\"port\":245},{\"cidr\":\"2001:db8::376/128\",\"protocol\":\"tcp\",\"port\":101,\"endPort\":102},{\"cidr\":\"10.0.0.192/32\",\"protocol\":\"any\",\"port\":57,\"endPort\":57},{\"cidr\":\"10.0.2.30/31\",\"protocol\":\"any\",\"port\":57,\"endPort\":60},{\"cidr\":\"10.0.3.10/32\",\"protocol\":\"any\",\"port\":313},{\"cidr\":\"2001:db8::25b/128\",\"protocol\":\"udp\",\"port\":385},{\"cidr\":\"2001:db8::3b8/128\",\"protocol\":\"tcp\",\"port\":49},{\"cidr\":\"10.0.2.156/31\",\"protocol\":\"tcp\",\"port\":205},{\"cidr\":\"2001:db8::189/128\",\"protocol\":\"any\",\"port\":331},{\"cidr\":\"10.0.1.44/30\",\"protocol\":\"udp\",\"port\":291},{\"cidr\":\"2001:db8::3a5/128\",\"protocol\":\"udp\",\"port\":377,\"endPort\":377},{\"cidr\":\"10.0.3.36/31\",\"protocol\":\"tcp\",\"port\":395,\"endPort\":395},{\"cidr\":\"10.0.1.168/30\",\"protocol\":\"tcp\",\"port\":67},{\"cidr\":\"2001:db8::368/128\",\"protocol\":\"udp\",\"port\":177,\"endPort\":181},{\"cidr\":\"10.0.3.54/31\",\"protocol\":\"udp\",\"port\":141,\"endPort\":145},{\"cidr\":\"10.0.1.202/32\",\"protocol\":\"udp\",\"port\":85},{\"cidr\":\"10.0.0.113/32\",\"protocol\":\"any\",\"port\":381},{\"cidr\":\"10.0.1.116/31\",\"protocol\":\"any\",\"port\":389},{\"cidr\":\"10.0.1.238/31\",\"protocol\":\"any\",\"port\":261},{\"cidr\":\"10.0.0.240/30\",\"protocol\":\"any\",\"port\":301},{\"cidr\":\"10.0.2.15/32\",\"protocol\":\"tcp\",\"port\":323},{\"cidr\":\"2001:db8::e2/128\",\"protocol\":\"udp\",\"port\":233},{\"cidr\":\"10.0.3.93/32\",\"protocol\":\"tcp\",\"port\":215,\"endPort\":215},{\"cidr\":\"10.0.0.172/30\",\"protocol\":\"tcp\",\"port\":277},{\"cidr\":\"10.0.3.112/30\",\"protocol\":\"udp\",\"port\":221},{\"cidr\":\"10.0.0.72/30\",\"protocol\":\"tcp\",\"port\":217},{\"cidr\":\"10.0.1.54/31\",\"protocol\":\"any\",\"port\":385,\"endPort\":389},{\"cidr\":\"10.0.3.36/30\",\"protocol\":\"any\",\"port\":41},{\"cidr\":\"10.0.2.128/30\",\"protocol\":\"udp\",\"port\":363,\"endPort\":366},{\"cidr\":\"2001:db8::3ec/128\",\"protocol\":\"any\",\"port\":167},{\"cidr\":\"10.0.3.175/32\",\"protocol\":\"any\",\"port\":153},{\"cidr\":\"10.0.0.12/32\",\"protocol\":\"any\",\"port\":205,\"endPort\":208},{\"cidr\":\"10.0.1.22/31\",\"protocol\":\"any\",\"port\":69,\"endPort\":72},{\"cidr\":\"2001:db8::132/128\",\"protocol\":\"any\",\"port\":349,\"endPort\":353},{\"cidr\":\"10.0.2.35/32\",\"protocol\":\"any\",\"port\":265},{\"cidr\":\"10.0.0.244/31\",\"protocol\":\"udp\",\"port\":121},{\"cidr\":\"10.0.2.160/30\",\"protocol\":\"any\",\"port\":23},{\"cidr\":\"2001:db8::2da/128\",\"protocol\":\"tcp\",\"port\":123,\"endPort\":126},{\"cidr\":\"10.0.3.130/31\",\"protocol\":\"udp\",\"port\":29,\"endPort\":32},{\"cidr\":\"10.0.3.211/32\",\"protocol\":\"udp\",\"port\":111,\"endPort\":113},{\"cidr\":\"10.0.0.223/32\",\"protocol\":\"any\",\"port\":29},{\"cidr\":\"2001:db8::ff/128\",\"protocol\":\"tcp\",\"port\":123},{\"cidr\":\"10.0.1.222/32\",\"protocol\":\"tcp\",\"port\":337},{\"cidr\":\"10.0.2.48/32\",\"protocol\":\"udp\",\"port\":139},{\"cidr\":\"10.0.3.236/30\",\"protocol\":\"any\",\"port\":373},{\"cidr\":\"10.0.0.204/31\",\"protocol\":\"any\",\"port\":195},{\"cidr\":\"2001:db8::1c7/128\",\"protocol\":\"udp\",\"port\":239},{\"cidr\":\"2001:db8::15a/128\",\"protocol\":\"udp\",\"port\":327},{\"cidr\":\"10.0.0.218/31\",\"protocol\":\"tcp\",\"port\":319,\"endPort\":322},{\"cidr\":\"10.0.0.208/31\",\"protocol\":\"any\",\"port\":61,\"endPort\":65},{\"cidr\":\"2001:db8::3f6/128\",\"protocol\":\"tcp\",\"port\":255},{\"cidr\":\"10.0.0.72/30\",\"protocol\":\"tcp\",\"port\":235},{\"cidr\":\"2001:db8::3b3/128\",\"protocol\":\"udp\",\"port\":341,\"endPort\":345},{\"cidr\":\"10.0.2.140/30\",\"protocol\":\"tcp\",\"port\":87},{\"cidr\":\"10.0.0.240/31\",\"protocol\":\"tcp\",\"port\":225},{\"cidr\":\"10.0.0.188/31\",\"protocol\":\"udp\",\"port\":107},{\"cidr\":\"2001:db8::2d8/128\",\"protocol\":\"tcp\",\"port\":337},{\"cidr\":\"10.0.0.255/32\",\"protocol\":\"tcp\",\"port\":293},{\"cidr\":\"10.0.1.99/32\",\"protocol\":\"udp\",\"port\":363},{\"cidr\":\"10.0.2.150/31\",\"protocol\":\"udp\",\"port\":321,\"endPort\":324},{\"cidr\":\"2001:db8::18f/128\",\"protocol\":\"udp\",\"port\":147,\"endPort\":150},{\"cidr\":\"2001:db8::f/128\",\"protocol\":\"tcp\",\"port\":99},{\"cidr\":\"10.0.1.4/31\",\"protocol\":\"any\",\"port\":341},{\"cidr\":\"10.0.2.248/30\",\"protocol\":\"tcp\",\"port\":103},{\"cidr\":\"10.0.1.170/32\",\"protocol\":\"udp\",\"port\":13},{\"cidr\":\"10.0.2.113/32\",\"protocol\":\"tcp\",\"port\":147},{\"cidr\":\"10.0.0.208/30\",\"protocol\":\"any\",\"port\":343},{\"cidr\":\"2001:db8::ce/128\",\"protocol\":\"any\",\"port\":253,\"endPort\":254},{\"cidr\":\"10.0.1.232/30\",\"protocol\":\"udp\",\"port\":37,\"endPort\":39},{\"cidr\":\"10.0.2.4/30\",\"protocol\":\"tcp\",\"port\":247,\"endPort\":250},{\"cidr\":\"10.0.1.216/30\",\"protocol\":\"any\",\"port\":177},{\"cidr\":\"2001:db8::107/128\",\"protocol\":\"any\",\"port\":33,\"endPort\":37},{\"cidr\":\"10.0.2.185/32\",\"protocol\":\"tcp\",\"port\":305},{\"cidr\":\"10.0.3.228/31\",\"protocol\":\"tcp\",\"port\":31},{\"cidr\":\"10.0.2.48/30\",\"protocol\":\"tcp\",\"port\":361,\"endPort\":365},{\"cidr\":\"2001:db8::2da/128\",\"protocol\":\"tcp\",\"port\":117},{\"cidr\":\"2001:db8::272/128\",\"protocol\":\"udp\",\"port\":75,\"endPort\":77},{\"cidr\":\"10.0.2.112/32\",\"protocol\":\"udp\",\"port\":89,\"endPort\":92},{\"cidr\":\"10.0.1.155/32\",\"protocol\":\"udp\",\"port\":257},{\"cidr\":\"10.0.1.12/30\",\"protocol\":\"any\",\"port\":301,\"endPort\":303},{\"cidr\":\"2001:db8::389/128\",\"protocol\":\"tcp\",\"port\":69},{\"cidr\":\"10.0.1.124/31\",\"protocol\":\"udp\",\"port\":113},{\"cidr\":\"2001:db8::284/128\",\"protocol\":\"tcp\",\"port\":119},{\"cidr\":\"10.0.3.161/32\",\"protocol\":\"any\",\"port\":129},{\"cidr\":\"10.0.3.190/31\",\"protocol\":\"any\",\"port\":17,\"endPort\":21},{\"cidr\":\"10.0.1.132/31\",\"protocol\":\"any\",\"port\":299,\"endPort\":302},{\"cidr\":\"10.0.1.100/30\",\"protocol\":\"tcp\",\"port\":73,\"endPort\":73},{\"cidr\":\"10.0.2.132/31\",\"protocol\":\"any\",\"port\":175,\"endPort\":178},{\"cidr\":\"10.0.0.240/30\",\"protocol\":\"tcp\",\"port\":375,\"endPort\":378},{\"cidr\":\"10.0.0.200/31\",\"protocol\":\"any\",\"port\":105,\"endPort\":107},{\"cidr\":\"10.0.1.244/30\",\"protocol\":\"any\",\"port\":155,\"endPort\":157},{\"cidr\":\"2001:db8::a9/128\",\"protocol\":\"tcp\",\"port\":343},{\"cidr\":\"2001:db8::55/128\",\"protocol\":\"any\",\"port\":127,\"endPort\":128},{\"cidr\":\"10.0.2.200/30\",\"protocol\":\"any\",\"port\":231},{\"cidr\":\"10.0.3.216/30\",\"protocol\":\"any\",\"port\":241},{\"cidr\":\"2001:db8::361/128\",\"protocol\":\"any\",\"port\":147},{\"cidr\":\"10.0.1.84/32\",\"protocol\":\"tcp\",\"port\":207},{\"cidr\":\"2001:db8::23c/128\",\"protocol\":\"tcp\",\"port\":31,\"endPort\":31},{\"cidr\":\"10.0.1.135/32\",\"protocol\":\"any\",\"port\":249,\"endPort\":252},{\"cidr\":\"10.0.3.68/30\",\"protocol\":\"tcp\",\"port\":95},{\"cidr\":\"2001:db8::20c/128\",\"protocol\":\"tcp\",\"port\":73},{\"cidr\":\"10.0.3.244/30\",\"protocol\":\"tcp\",\"port\":333},{\"cidr\":\"10.0.1.24/30\",\"protocol\":\"udp\",\"port\":187},{\"cidr\":\"2001:db8::1f2/128\",\"protocol\":\"udp\",\"port\":159,\"endPort\":160},{\"cidr\":\"2001:db8::2a5/128\",\"protocol\":\"tcp\",\"port\":195},{\"cidr\":\"2001:db8::391/128\",\"protocol\":\"tcp\",\"port\":167,\"endPort\":169},{\"cidr\":\"10.0.2.248/30\",\"protocol\":\"udp\",\"port\":395},{\"cidr\":\"10.0.1.159/32\",\"protocol\":\"udp\",\"port\":175,\"endPort\":177},{\"cidr\":\"10.0.3.42/31\",\"protocol\":\"udp\",\"port\":57},{\"cidr\":\"10.0.0.196/31\",\"protocol\":\"any\",\"port\":241,\"endPort\":243},{\"cidr\":\"2001:db8::2cd/128\",\"protocol\":\"udp\",\"port\":283},{\"cidr\":\"2001:db8::2a8/128\",\"protocol\":\"tcp\",\"port\":267},{\"cidr\":\"2001:db8::265/128\",\"protocol\":\"tcp\",\"port\":307,\"endPort\":310},{\"cidr\":\"10.0.2.194/31\",\"protocol\":\"tcp\",\"port\":135,\"endPort\":139},{\"cidr\":\"10.0.2.84/31\",\"protocol\":\"any\",\"port\":55},{\"cidr\":\"10.0.1.120/31\",\"protocol\":\"udp\",\"port\":213},{\"cidr\":\"2001:db8::3f4/128\",\"protocol\":\"udp\",\"port\":243,\"endPort\":243},{\"cidr\":\"10.0.2.216/30\",\"protocol\":\"tcp\",\"port\":307},{\"cidr\":\"10.0.1.176/32\",\"protocol\":\"tcp\",\"port\":225},{\"cidr\":\"2001:db8::1af/128\",\"protocol\":\"udp\",\"port\":397},{\"cidr\":\"10.0.1.64/31\",\"protocol\":\"tcp\",\"port\":263},{\"cidr\":\"2001:db8::9c/128\",\"protocol\":\"any\",\"port\":147},{\"cidr\":\"2001:db8::3ff/128\",\"protocol\":\"any\",\"port\":155,\"endPort\":157},{\"cidr\":\"2001:db8::338/128\",\"protocol\":\"tcp\",\"port\":21,\"endPort\":25},{\"cidr\":\"10.0.1.113/32\",\"protocol\":\"any\",\"port\":251},{\"cidr\":\"10.0.2.172/31\",\"protocol\":\"tcp\",\"port\":157,\"endPort\":159},{\"cidr\":\"10.0.2.68/31\",\"protocol\":\"udp\",\"port\":119,\"endPort\":123},{\"cidr\":\"10.0.1.232/30\",\"protocol\":\"any\",\"port\":341},{\"cidr\":\"10.0.2.236/30\",\"protocol\":\"udp\",\"port\":109},{\"cidr\":\"10.0.1.67/32\",\"protocol\":\"any\",\"port\":261},{\"cidr\":\"10.0.1.240/30\",\"protocol\":\"any\",\"port\":31},{\"cidr\":\"10.0.2.176/30\",\"protocol\":\"udp\",\"port\":55},{\"cidr\":\"10.0.3.56/30\",\"protocol\":\"tcp\",\"port\":177},{\"cidr\":\"10.0.2.28/30\",\"protocol\":\"udp\",\"port\":347},{\"cidr\":\"10.0.3.250/31\",\"protocol\":\"tcp\",\"port\":27},{\"cidr\":\"10.0.2.58/32\",\"protocol\":\"any\",\"port\":135},{\"cidr\":\"10.0.2.172/30\",\"protocol\":\"any\",\"port\":389},{\"cidr\":\"10.0.1.172/30\",\"protocol\":\"any\",\"port\":149},{\"cidr\":\"10.0.0.140/31\",\"protocol\":\"udp\",\"port\":387},{\"cidr\":\"10.0.2.48/30\",\"protocol\":\"tcp\",\"port\":171,\"endPort\":174},{\"cidr\":\"2001:db8::105/128\",\"protocol\":\"tcp\",\"port\":47},{\"cidr\":\"10.0.3.214/32\",\"protocol\":\"tcp\",\"port\":355,\"endPort\":358},{\"cidr\":\"2001:db8::2bf/128\",\"protocol\":\"any\",\"port\":137,\"endPort\":137},{\"cidr\":\"10.0.2.90/31\",\"protocol\":\"any\",\"port\":135},{\"cidr\":\"10.0.2.197/32\",\"protocol\":\"tcp\",\"port\":309},{\"cidr\":\"10.0.1.18/31\",\"protocol\":\"any\",\"port\":271,\"endPort\":275}]}"}],"expected":{"error":"deny[138].port must be an integer"}}, + {"name":"random #143","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.36/32\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 442, \n\t\t\t\t\t\"endPort\": 443\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.38/25\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"port\": 440\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:DB8::180/124\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/22\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 9, \n\t\t\t\t\t\"endPort\": 10\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 11\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t], \n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::3a0/123\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.144/30\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.145/32\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"0.0.0.0/2\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"0.0.0.0/2\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.74\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.49/255.255.255.248\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 442\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"10.0.2.196/255.255.255.128\", \n\t\t\t\"except\": [\n\t\t\t\t\"10.0.3.16/28\"\n\t\t\t], \n\t\t\t\"protocol\": \"tcp\"\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[2].except[0] must be contained in 10.0.2.128/25"}}, + {"name":"random #144","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.0.0/0024\"}, {\"cidr\": \"2001:db8::118/127\"}], \"ports\": [{}, {\"protocol\": \"udp\", \"port\": 441, \"endPort\": 446}, {\"protocol\": \"any\", \"port\": 443, \"endPort\": 60858}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"10.0.2.1/30\", \"except\": [\"10.0.2.2/32\", \"10.0.2.2/0.0.0.1\"]}, {\"cidr\": \"10.0.0.128/29\"}, {\"cidr\": \"10.0.3.131/31\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 5}, {}, {\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"2001:DB8::2C4/127\", \"except\": [\"2001:db8::2c4/128\"]}]}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.2.128/26\", \"except\": [\"10.0.2.188/30\", \"10.0.2.143/31\"]}, {\"cidr\": \"10.0.1.160/27\"}]}, {\"ports\": [{\"protocol\": \"udp\", \"port\": 444, \"endPort\": 3435}, {\"protocol\": \"tcp\", \"port\": 82}, {\"protocol\": \"any\", \"port\": 81}]}, {\"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\", \"port\": 81, \"endPort\": 87}]}, {\"cidr\": \"2001:db8::40/122\", \"protocol\": \"any\"}, {\"cidr\": \"10.0.0.46/32\", \"protocol\": \"any\", \"port\": 225}, {\"cidr\": \"10.0.2.84/30\", \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"2001:db8::125/128\", \"protocol\": \"tcp\", \"port\": 85}, {\"cidr\": \"10.0.1.248/30\", \"protocol\": \"udp\", \"port\": 291}, {\"cidr\": \"10.0.3.146/31\", \"protocol\": \"any\", \"port\": 7}, {\"cidr\": \"10.0.2.230/31\", \"protocol\": \"udp\", \"port\": 265}, {\"cidr\": \"10.0.3.170/31\", \"protocol\": \"tcp\", \"port\": 191, \"endPort\": 191}, {\"cidr\": \"2001:db8::188/128\", \"protocol\": \"udp\", \"port\": 321, \"endPort\": 324}, {\"cidr\": \"10.0.1.113/32\", \"protocol\": \"udp\", \"port\": 327, \"endPort\": 331}, {\"cidr\": \"2001:db8::246/128\", \"protocol\": \"tcp\", \"port\": 61}, {\"cidr\": \"2001:db8::56/128\", \"protocol\": \"udp\", \"port\": 129}, {\"cidr\": \"2001:db8::3fd/128\", \"protocol\": \"tcp\", \"port\": 297}, {\"cidr\": \"10.0.2.76/31\", \"protocol\": \"tcp\", \"port\": 85, \"endPort\": 89}, {\"cidr\": \"10.0.3.214/32\", \"protocol\": \"any\", \"port\": 199, \"endPort\": 203}, {\"cidr\": \"10.0.0.92/31\", \"protocol\": \"tcp\", \"port\": 289, \"endPort\": 292}, {\"cidr\": \"2001:db8::3da/128\", \"protocol\": \"udp\", \"port\": 273}, {\"cidr\": \"10.0.1.96/31\", \"protocol\": \"udp\", \"port\": 189}, {\"cidr\": \"10.0.0.122/31\", \"protocol\": \"udp\", \"port\": 249}, {\"cidr\": \"10.0.0.62/31\", \"protocol\": \"tcp\", \"port\": 189, \"endPort\": 192}, {\"cidr\": \"10.0.0.232/30\", \"protocol\": \"tcp\", \"port\": 265, \"endPort\": 265}, {\"cidr\": \"10.0.1.50/31\", \"protocol\": \"tcp\", \"port\": 179}, {\"cidr\": \"10.0.0.11/32\", \"protocol\": \"udp\", \"port\": 223}, {\"cidr\": \"2001:db8::a0/128\", \"protocol\": \"udp\", \"port\": 287}, {\"cidr\": \"2001:db8::2a3/128\", \"protocol\": \"tcp\", \"port\": 281}, {\"cidr\": \"10.0.1.144/30\", \"protocol\": \"tcp\", \"port\": 53}, {\"cidr\": \"2001:db8::382/128\", \"protocol\": \"udp\", \"port\": 219, \"endPort\": 223}, {\"cidr\": \"10.0.2.108/32\", \"protocol\": \"udp\", \"port\": 179}, {\"cidr\": \"2001:db8::383/128\", \"protocol\": \"tcp\", \"port\": 253}, {\"cidr\": \"10.0.1.40/30\", \"protocol\": \"udp\", \"port\": 25}, {\"cidr\": \"10.0.2.232/32\", \"protocol\": \"tcp\", \"port\": 205, \"endPort\": 209}, {\"cidr\": \"10.0.3.28/32\", \"protocol\": \"any\", \"port\": 279}, {\"cidr\": \"10.0.0.124/30\", \"protocol\": \"udp\", \"port\": 243}, {\"cidr\": \"10.0.1.5/32\", \"protocol\": \"tcp\", \"port\": 245, \"endPort\": 245}, {\"cidr\": \"10.0.1.46/32\", \"protocol\": \"udp\", \"port\": 239}, {\"cidr\": \"10.0.0.198/31\", \"protocol\": \"udp\", \"port\": 365}, {\"cidr\": \"10.0.1.152/31\", \"protocol\": \"udp\", \"port\": 247}, {\"cidr\": \"10.0.0.170/32\", \"protocol\": \"udp\", \"port\": 79, \"endPort\": 82}, {\"cidr\": \"10.0.3.90/31\", \"protocol\": \"any\", \"port\": 97, \"endPort\": 98}, {\"cidr\": \"10.0.3.84/31\", \"protocol\": \"any\", \"port\": 333}, {\"cidr\": \"10.0.0.228/30\", \"protocol\": \"tcp\", \"port\": 133, \"endPort\": 137}, {\"cidr\": \"2001:db8::83/128\", \"protocol\": \"udp\", \"port\": 51, \"endPort\": 51}, {\"cidr\": \"10.0.2.111/32\", \"protocol\": \"udp\", \"port\": 19}, {\"cidr\": \"2001:db8::113/128\", \"protocol\": \"udp\", \"port\": 193}, {\"cidr\": \"10.0.2.138/32\", \"protocol\": \"udp\", \"port\": 189}, {\"cidr\": \"10.0.2.140/30\", \"protocol\": \"tcp\", \"port\": 67}, {\"cidr\": \"2001:db8::288/128\", \"protocol\": \"any\", \"port\": 89}, {\"cidr\": \"10.0.2.96/30\", \"protocol\": \"tcp\", \"port\": 307}, {\"cidr\": \"10.0.1.216/30\", \"protocol\": \"tcp\", \"port\": 131, \"endPort\": 131}, {\"cidr\": \"10.0.2.184/31\", \"protocol\": \"udp\", \"port\": 201}, {\"cidr\": \"2001:db8::3c/128\", \"protocol\": \"any\", \"port\": 295, \"endPort\": 298}, {\"cidr\": \"2001:db8::331/128\", \"protocol\": \"tcp\", \"port\": 131}, {\"cidr\": \"10.0.3.36/30\", \"protocol\": \"any\", \"port\": 263}, {\"cidr\": \"10.0.3.154/32\", \"protocol\": \"any\", \"port\": 31}, {\"cidr\": \"10.0.1.236/30\", \"protocol\": \"tcp\", \"port\": 217}, {\"cidr\": \"10.0.2.210/31\", \"protocol\": \"tcp\", \"port\": 383, \"endPort\": 384}, {\"cidr\": \"10.0.0.140/30\", \"protocol\": \"tcp\", \"port\": 397}, {\"cidr\": \"2001:db8::1dd/128\", \"protocol\": \"udp\", \"port\": 395}, {\"cidr\": \"10.0.0.184/30\", \"protocol\": \"tcp\", \"port\": 155}, {\"cidr\": \"2001:db8::200/128\", \"protocol\": \"any\", \"port\": 169, \"endPort\": 169}, {\"cidr\": \"10.0.2.136/30\", \"protocol\": \"tcp\", \"port\": 109}, {\"cidr\": \"2001:db8::1a9/128\", \"protocol\": \"udp\", \"port\": 239}, {\"cidr\": \"2001:db8::38a/128\", \"protocol\": \"udp\", \"port\": 25}, {\"cidr\": \"2001:db8::129/128\", \"protocol\": \"any\", \"port\": 49, \"endPort\": 53}, {\"cidr\": \"10.0.2.46/32\", \"protocol\": \"any\", \"port\": 93}, {\"cidr\": \"10.0.0.87/32\", \"protocol\": \"tcp\", \"port\": 95, \"endPort\": 97}, {\"cidr\": \"2001:db8::1e6/128\", \"protocol\": \"udp\", \"port\": 239}, {\"cidr\": \"10.0.2.236/31\", \"protocol\": \"tcp\", \"port\": 199, \"endPort\": 199}, {\"cidr\": \"10.0.2.56/30\", \"protocol\": \"udp\", \"port\": 135}, {\"cidr\": \"2001:db8::2c7/128\", \"protocol\": \"any\", \"port\": 111, \"endPort\": 114}, {\"cidr\": \"10.0.0.225/32\", \"protocol\": \"udp\", \"port\": 117, \"endPort\": 121}, {\"cidr\": \"10.0.1.196/32\", \"protocol\": \"any\", \"port\": 15}, {\"cidr\": \"2001:db8::3c4/128\", \"protocol\": \"tcp\", \"port\": 229}, {\"cidr\": \"10.0.2.242/31\", \"protocol\": \"any\", \"port\": 393}, {\"cidr\": \"10.0.0.2/31\", \"protocol\": \"tcp\", \"port\": 11}, {\"cidr\": \"10.0.3.230/31\", \"protocol\": \"udp\", \"port\": 313, \"endPort\": 314}, {\"cidr\": \"10.0.3.247/32\", \"protocol\": \"udp\", \"port\": 297}, {\"cidr\": \"10.0.0.28/30\", \"protocol\": \"udp\", \"port\": 85}, {\"cidr\": \"10.0.1.92/30\", \"protocol\": \"udp\", \"port\": 393}, {\"cidr\": \"10.0.1.129/32\", \"protocol\": \"tcp\", \"port\": 371, \"endPort\": 373}, {\"cidr\": \"10.0.2.54/32\", \"protocol\": \"udp\", \"port\": 143}, {\"cidr\": \"10.0.2.40/30\", \"protocol\": \"any\", \"port\": 253}, {\"cidr\": \"10.0.0.24/31\", \"protocol\": \"tcp\", \"port\": 55}, {\"cidr\": \"10.0.1.252/30\", \"protocol\": \"any\", \"port\": 95}, {\"cidr\": \"10.0.0.14/31\", \"protocol\": \"any\", \"port\": 33}, {\"cidr\": \"2001:db8::378/128\", \"protocol\": \"udp\", \"port\": 381}, {\"cidr\": \"10.0.2.184/30\", \"protocol\": \"udp\", \"port\": 335}, {\"cidr\": \"2001:db8::2e/128\", \"protocol\": \"tcp\", \"port\": 85}, {\"cidr\": \"10.0.2.118/31\", \"protocol\": \"tcp\", \"port\": 117}, {\"cidr\": \"10.0.0.0/32\", \"protocol\": \"udp\", \"port\": 299}, {\"cidr\": \"2001:db8::372/128\", \"protocol\": \"tcp\", \"port\": 257}, {\"cidr\": \"10.0.1.52/30\", \"protocol\": \"any\", \"port\": 103}, {\"cidr\": \"10.0.2.68/30\", \"protocol\": \"any\", \"port\": 69}, {\"cidr\": \"10.0.1.171/32\", \"protocol\": \"any\", \"port\": 365}, {\"cidr\": \"10.0.2.101/32\", \"protocol\": \"tcp\", \"port\": 113}, {\"cidr\": \"10.0.0.23/32\", \"protocol\": \"tcp\", \"port\": 273}, {\"cidr\": \"10.0.2.218/32\", \"protocol\": \"tcp\", \"port\": 157, \"endPort\": 158}, {\"cidr\": \"10.0.0.232/30\", \"protocol\": \"udp\", \"port\": 131}, {\"cidr\": \"10.0.1.204/31\", \"protocol\": \"tcp\", \"port\": 157}, {\"cidr\": \"2001:db8::186/128\", \"protocol\": \"tcp\", \"port\": 191}, {\"cidr\": \"2001:db8::187/128\", \"protocol\": \"tcp\", \"port\": 361}, {\"cidr\": \"10.0.3.34/31\", \"protocol\": \"udp\", \"port\": 5}, {\"cidr\": \"10.0.1.5/32\", \"protocol\": \"tcp\", \"port\": 383}, {\"cidr\": \"10.0.3.113/32\", \"protocol\": \"udp\", \"port\": 155, \"endPort\": 157}, {\"cidr\": \"2001:db8::2/128\", \"protocol\": \"any\", \"port\": 173}, {\"cidr\": \"2001:db8::1d2/128\", \"protocol\": \"tcp\", \"port\": 189}, {\"cidr\": \"10.0.1.4/32\", \"protocol\": \"tcp\", \"port\": 37, \"endPort\": 38}, {\"cidr\": \"10.0.1.2/31\", \"protocol\": \"tcp\", \"port\": 277, \"endPort\": 281}, {\"cidr\": \"2001:db8::f9/128\", \"protocol\": \"tcp\", \"port\": 351}, {\"cidr\": \"10.0.0.228/30\", \"protocol\": \"any\", \"port\": 145}, {\"cidr\": \"10.0.2.212/30\", \"protocol\": \"udp\", \"port\": 89}, {\"cidr\": \"10.0.0.76/30\", \"protocol\": \"any\", \"port\": 379, \"endPort\": 380}, {\"cidr\": \"2001:db8::2ed/128\", \"protocol\": \"udp\", \"port\": 155}, {\"cidr\": \"10.0.0.236/31\", \"protocol\": \"udp\", \"port\": 187}, {\"cidr\": \"10.0.2.160/32\", \"protocol\": \"tcp\", \"port\": 155}, {\"cidr\": \"10.0.1.18/31\", \"protocol\": \"tcp\", \"port\": 233}, {\"cidr\": \"10.0.0.2/32\", \"protocol\": \"udp\", \"port\": 13}, {\"cidr\": \"2001:db8::91/128\", \"protocol\": \"any\", \"port\": 209}, {\"cidr\": \"10.0.2.168/30\", \"protocol\": \"any\", \"port\": 315, \"endPort\": 317}, {\"cidr\": \"10.0.1.60/30\", \"protocol\": \"udp\", \"port\": 329}, {\"cidr\": \"10.0.3.176/30\", \"protocol\": \"tcp\", \"port\": 45}, {\"cidr\": \"10.0.0.24/30\", \"protocol\": \"any\", \"port\": 7, \"endPort\": 8}, {\"cidr\": \"10.0.0.71/32\", \"protocol\": \"tcp\", \"port\": 117}, {\"cidr\": \"10.0.0.47/32\", \"protocol\": \"any\", \"port\": 163}, {\"cidr\": \"10.0.1.73/32\", \"protocol\": \"tcp\", \"port\": 227}, {\"cidr\": \"10.0.1.32/30\", \"protocol\": \"tcp\", \"port\": 147}, {\"cidr\": \"10.0.0.60/30\", \"protocol\": \"any\", \"port\": 67}, {\"cidr\": \"2001:db8::126/128\", \"protocol\": \"any\", \"port\": 229, \"endPort\": 233}, {\"cidr\": \"10.0.2.232/31\", \"protocol\": \"tcp\", \"port\": 127}, {\"cidr\": \"2001:db8::236/128\", \"protocol\": \"udp\", \"port\": 49, \"endPort\": 52}, {\"cidr\": \"10.0.3.194/31\", \"protocol\": \"any\", \"port\": 395}, {\"cidr\": \"10.0.0.184/30\", \"protocol\": \"tcp\", \"port\": 55}, {\"cidr\": \"10.0.0.112/30\", \"protocol\": \"udp\", \"port\": 149}, {\"cidr\": \"2001:db8::2cf/128\", \"protocol\": \"any\", \"port\": 279, \"endPort\": 281}, {\"cidr\": \"10.0.3.132/30\", \"protocol\": \"tcp\", \"port\": 85, \"endPort\": 86}, {\"cidr\": \"10.0.0.90/31\", \"protocol\": \"udp\", \"port\": 21, \"endPort\": 22}, {\"cidr\": \"2001:db8::1f7/128\", \"protocol\": \"tcp\", \"port\": 143}, {\"cidr\": \"10.0.0.172/32\", \"protocol\": \"any\", \"port\": 363}, {\"cidr\": \"10.0.2.8/31\", \"protocol\": \"tcp\", \"port\": 85, \"endPort\": 87}, {\"cidr\": \"10.0.0.169/32\", \"protocol\": \"tcp\", \"port\": 219, \"endPort\": 219}, {\"cidr\": \"10.0.3.192/31\", \"protocol\": \"any\", \"port\": 343}, {\"cidr\": \"2001:db8::3c9/128\", \"protocol\": \"tcp\", \"port\": 209}, {\"cidr\": \"2001:db8::3c0/128\", \"protocol\": \"udp\", \"port\": 63, \"endPort\": 65}, {\"cidr\": \"10.0.2.0/30\", \"protocol\": \"udp\", \"port\": 71}, {\"cidr\": \"10.0.3.56/30\", \"protocol\": \"any\", \"port\": 139}, {\"cidr\": \"2001:db8::236/128\", \"protocol\": \"tcp\", \"port\": 361, \"endPort\": 365}, {\"cidr\": \"10.0.2.8/31\", \"protocol\": \"tcp\", \"port\": 257}, {\"cidr\": \"10.0.1.18/32\", \"protocol\": \"tcp\", \"port\": 355}, {\"cidr\": \"2001:db8::11d/128\", \"protocol\": \"tcp\", \"port\": 65, \"endPort\": 69}, {\"cidr\": \"10.0.0.98/31\", \"protocol\": \"udp\", \"port\": 179}, {\"cidr\": \"10.0.0.186/31\", \"protocol\": \"tcp\", \"port\": 157}, {\"cidr\": \"10.0.1.124/31\", \"protocol\": \"tcp\", \"port\": 133, \"endPort\": 137}, {\"cidr\": \"10.0.0.210/32\", \"protocol\": \"any\", \"port\": 61, \"endPort\": 61}, {\"cidr\": \"10.0.0.96/30\", \"protocol\": \"any\", \"port\": 241}, {\"cidr\": \"10.0.0.28/31\", \"protocol\": \"tcp\", \"port\": 335, \"endPort\": 339}, {\"cidr\": \"2001:db8::69/128\", \"protocol\": \"tcp\", \"port\": 203, \"endPort\": 207}, {\"cidr\": \"10.0.1.26/31\", \"protocol\": \"tcp\", \"port\": 389}, {\"cidr\": \"10.0.1.76/30\", \"protocol\": \"any\", \"port\": 219, \"endPort\": 220}, {\"cidr\": \"10.0.1.230/31\", \"protocol\": \"any\", \"port\": 203, \"endPort\": 204}, {\"cidr\": \"10.0.0.74/31\", \"protocol\": \"any\", \"port\": 65}, {\"cidr\": \"10.0.3.152/30\", \"protocol\": \"udp\", \"port\": 145}, {\"cidr\": \"10.0.3.10/31\", \"protocol\": \"tcp\", \"port\": 371}, {\"cidr\": \"10.0.1.134/32\", \"protocol\": \"udp\", \"port\": 3}, {\"cidr\": \"10.0.3.107/32\", \"protocol\": \"udp\", \"port\": 57}, {\"cidr\": \"2001:db8::2d5/128\", \"protocol\": \"any\", \"port\": 333}, {\"cidr\": \"10.0.1.185/32\", \"protocol\": \"tcp\", \"port\": 119}, {\"cidr\": \"10.0.2.42/32\", \"protocol\": \"any\", \"port\": 23}, {\"cidr\": \"10.0.0.65/32\", \"protocol\": \"tcp\", \"port\": 187, \"endPort\": 189}, {\"cidr\": \"10.0.2.244/31\", \"protocol\": \"any\", \"port\": 89}, {\"cidr\": \"2001:db8::3b9/128\", \"protocol\": \"tcp\", \"port\": 333}, {\"cidr\": \"10.0.3.220/31\", \"protocol\": \"udp\", \"port\": 103}, {\"cidr\": \"10.0.3.240/32\", \"protocol\": \"udp\", \"port\": 283, \"endPort\": 286}, {\"cidr\": \"10.0.3.40/31\", \"protocol\": \"tcp\", \"port\": 133}, {\"cidr\": \"10.0.0.230/31\", \"protocol\": \"any\", \"port\": 191}, {\"cidr\": \"2001:db8::3cf/128\", \"protocol\": \"tcp\", \"port\": 381, \"endPort\": 384}, {\"cidr\": \"2001:db8::172/128\", \"protocol\": \"udp\", \"port\": 395}, {\"cidr\": \"10.0.3.236/30\", \"protocol\": \"any\", \"port\": 95}, {\"cidr\": \"10.0.1.192/30\", \"protocol\": \"udp\", \"port\": 33}, {\"cidr\": \"10.0.0.148/31\", \"protocol\": \"any\", \"port\": 57, \"endPort\": 57}, {\"cidr\": \"10.0.2.202/32\", \"protocol\": \"any\", \"port\": 389}, {\"cidr\": \"10.0.3.16/31\", \"protocol\": \"tcp\", \"port\": 85}, {\"cidr\": \"10.0.2.98/31\", \"protocol\": \"udp\", \"port\": 183}, {\"cidr\": \"10.0.3.36/31\", \"protocol\": \"udp\", \"port\": 271, \"endPort\": 273}, {\"cidr\": \"10.0.3.234/31\", \"protocol\": \"tcp\", \"port\": 387}, {\"cidr\": \"10.0.3.146/31\", \"protocol\": \"any\", \"port\": 375}, {\"cidr\": \"2001:db8::ae/128\", \"protocol\": \"any\", \"port\": 365}, {\"cidr\": \"10.0.0.200/30\", \"protocol\": \"tcp\", \"port\": 239}, {\"cidr\": \"2001:db8::3e6/128\", \"protocol\": \"tcp\", \"port\": 29}, {\"cidr\": \"10.0.0.104/32\", \"protocol\": \"any\", \"port\": 257}, {\"cidr\": \"2001:db8::15b/128\", \"protocol\": \"any\", \"port\": 53}, {\"cidr\": \"10.0.0.86/31\", \"protocol\": \"any\", \"port\": 297, \"endPort\": 300}, {\"cidr\": \"2001:db8::2cc/128\", \"protocol\": \"tcp\", \"port\": 73}, {\"cidr\": \"10.0.2.3/32\", \"protocol\": \"udp\", \"port\": 3}, {\"cidr\": \"10.0.3.196/31\", \"protocol\": \"udp\", \"port\": 135}, {\"cidr\": \"10.0.1.216/30\", \"protocol\": \"any\", \"port\": 15}, {\"cidr\": \"2001:db8::6f/128\", \"protocol\": \"udp\", \"port\": 21}, {\"cidr\": \"2001:db8::3cc/128\", \"protocol\": \"udp\", \"port\": 169}, {\"cidr\": \"10.0.2.124/30\", \"protocol\": \"any\", \"port\": 81}, {\"cidr\": \"2001:db8::6d/128\", \"protocol\": \"udp\", \"port\": 79}, {\"cidr\": \"10.0.0.205/32\", \"protocol\": \"tcp\", \"port\": 243}, {\"cidr\": \"10.0.0.244/32\", \"protocol\": \"udp\", \"port\": 389, \"endPort\": 389}, {\"cidr\": \"10.0.3.228/30\", \"protocol\": \"udp\", \"port\": 43, \"endPort\": 43}, {\"cidr\": \"10.0.1.188/31\", \"protocol\": \"any\", \"port\": 165}, {\"cidr\": \"2001:db8::2ec/128\", \"protocol\": \"any\", \"port\": 39}, {\"cidr\": \"10.0.2.240/32\", \"protocol\": \"any\", \"port\": 259, \"endPort\": 260}, {\"cidr\": \"10.0.3.142/31\", \"protocol\": \"tcp\", \"port\": 365}, {\"cidr\": \"10.0.2.195/32\", \"protocol\": \"any\", \"port\": 213}, {\"cidr\": \"2001:db8::2bd/128\", \"protocol\": \"any\", \"port\": 139}, {\"cidr\": \"2001:db8::3a9/128\", \"protocol\": \"udp\", \"port\": 7}, {\"cidr\": \"10.0.1.104/30\", \"protocol\": \"tcp\", \"port\": 135}, {\"cidr\": \"10.0.1.56/30\", \"protocol\": \"any\", \"port\": 341}, {\"cidr\": \"10.0.1.47/32\", \"protocol\": \"udp\", \"port\": 227}, {\"cidr\": \"2001:db8::3a7/128\", \"protocol\": \"tcp\", \"port\": 161, \"endPort\": 162}, {\"cidr\": \"2001:db8::20a/128\", \"protocol\": \"udp\", \"port\": 337}, {\"cidr\": \"2001:db8::144/128\", \"protocol\": \"any\", \"port\": 133, \"endPort\": 133}, {\"cidr\": \"10.0.0.128/31\", \"protocol\": \"udp\", \"port\": 197}, {\"cidr\": \"2001:db8::1d4/128\", \"protocol\": \"tcp\", \"port\": 337, \"endPort\": 340}, {\"cidr\": \"10.0.2.198/32\", \"protocol\": \"tcp\", \"port\": 381}, {\"cidr\": \"2001:db8::259/128\", \"protocol\": \"tcp\", \"port\": 23, \"endPort\": 24}, {\"cidr\": \"10.0.1.202/31\", \"protocol\": \"tcp\", \"port\": 19}, {\"cidr\": \"10.0.2.88/30\", \"protocol\": \"udp\", \"port\": 15}, {\"cidr\": \"10.0.0.232/30\", \"protocol\": \"tcp\", \"port\": 217}, {\"cidr\": \"10.0.1.28/30\", \"protocol\": \"udp\", \"port\": 191, \"endPort\": 191}, {\"cidr\": \"10.0.1.116/32\", \"protocol\": \"udp\", \"port\": 301, \"endPort\": 305}, {\"cidr\": \"2001:db8::225/128\", \"protocol\": \"udp\", \"port\": 325}, {\"cidr\": \"2001:db8::339/128\", \"protocol\": \"tcp\", \"port\": 297}, {\"cidr\": \"10.0.2.116/30\", \"protocol\": \"udp\", \"port\": 287}, {\"cidr\": \"10.0.0.232/31\", \"protocol\": \"any\", \"port\": 277}, {\"cidr\": \"10.0.0.56/30\", \"protocol\": \"any\", \"port\": 167, \"endPort\": 171}, {\"cidr\": \"10.0.2.62/31\", \"protocol\": \"udp\", \"port\": 353}, {\"cidr\": \"10.0.2.254/31\", \"protocol\": \"udp\", \"port\": 393}, {\"cidr\": \"10.0.2.6/32\", \"protocol\": \"udp\", \"port\": 247}, {\"cidr\": \"10.0.2.190/31\", \"protocol\": \"any\", \"port\": 227, \"endPort\": 231}, {\"cidr\": \"2001:db8::2d3/128\", \"protocol\": \"any\", \"port\": 23}, {\"cidr\": \"10.0.1.100/32\", \"protocol\": \"udp\", \"port\": 279, \"endPort\": 281}, {\"cidr\": \"10.0.0.196/30\", \"protocol\": \"tcp\", \"port\": 9, \"endPort\": 11}, {\"cidr\": \"10.0.2.128/31\", \"protocol\": \"udp\", \"port\": 343}, {\"cidr\": \"2001:db8::377/128\", \"protocol\": \"udp\", \"port\": 83}, {\"cidr\": \"2001:db8::c5/128\", \"protocol\": \"udp\", \"port\": 199}, {\"cidr\": \"2001:db8::2ea/128\", \"protocol\": \"any\", \"port\": 21}, {\"cidr\": \"10.0.1.156/31\", \"protocol\": \"tcp\", \"port\": 179}, {\"cidr\": \"2001:db8::340/128\", \"protocol\": \"any\", \"port\": 301}, {\"cidr\": \"10.0.0.248/30\", \"protocol\": \"tcp\", \"port\": 119}, {\"cidr\": \"10.0.1.176/30\", \"protocol\": \"udp\", \"port\": 139}, {\"cidr\": \"10.0.1.198/32\", \"protocol\": \"tcp\", \"port\": 121}, {\"cidr\": \"10.0.1.112/31\", \"protocol\": \"tcp\", \"port\": 145}, {\"cidr\": \"2001:db8::4f/128\", \"protocol\": \"tcp\", \"port\": 295}, {\"cidr\": \"2001:db8::4/128\", \"protocol\": \"udp\", \"port\": 199, \"endPort\": 203}, {\"cidr\": \"10.0.0.80/31\", \"protocol\": \"any\", \"port\": 323}, {\"cidr\": \"10.0.0.250/31\", \"protocol\": \"udp\", \"port\": 79}, {\"cidr\": \"10.0.3.26/31\", \"protocol\": \"any\", \"port\": 301, \"endPort\": 301}, {\"cidr\": \"2001:db8::1dd/128\", \"protocol\": \"udp\", \"port\": 339, \"endPort\": 340}, {\"cidr\": \"10.0.1.45/32\", \"protocol\": \"any\", \"port\": 141}, {\"cidr\": \"2001:db8::f8/128\", \"protocol\": \"udp\", \"port\": 59}, {\"cidr\": \"2001:db8::ab/128\", \"protocol\": \"any\", \"port\": 293, \"endPort\": 293}, {\"cidr\": \"2001:db8::f7/128\", \"protocol\": \"udp\", \"port\": 33}, {\"cidr\": \"10.0.2.36/32\", \"protocol\": \"any\", \"port\": 371, \"endPort\": 372}, {\"cidr\": \"2001:db8::2c6/128\", \"protocol\": \"tcp\", \"port\": 351, \"endPort\": 352}, {\"cidr\": \"2001:db8::1c3/128\", \"protocol\": \"any\", \"port\": 145}, {\"cidr\": \"2001:db8::28e/128\", \"protocol\": \"tcp\", \"port\": 299}, {\"cidr\": \"10.0.3.148/30\", \"protocol\": \"tcp\", \"port\": 287}, {\"cidr\": \"10.0.1.160/32\", \"protocol\": \"any\", \"port\": 395, \"endPort\": 397}, {\"cidr\": \"10.0.2.228/30\", \"protocol\": \"tcp\", \"port\": 119}, {\"cidr\": \"10.0.0.116/31\", \"protocol\": \"udp\", \"port\": 153, \"endPort\": 155}, {\"cidr\": \"10.0.1.56/31\", \"protocol\": \"any\", \"port\": 1, \"endPort\": 4}, {\"cidr\": \"2001:db8::11d/128\", \"protocol\": \"any\", \"port\": 71}, {\"cidr\": \"2001:db8::3a1/128\", \"protocol\": \"any\", \"port\": 341, \"endPort\": 342}, {\"cidr\": \"10.0.3.180/31\", \"protocol\": \"tcp\", \"port\": 213}, {\"cidr\": \"10.0.2.102/31\", \"protocol\": \"any\", \"port\": 385}, {\"cidr\": \"10.0.3.104/30\", \"protocol\": \"tcp\", \"port\": 377}, {\"cidr\": \"2001:db8::1c7/128\", \"protocol\": \"any\", \"port\": 237}, {\"cidr\": \"10.0.3.89/32\", \"protocol\": \"any\", \"port\": 173}, {\"cidr\": \"2001:db8::cd/128\", \"protocol\": \"udp\", \"port\": 77}, {\"cidr\": \"2001:db8::3d4/128\", \"protocol\": \"udp\", \"port\": 397}, {\"cidr\": \"2001:db8::244/128\", \"protocol\": \"udp\", \"port\": 77}, {\"cidr\": \"10.0.1.169/32\", \"protocol\": \"tcp\", \"port\": 279, \"endPort\": 283}, {\"cidr\": \"10.0.3.86/31\", \"protocol\": \"udp\", \"port\": 17}, {\"cidr\": \"2001:db8::55/128\", \"protocol\": \"udp\", \"port\": 13}, {\"cidr\": \"2001:db8::21b/128\", \"protocol\": \"udp\", \"port\": 355}, {\"cidr\": \"10.0.1.208/31\", \"protocol\": \"tcp\", \"port\": 123}, {\"cidr\": \"10.0.1.184/30\", \"protocol\": \"any\", \"port\": 191}, {\"cidr\": \"2001:db8::111/128\", \"protocol\": \"any\", \"port\": 355}, {\"cidr\": \"2001:db8::f1/128\", \"protocol\": \"tcp\", \"port\": 395}, {\"cidr\": \"10.0.1.244/30\", \"protocol\": \"tcp\", \"port\": 27}, {\"cidr\": \"10.0.1.12/30\", \"protocol\": \"any\", \"port\": 241}, {\"cidr\": \"10.0.3.16/31\", \"protocol\": \"udp\", \"port\": 333, \"endPort\": 334}, {\"cidr\": \"10.0.2.252/30\", \"protocol\": \"tcp\", \"port\": 167}, {\"cidr\": \"10.0.0.170/32\", \"protocol\": \"udp\", \"port\": 311}, {\"cidr\": \"10.0.3.46/31\", \"protocol\": \"any\", \"port\": 55}, {\"cidr\": \"2001:db8::156/128\", \"protocol\": \"udp\", \"port\": 311}, {\"cidr\": \"10.0.3.252/31\", \"protocol\": \"any\", \"port\": 177, \"endPort\": 179}, {\"cidr\": \"2001:db8::58/128\", \"protocol\": \"udp\", \"port\": 137, \"endPort\": 140}, {\"cidr\": \"10.0.2.10/31\", \"protocol\": \"udp\", \"port\": 259}, {\"cidr\": \"10.0.3.64/31\", \"protocol\": \"udp\", \"port\": 189, \"endPort\": 189}, {\"cidr\": \"10.0.0.49/32\", \"protocol\": \"tcp\", \"port\": 123}]}"}],"expected":{"allow":["10.0.0.0/24","10.0.2.0/31","10.0.3.130/31","2001:db8::118/127","2001:db8::2c5/128"],"deny":["0.0.0.0/0:icmp","0.0.0.0/0:tcp","0.0.0.0/0:udp:81","0.0.0.0/0:udp:444-3435","10.0.0.0/32:udp:299","10.0.0.2/32:udp:13","10.0.0.11/32:udp:223","10.0.0.14/31:udp:33","10.0.0.24/30:udp:7-8","10.0.0.28/30:udp:85","10.0.0.46/32:udp:225","10.0.0.47/32:udp:163","10.0.0.56/30:udp:167-171","10.0.0.60/30:udp:67","10.0.0.74/31:udp:65","10.0.0.76/30:udp:379-380","10.0.0.80/31:udp:323","10.0.0.86/31:udp:297-300","10.0.0.90/31:udp:21-22","10.0.0.96/30:udp:241","10.0.0.98/31:udp:179","10.0.0.104/32:udp:257","10.0.0.112/30:udp:149","10.0.0.116/31:udp:153-155","10.0.0.122/31:udp:249","10.0.0.124/30:udp:243","10.0.0.128/31:udp:197","10.0.0.148/31:udp:57","10.0.0.170/32:udp:79-82","10.0.0.170/32:udp:311","10.0.0.172/32:udp:363","10.0.0.198/31:udp:365","10.0.0.210/32:udp:61","10.0.0.225/32:udp:117-121","10.0.0.228/30:udp:145","10.0.0.230/31:udp:191","10.0.0.232/30:udp:131","10.0.0.232/31:udp:277","10.0.0.236/31:udp:187","10.0.0.244/32:udp:389","10.0.0.250/31:udp:79","10.0.1.12/30:udp:241","10.0.1.28/30:udp:191","10.0.1.40/30:udp:25","10.0.1.45/32:udp:141","10.0.1.46/32:udp:239","10.0.1.47/32:udp:227","10.0.1.52/30:udp:103","10.0.1.56/30:udp:341","10.0.1.56/31:udp:1-4","10.0.1.60/30:udp:329","10.0.1.76/30:udp:219-220","10.0.1.92/30:udp:393","10.0.1.96/31:udp:189","10.0.1.100/32:udp:279-281","10.0.1.113/32:udp:327-331","10.0.1.116/32:udp:301-305","10.0.1.134/32:udp:3","10.0.1.152/31:udp:247","10.0.1.160/27","10.0.1.192/30:udp:33","10.0.1.196/32:udp:15","10.0.1.216/30:udp:15","10.0.1.230/31:udp:203-204","10.0.1.248/30:udp:291","10.0.1.252/30:udp:95","10.0.2.0/30:udp:71","10.0.2.3/32:udp:3","10.0.2.6/32:udp:247","10.0.2.10/31:udp:259","10.0.2.36/32:udp:371-372","10.0.2.40/30:udp:253","10.0.2.42/32:udp:23","10.0.2.46/32:udp:93","10.0.2.54/32:udp:143","10.0.2.56/30:udp:135","10.0.2.62/31:udp:353","10.0.2.68/30:udp:69","10.0.2.88/30:udp:15","10.0.2.98/31:udp:183","10.0.2.102/31:udp:385","10.0.2.108/32:udp:179","10.0.2.111/32:udp:19","10.0.2.116/30:udp:287","10.0.2.128/29","10.0.2.136/30","10.0.2.140/31","10.0.2.144/28","10.0.2.160/28","10.0.2.176/29","10.0.2.184/30","10.0.2.190/31:udp:227-231","10.0.2.195/32:udp:213","10.0.2.202/32:udp:389","10.0.2.212/30:udp:89","10.0.2.230/31:udp:265","10.0.2.240/32:udp:259-260","10.0.2.242/31:udp:393","10.0.2.244/31:udp:89","10.0.2.254/31:udp:393","10.0.3.16/31:udp:333-334","10.0.3.26/31:udp:301","10.0.3.28/32:udp:279","10.0.3.34/31:udp:5","10.0.3.36/30:udp:263","10.0.3.36/31:udp:271-273","10.0.3.46/31:udp:55","10.0.3.56/30:udp:139","10.0.3.64/31:udp:189","10.0.3.84/31:udp:333","10.0.3.86/31:udp:17","10.0.3.89/32:udp:173","10.0.3.90/31:udp:97-98","10.0.3.107/32:udp:57","10.0.3.113/32:udp:155-157","10.0.3.146/31:udp:7","10.0.3.146/31:udp:375","10.0.3.152/30:udp:145","10.0.3.154/32:udp:31","10.0.3.192/31:udp:343","10.0.3.194/31:udp:395","10.0.3.196/31:udp:135","10.0.3.214/32:udp:199-203","10.0.3.220/31:udp:103","10.0.3.228/30:udp:43","10.0.3.230/31:udp:313-314","10.0.3.236/30:udp:95","10.0.3.240/32:udp:283-286","10.0.3.247/32:udp:297","10.0.3.252/31:udp:177-179","::/0:icmp","::/0:tcp","::/0:udp:81","::/0:udp:444-3435","2001:db8::2/128:udp:173","2001:db8::4/128:udp:199-203","2001:db8::3c/128:udp:295-298","2001:db8::40/122","2001:db8::83/128:udp:51","2001:db8::91/128:udp:209","2001:db8::a0/128:udp:287","2001:db8::ab/128:udp:293","2001:db8::ae/128:udp:365","2001:db8::c5/128:udp:199","2001:db8::cd/128:udp:77","2001:db8::f7/128:udp:33","2001:db8::f8/128:udp:59","2001:db8::111/128:udp:355","2001:db8::113/128:udp:193","2001:db8::11d/128:udp:71","2001:db8::126/128:udp:229-233","2001:db8::129/128:udp:49-53","2001:db8::144/128:udp:133","2001:db8::156/128:udp:311","2001:db8::15b/128:udp:53","2001:db8::172/128:udp:395","2001:db8::188/128:udp:321-324","2001:db8::1a9/128:udp:239","2001:db8::1c3/128:udp:145","2001:db8::1c7/128:udp:237","2001:db8::1dd/128:udp:339-340","2001:db8::1dd/128:udp:395","2001:db8::1e6/128:udp:239","2001:db8::200/128:udp:169","2001:db8::20a/128:udp:337","2001:db8::21b/128:udp:355","2001:db8::225/128:udp:325","2001:db8::236/128:udp:49-52","2001:db8::244/128:udp:77","2001:db8::288/128:udp:89","2001:db8::2bd/128:udp:139","2001:db8::2c7/128:udp:111-114","2001:db8::2cf/128:udp:279-281","2001:db8::2d3/128:udp:23","2001:db8::2d5/128:udp:333","2001:db8::2ea/128:udp:21","2001:db8::2ec/128:udp:39","2001:db8::2ed/128:udp:155","2001:db8::340/128:udp:301","2001:db8::377/128:udp:83","2001:db8::378/128:udp:381","2001:db8::382/128:udp:219-223","2001:db8::38a/128:udp:25","2001:db8::3a1/128:udp:341-342","2001:db8::3a9/128:udp:7","2001:db8::3c0/128:udp:63-65","2001:db8::3cc/128:udp:169","2001:db8::3d4/128:udp:397","2001:db8::3da/128:udp:273"]}}, + {"name":"random #145","input":[{"text":"{\"deny\":[]}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #146","input":[{"text":"{\"allow\": [], \"allow\": [{\"cidr\": \"c045:a4e3:d0d0:372f:484e:11:e92b:5e00/120\", \"protocol\": \"tcp\", \"port\": 443, \"endPort\": 45408}, {\"cidr\": \"250.35.11.17/0\"}, {\"to\": [{\"cidr\": \"178.0.0.0/8\"}]}, {\"to\": [{\"cidr\": \"2001:db8::224/128\"}, {\"cidr\": \"10.0.2.128/00027\", \"except\": [\"10.0.2.144/28\", \"10.0.2.128/27\", \"10.0.2.128/28\"]}, {\"cidr\": \"e000::/3\", \"except\": [\"fb9e:4235:aae5:632e:669f:f403:de0:6414/08\"]}, {\"cidr\": \"10.0.0.0/00022\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 7, \"endPort\": 8568}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 65534}]}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.0.0/25\"}, {\"cidr\": \"117.212.204.0/24\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 65531, \"endPort\": 65531}]}]}"}],"expected":{"error":"duplicate JSON property: allow"}}, + {"name":"random #147","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 445, \n\t\t\t\t\t\"endPort\": 450\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::161/125\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.192/26\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.224/27\", \n\t\t\t\t\t\t\"10.0.1.192/27\", \n\t\t\t\t\t\t\"10.0.1.254/31\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"454D:6B9D:7AE1:FC58:D963:EE21:245E:40C2/120\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::300/00121\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::336/128\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::120/126\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::123\", \n\t\t\t\t\t\t\"2001:db8::120/126\", \n\t\t\t\t\t\t\"2001:db8::122/128\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65535\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t], \n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.26/00031\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.32/28\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"162.104.211.148/1\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["0.0.0.0/0:tcp","::/0:tcp","2001:db8::160/125","2001:db8::300/121:icmp","2001:db8::300/121:udp:65535","454d:6b9d:7ae1:fc58:d963:ee21:245e:4000/120:icmp","454d:6b9d:7ae1:fc58:d963:ee21:245e:4000/120:udp:65535"],"deny":["10.0.0.26/31","10.0.0.32/28","128.0.0.0/1"]}}, + {"name":"random #148","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"96.0.0.0/4\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.32/28\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\",\n\t\t\t\t\t\"port\": 10\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"4220:2525:3b7a:b024:40e0:4161:6ec5:c300/120\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 65535\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::130/124\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.189/22\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\",\n\t\t\t\t\t\"port\": 81,\n\t\t\t\t\t\"endPort\": 83\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 65530,\n\t\t\t\t\t\"endPort\": 65534\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 6,\n\t\t\t\t\t\"endPort\": 9\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 80\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 3\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.2.240/255.255.255.224\",\n\t\t\t\"except\": [\n\t\t\t\t\"10.0.2.249\"\n\t\t\t],\n\t\t\t\"protocol\": \"any\",\n\t\t\t\"port\": 80,\n\t\t\t\"endPort\": 82\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::d0/126\",\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 442\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.32/28\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 7\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"port\": 2,\n\t\t\t\t\t\"endPort\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.104\",\n\t\t\t\"except\": [\n\t\t\t\t\"10.0.1.104/32\"\n\t\t\t],\n\t\t\t\"protocol\": \"tcp\",\n\t\t\t\"port\": 65533\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.0/24\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 3,\n\t\t\t\t\t\"endPort\": 5\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"125.6.187.218/0.0.0.1\",\n\t\t\t\"protocol\": \"tcp\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0280/121\",\n\t\t\t\"protocol\": \"tcp\"\n\t\t}\n\t]\n}"}],"expected":{"error":"allow[2].ports[0].port is not supported with icmp"}}, + {"name":"random #149","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"885b:4af7::/32\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 443\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.0/24\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.65\", \n\t\t\t\t\t\t\"10.0.1.188/30\", \n\t\t\t\t\t\t\"10.0.1.252/32\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.188\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.9/31\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.144/29\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.2.144/29\", \n\t\t\t\t\t\t\"10.0.2.150/31\", \n\t\t\t\t\t\t\"10.0.2.149/031\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"port\": 79\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"port\": 65530\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 79, \n\t\t\t\t\t\"endPort\": 9326\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.147/30\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 65531\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 441\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 11, \n\t\t\t\t\t\"endPort\": 14\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"2001:DB8::3AA/122\", \n\t\t\t\"protocol\": \"any\"\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"518d:43af:6a29:c3b:c27f:24fa::/96\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"518d:43af:6a29:0c3b:c27f:24fa:0000:0000/97\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.128/28\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.37.26.30\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"145.123.126.13/31\", \n\t\t\t\"protocol\": \"tcp\", \n\t\t\t\"port\": 82\n\t\t}\n\t], \n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"23c1:b37c:a7ca:91f8:d4fb:1710:b26:4272/1\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"5000::/4\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"0.0.0.0/1\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 440, \n\t\t\t\t\t\"endPort\": 55152\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 443, \n\t\t\t\t\t\"endPort\": 445\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65534, \n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 1, \n\t\t\t\t\t\"endPort\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.105/28\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::100/000121\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"128.0.0.0/240.0.0.0\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.156/30\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65530\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 1\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.0.144/30:tcp:11-14","10.0.0.144/30:tcp:65531","10.0.0.144/30:udp:11-14","10.0.0.144/30:udp:441","10.0.1.0/26","10.0.1.64/32","10.0.1.66/31","10.0.1.68/30","10.0.1.72/29","10.0.1.80/28","10.0.1.96/27","10.0.1.128/27","10.0.1.160/28","10.0.1.176/29","10.0.1.184/30","10.0.1.192/27","10.0.1.224/28","10.0.1.240/29","10.0.1.248/30","10.0.1.253/32","10.0.1.254/31","10.0.2.188/32","10.0.3.8/31","10.37.26.30/32","145.123.126.12/31:tcp:82","2001:db8::380/122","518d:43af:6a29:c3b:c27f:24fa:8000:0/97","885b:4af7::/32:icmp","885b:4af7::/32:tcp"],"deny":["0.0.0.0/0:tcp","0.0.0.0/0:udp:65534-65535","0.0.0.0/1:udp:443-445","10.0.0.156/30:icmp","10.0.0.156/30:udp:65530","10.0.3.96/28:icmp","10.0.3.96/28:udp:65530","128.0.0.0/4:icmp","128.0.0.0/4:udp:65530","::/0:tcp","::/0:udp:65534-65535","::/2:udp:443-445","2001:db8::100/121:icmp","2001:db8::100/121:udp:65530","4000::/4:udp:443-445","6000::/3:udp:443-445"]}}, + {"name":"random #150","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"2001:DB8::300/123\"}, {\"cidr\": \"2001:db8::10/126\", \"except\": [\"2001:db8::12/127\", \"2001:DB8::11\"]}, {\"cidr\": \"10.0.1.212/32\"}], \"ports\": [{\"protocol\": \"udp\", \"port\": 11, \"endPort\": 13}, {\"protocol\": \"tcp\", \"port\": 442, \"endPort\": 443}]}, {\"cidr\": \"10.0.0.0/22\", \"protocol\": \"tcp\", \"port\": 79}, {\"cidr\": \"10.0.1.92/30\", \"protocol\": \"any\", \"port\": 4}, {\"cidr\": \"2001:DB8::70/124\", \"protocol\": \"udp\"}, {\"to\": [{\"cidr\": \"10.0.2.64/26\", \"except\": [\"10.0.2.104/0.0.0.7\"]}, {\"cidr\": \"2001:DB8::140/122\"}, {\"cidr\": \"8aae:1c17:5def:95fb:3efa:5079:b0f3:ec00/120\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 443}, {\"protocol\": \"udp\", \"port\": 8}]}, {}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.2.32/027\"}, {\"cidr\": \"10.0.0.0/255.255.254.0\", \"except\": []}, {\"cidr\": \"10.0.1.224/27\"}]}, {\"cidr\": \"10.0.0.201\", \"except\": [\"10.0.0.201\", \"10.0.0.201\", \"10.0.0.201\"], \"protoc"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #151","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"c179:f3fc:c0b8:b589:bdcf:49a4:5100:0/000104\"\n },\n {\n \"cidr\": \"2001:db8::/118\"\n },\n {\n \"cidr\": \"10.0.1.0/255.255.255.0\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"any\",\n \"port\": 442,\n \"endPort\": 446\n }\n ]\n },\n {\n \"cidr\": \"217.153.117.185/2\",\n \"except\": [],\n \"protocol\": \"udp\"\n },\n {\n \"cidr\": \"2001:db8::180/121\",\n \"except\": [\n \"2001:db8::1ca/127\",\n \"2001:db8::1c0/124\"\n ],\n \"protocol\": \"tcp\"\n },\n {\n \"cidr\": \"7e3e:abe8:18b2:dc19:7b44:f0e::/96\",\n \"except\": [\n \"7e3e:abe8:18b2:dc19:7b44:f0e:8000:0/97\",\n \"7e3e:abe8:18b2:dc19:7b44:f0e:2200:0/103\"\n ],\n \"protocol\": \"tcp\"\n },\n {\n \"cidr\": \"2001:DB8::360/126\"\n },\n {\n \"ports\": [\n {\n \"protocol\": \"tcp\",\n \"port\": 5,\n \"endPort\": 6\n },\n {\n \"protocol\": \"any\"\n },\n {\n \"protocol\": \"tcp\",\n \"port\": 444,\n \"endPort\": 447\n },\n {\n \"protocol\": \"icmp\"\n }\n ]\n },\n {\n \"cidr\": \"2001:db8::1e0/123\",\n \"except\": [\n \"2001:db8::1e0/124\"\n ],\n \"protocol\": \"icmp\"\n },\n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::/119\",\n \"except\": [\n \"2001:db8::1a1/124\",\n \"2001:db8::1ae/127\"\n ]\n },\n {\n \"cidr\": \"10.0.3.98/255.255.255.254\"\n },\n {\n \"cidr\": \"10.0.2.105/24\"\n },\n {\n \"cidr\": \"10.0.2.210/255.255.255.255\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"any\",\n \"port\": 444\n },\n {\n \"protocol\": \"icmp\"\n },\n {}\n ]\n }\n ],\n \"deny\": [\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.30/00031\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"tcp\",\n \"port\": 65533\n },\n {\n \"protocol\": \"any\",\n \"port\": 12\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.1.128/25\",\n \"except\": [\n \"10.0.1.192/29\"\n ]\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"udp\",\n \"port\": 441\n },\n {\n \"protocol\": \"udp\"\n },\n {\n \"protocol\": \"udp\",\n \"port\": 65534\n },\n {\n \"protocol\": \"tcp\"\n }\n ]\n },\n {\n \"cidr\": \"10.0.1.138/026\",\n \"protocol\": \"any\",\n \"port\": 82\n },\n {\n \"cidr\": \"144.175.239.16/31\",\n \"except\": [\n \"144.175.239.16\"\n ]\n },\n {\n \"cidr\": \"161.210.189.160/4\",\n \"protocol\": \"udp\"\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.132/31\",\n \"except\": []\n },\n {\n \"cidr\": \"2001:db8::/119\"\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"tcp\"\n },\n {\n \"protocol\": \"any\",\n \"port\": 80\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01b0/124\"\n },\n {\n \"cidr\": \"10.0.1.0/26\",\n \"except\": []\n }\n ],\n \"ports\": [\n {\n \"protocol\": \"any\"\n },\n {\n \"protocol\": \"tcp\",\n \"port\": 445\n },\n {\n \"port\": 78,\n \"endPort\": 84\n }\n ]\n },\n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.139/32\"\n },\n {\n \"cidr\": \"8354:b120:72a8:d18b:b30f:c658:f5d1:df72/0000\",\n \"except\": []\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["10.0.1.0/26","10.0.1.128/26:tcp","10.0.1.128/26:udp","10.0.1.200/29:tcp","10.0.1.200/29:udp","10.0.1.208/28:tcp","10.0.1.208/28:udp","10.0.1.224/27:tcp","10.0.1.224/27:udp","10.0.3.30/31:tcp:12","10.0.3.30/31:tcp:65533","10.0.3.30/31:udp:12","10.0.3.132/31:tcp","10.0.3.132/31:udp:80","10.0.3.139/32","144.175.239.17/32","160.0.0.0/4:udp","::/0"]}}, + {"name":"random #152","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #153","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"tcp\", \"port\": NaN}, {\"protocol\": \"udp\", \"port\": 445}, {\"protocol\": \"any\", \"port\": 82, \"endPort\": 86}]}, {\"cidr\": \"10.0.0.0/24\", \"protocol\": \"icmp\"}, {\"cidr\": \"a000::/3\", \"protocol\": \"udp\", \"port\": 78, \"endPort\": 84}, {\"to\": [{\"cidr\": \"0000:0000:0000:0000:0000:0000:0000:0000/1\"}, {\"cidr\": \"10.0.1.245/28\", \"except\": []}, {\"cidr\": \"10.0.0.0/23\", \"except\": [\"10.0.0.92/0.0.0.3\", \"10.0.1.164/30\"]}, {\"cidr\": \"10.0.2.192/27\", \"except\": [\"10.0.2.192/29\"]}]}, {\"to\": [{\"cidr\": \"10.0.1.126/32\"}, {\"cidr\": \"10.0.0.160/255.255.255.248\"}]}, {\"cidr\": \"10.0.1.157/25\", \"protocol\": \"any\"}, {\"to\": [{\"cidr\": \"2001:DB8::200/121\", \"except\": [\"2001:db8::262/127\", \"2001:db8::26f/126\", \"2001:db8::268/000125\"]}, {\"cidr\": \"10.0.2.60/30\"}, {\"cidr\": \"10.0.3.64/28\"}, {\"cidr\": \"128.0.0.0/1\", \"except\": [\"238.0.0.0/7\", \"169.0.0.0/8\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 65531, \"endPort\": 65535}]}, {\"to\": [{\"cidr\": \"10.0.1.184/30\"}, {\"cidr\": \"10.0.2.4/30\"}], \"ports\": [{\"protocol\": \"tcp\"}]}]}"}],"expected":{"error":"allow[0].ports[0].port must be an integer"}}, + {"name":"random #154","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:016c\"\n },\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0280/122\"\n },\n {\n \"cidr\": \"207.132.31.128/255.255.255.0\",\n \"except\": []\n }\n ]\n }\n ],\n \"deny\": []\n}"}],"expected":{"allow":["207.132.31.0/24","2001:db8::16c/128","2001:db8::280/122"],"deny":[]}}, + {"name":"random #155","input":[{"text":"{\"allow\":[{\"ports\":[{\"protocol\":\"udp\",\"port\":441,\"endPort\":447},{\"protocol\":\"icmp\"},{\"protocol\":\"tcp\",\"port\":81},{\"protocol\":\"any\"}]},{\"cidr\":\"10.0.1.128/255.255.255.192\",\"except\":[\"10.0.1.170\",\"10.0.1.184/0.0.0.7\"],\"protocol\":\"udp\"},{\"to\":[{\"cidr\":\"2001:db8::/118\"},{\"cidr\":\"2001:db8::1f0/125\"},{\"cidr\":\"80.16.115.0/24\"},{\"cidr\":\"2001:db8::31f/118\"}]},{\"cidr\":\"87.33.76.82\",\"protocol\":\"tcp\",\"port\":5},{\"cidr\":\"10.0.0.120/29\",\"protocol\":\"tcp\"},{\"to\":[{\"cidr\":\"10.0.1.0/26\"},{\"cidr\":\"6e9a:1990:300:2231:e753:ea37:e25:2f23/128\"},{\"cidr\":\"10.0.1.128/25\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":5},{\"protocol\":\"tcp\"},{\"protocol\":\"udp\",\"port\":445}]}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.3.128/25\",\"except\":[\"10.0.3.178/032\"]}],\"ports\":[{\"protocol\":\"udp\",\"port\":7}]}]}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["10.0.3.128/27:udp:7","10.0.3.160/28:udp:7","10.0.3.176/31:udp:7","10.0.3.179/32:udp:7","10.0.3.180/30:udp:7","10.0.3.184/29:udp:7","10.0.3.192/26:udp:7"]}}, + {"name":"random #156","input":[{"text":"{\"allow\": [], \"deny\": [{\"to\": [{\"cidr\": \"31.37.208.219/4\"}, {\"cidr\": \"10.0.2.193/27\"}, {\"cidr\": \"10.0.3.152/29\"}, {\"cidr\": \"25.187.177.169/0\"}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"any\", \"port\": 12}, {\"protocol\": \"icmp\"}]}, {}, {\"to\": [{\"cidr\": \"10.0.0.0/22\"}, {\"cidr\": \"10.0.0.0/22\", \"except\": [\"10.0.0.224/255.255.255.224\"]}, {\"cidr\": \"10.0.1.72/31\"}]}, {\"to\": [{\"cidr\": \"10.0.2.198/31\", \"except\": []}, {\"cidr\": \"10.0.0.140/22\"}]"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #157","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.0.160/27\"}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:00f0/124\"}, {\"cidr\": \"10.0.1.99/32\", \"except\": [\"10.0.1.99\", \"10.0.1.99/32\"]}], \"ports\": [{\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"2001:db8::/121\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"udp\"}]}, {\"to\": [{\"cidr\": \"10.0.2.0/23\"}], \"ports\": [{\"protocol\": \"udp\"}, {\"protocol\": \"udp\", \"port\": 80}, {\"protocol\": \"icmp\"}]}, {\"to\": [{\"cidr\": \"175.171.0.0/16\"}, {\"cidr\": \"10.0.2.0/0.0.1.255\", \"except\": [\"::/0\", \"10.0.2.88/29\"]}, {\"cidr\": \"::ffff:39.0.0.0/104\", \"except\": []}, {\"cidr\": \"2001:db8::/118\"}]}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.3.228/22\", \"except\": [\"10.0.3.183/0.0.0.15\", \"10.0.1.64/26\", \"10.0.3.32/27\"]}, {\"cidr\": \"10.0.0.0/25\"}, {\"cidr\": \"10.0.1.64/255.255.255.224\", \"except\": [\"10.0.1.69/00032\"]}, {\"cidr\": \"34.213.155.127/24\", \"except\": []}], \"ports\": [{\"protocol\": \"any\", \"port\": 80, \"endPort\": 86}, {\"protocol\": \"udp\", \"port\": 443}]}]}"}],"expected":{"error":"allow[3].to[1].except[0] must be contained in 10.0.2.0/23"}}, + {"name":"random #158","input":[{"text":"{\n\"deny\": [\n{\n\"cidr\": \"f1fa:3bac:e784:7a7c:66c6:3c88:900d:9cd3/0001\",\n\"except\": []\n},\n{\n\"to\": [\n{\n\"cidr\": \"0.0.0.0/2\"\n},\n{\n\"cidr\": \"10.0.1.168/31\"\n},\n{\n\"cidr\": \"10.0.2.143\",\n\"except\": [\n\"10.0.2.143\"\n]\n},\n{\n\"cidr\": \"2001:db8::44\"\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::280/121\"\n},\n{\n\"cidr\": \"b146:bd3c:1da9:fb7c:4c7a:6a46:7e3c:9f00/120\"\n},\n{\n\"cidr\": \"2001:db8::3d3/128\"\n},\n{\n\"cidr\": \"2001:db8::1d9/127\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\",\n\"port\": 78\n}\n]\n}\n]\n}"}],"expected":{"allow":[],"deny":["0.0.0.0/2","2001:db8::44/128","2001:db8::1d8/127:tcp:78","2001:db8::1d8/127:udp:78","2001:db8::280/121:tcp:78","2001:db8::280/121:udp:78","2001:db8::3d3/128:tcp:78","2001:db8::3d3/128:udp:78","8000::/1"]}}, + {"name":"random #159","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.252/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":7\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::313/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":85\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.100/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.236/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":139\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::5c/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":297\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1b2/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":33,\n\t\t\t\"endPort\":34\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.252/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":301\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.247/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":57\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.194/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":157,\n\t\t\t\"endPort\":160\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.138/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":55\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.116/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":69\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::91/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":295\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.38/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":399,\n\t\t\t\"endPort\":400\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.6/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":13\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.20/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":341\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.35/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":333,\n\t\t\t\"endPort\":337\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.88/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":217,\n\t\t\t\"endPort\":221\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.218/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":39,\n\t\t\t\"endPort\":43\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.140/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":199\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1cc/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":257,\n\t\t\t\"endPort\":261\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2cd/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":263\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.178/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":241,\n\t\t\t\"endPort\":241\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2c8/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":35\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::37b/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":271\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.166/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":385\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.48/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":341,\n\t\t\t\"endPort\":343\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.104/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":59,\n\t\t\t\"endPort\":61\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.216/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":89\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::22a/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":63\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::df/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":29\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.204/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":255,\n\t\t\t\"endPort\":255\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::de/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":367\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.89/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":353,\n\t\t\t\"endPort\":353\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.112/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":293\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::385/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":99\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::115/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":197\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.208/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":29,\n\t\t\t\"endPort\":32\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.240/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":87,\n\t\t\t\"endPort\":91\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::97/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":59\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.68/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":361\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.175/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":11,\n\t\t\t\"endPort\":12\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.128/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":219,\n\t\t\t\"endPort\":223\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.218/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":149\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.168/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":139,\n\t\t\t\"endPort\":142\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1cc/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":143\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.47/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":289\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.20/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":167,\n\t\t\t\"endPort\":167\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.192/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":37\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::78/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":277\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.8/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":385,\n\t\t\t\"endPort\":386\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2f3/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":399\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.184/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":239\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.164/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":161\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.136/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":13\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.226/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":259\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.154/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":329\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.202/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":321,\n\t\t\t\"endPort\":325\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.58/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":299\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.10/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":123\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.78/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":1\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.154/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":147,\n\t\t\t\"endPort\":151\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.124/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":369,\n\t\t\t\"endPort\":370\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::e4/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":87,\n\t\t\t\"endPort\":89\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.228/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":365\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::ad/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":83\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.38/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":119,\n\t\t\t\"endPort\":122\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.248/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":207,\n\t\t\t\"endPort\":207\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::349/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":99\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.178/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":359\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.132/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":365,\n\t\t\t\"endPort\":369\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::89/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":333,\n\t\t\t\"endPort\":334\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.210/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":337\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.132/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":391\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.86/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":261,\n\t\t\t\"endPort\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.60/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":279\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.181/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.48/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":41\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.180/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":119\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.188/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":91\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.4/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":307\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.222/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":317\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2e/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":13,\n\t\t\t\"endPort\":13\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.54/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":357,\n\t\t\t\"endPort\":359\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.147/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":211\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.192/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":363,\n\t\t\t\"endPort\":366\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.204/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":309\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.80/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":333\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.111/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":267\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.226/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":209\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.238/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":145,\n\t\t\t\"endPort\":146\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.11/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":105\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.128/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":217\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.93/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":131\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.24/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":163,\n\t\t\t\"endPort\":166\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.248/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":35,\n\t\t\t\"endPort\":36\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2d9/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":175,\n\t\t\t\"endPort\":177\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.104/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":113\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.17/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":199\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.132/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":305,\n\t\t\t\"endPort\":305\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.175/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":351\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.117/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":245\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.161/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":379\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.196/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":119\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.34/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":383\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.190/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":359,\n\t\t\t\"endPort\":362\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.104/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":205\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.30/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":89\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.144/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":35,\n\t\t\t\"endPort\":39\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.56/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":119\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::256/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":103\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.232/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":35,\n\t\t\t\"endPort\":37\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.88/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":287\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::56/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":397,\n\t\t\t\"endPort\":399\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.201/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":49\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.243/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":391,\n\t\t\t\"endPort\":394\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.220/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":225,\n\t\t\t\"endPort\":229\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.229/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":213\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.168/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":187\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.190/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":49,\n\t\t\t\"endPort\":53\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.231/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":295,\n\t\t\t\"endPort\":296\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.166/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":5,\n\t\t\t\"endPort\":8\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.0/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":51\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.6/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":97,\n\t\t\t\"endPort\":98\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.89/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":103\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.219/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":223,\n\t\t\t\"endPort\":225\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.252/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":351,\n\t\t\t\"endPort\":354\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.143/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":325,\n\t\t\t\"endPort\":327\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::bc/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":105\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.236/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":255\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::19/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":167\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.240/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":51\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::30b/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":197\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.30/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":355\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.217/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":285,\n\t\t\t\"endPort\":286\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3e0/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":223\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.124/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":283\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.192/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":9\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.56/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":283\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.92/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":225,\n\t\t\t\"endPort\":226\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.88/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":57\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.190/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":369,\n\t\t\t\"endPort\":373\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.182/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":327\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1b/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":365\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::246/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":367,\n\t\t\t\"endPort\":370\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.252/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":21\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.193/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":233\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.100/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":89\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.84/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":283,\n\t\t\t\"endPort\":285\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.204/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":313\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.203/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":155,\n\t\t\t\"endPort\":156\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2fc/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":139,\n\t\t\t\"endPort\":143\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.230/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":77\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.132/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":357\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.68/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":357\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::23d/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":15\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::369/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":253\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::36e/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":1,\n\t\t\t\"endPort\":3\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.245/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":83\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.170/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":249\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.133/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":137\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.198/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":165\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.184/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":69\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::43/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":361\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.136/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":189\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::374/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":363\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.108/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":391\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.4/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":347\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.192/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":343\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.104/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":227\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.16/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":135\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.246/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":229,\n\t\t\t\"endPort\":230\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.4/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":309\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::3d/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":17\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.60/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":149,\n\t\t\t\"endPort\":149\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.179/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":315\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.118/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":275\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.74/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":327,\n\t\t\t\"endPort\":328\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::117/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":105\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::6f/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":7\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1c4/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.220/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":371\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.10/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":9,\n\t\t\t\"endPort\":10\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1af/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":321,\n\t\t\t\"endPort\":324\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::186/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":215\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.119/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":397\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.68/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":63,\n\t\t\t\"endPort\":65\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.64/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":193,\n\t\t\t\"endPort\":194\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.30/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":233\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.101/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":319\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.156/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":109\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.132/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":367,\n\t\t\t\"endPort\":371\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.232/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":41\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.26/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":297,\n\t\t\t\"endPort\":301\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.132/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":347\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.188/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":173\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.145/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":395\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.52/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":139\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.184/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":83\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.166/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":127\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1fd/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":273\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.12/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":317\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.149/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":265\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.184/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":71\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2eb/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":307\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::182/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":397\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.55/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":163\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.96/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":175\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.116/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":357,\n\t\t\t\"endPort\":360\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::e4/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":397\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::16d/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":25\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.235/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":311,\n\t\t\t\"endPort\":314\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.128/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":51\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::298/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":241,\n\t\t\t\"endPort\":243\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.47/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":123,\n\t\t\t\"endPort\":125\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.132/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":267\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.240/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":271,\n\t\t\t\"endPort\":272\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.212/30\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":387\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.48/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":161\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::10e/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":263\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.140/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":259\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.220/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":83\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.16/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":21\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.219/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":207\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.92/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":89\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.250/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":331\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.201/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":205\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.242/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":219\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":363\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.91/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":17\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.70/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":125,\n\t\t\t\"endPort\":126\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1e5/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":261\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.12/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":95\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.4/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":345,\n\t\t\t\"endPort\":349\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.168/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":129\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2c/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":113,\n\t\t\t\"endPort\":117\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.14/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":309\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.192/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":103\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::236/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":221\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.156/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":35,\n\t\t\t\"endPort\":35\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.182/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":85\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2cd/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":121\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1a3/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":339\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.4/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":19\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.60/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":157\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.181/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":19\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::385/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":355,\n\t\t\t\"endPort\":355\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.60/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":297,\n\t\t\t\"endPort\":301\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::147/128\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":319\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.16/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":231\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.64/30\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":139\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.108/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":193,\n\t\t\t\"endPort\":197\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.78/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":67\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.192/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":67\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.232/32\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":209\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.20/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":383\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.59/32\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":63\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::67/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":361\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.172/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":327\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::1c/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":141,\n\t\t\t\"endPort\":145\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.92/30\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":223\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.50/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":99\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::299/128\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":81\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.56/31\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":255\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.80/32\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":277\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.1.130/31\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":5,\n\t\t\t\"endPort\":8\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::338/128\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":69\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.122/31\",\n\t\t\t\"protocol\":\"any\",\n\t\t\t\"port\":89,\n\t\t\t\"endPort\":92\n\t\t}\n\t],\n\t\"deny\":[]\n}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"random #160","input":[{"text":"{\"allow\": [{\"ports\": [{\"protocol\": \"tcp\", \"port\": 4}], \"port\": 99999}]}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #161","input":[{"text":"{\"allow\": [{\"cidr\": \"10.0.0.0/0.0.0.127\", \"except\": [], \"protocol\": \"udp\"}, {\"cidr\": \"10.0.3.91/22\", \"except\": [\"10.0.3.27/28\"], \"protocol\": \"udp\", \"port\": 79, \"endPort\": 2998}], \"deny\": [{\"ports\": [{\"protocol\": \"udp\", \"port\": 80}, {\"protocol\": \"udp\", \"port\": 65531}, {\"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65532}, {\"protocol\": \"udp\", \"port\": 81, \"endPort\": 82}]}, {\"cidr\": \"::ffff:236.106.128.1/128\", \"except\": [], \"protocol\": \"any\", \"port\": 442, \"endPort\": 0}, {\"cidr\": \"10.0.1.96/27\", \"except\": [\"10.0.1.124/031\"]}]}"}],"expected":{"error":"deny[1].endPort must be between 1 and 65535"}}, + {"name":"random #162","input":[{"text":"{}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #163","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.181/30\",\n\"except\": []\n}\n],\n\"ports\": [\n{\n\"protocol\": \"udp\",\n\"port\": 441\n},\n{\n\"protocol\": \"udp\"\n}\n]\n},\n{\n\"cidr\": \"10.0.3.216/29\",\n\"protocol\": \"udp\",\n\"port\": 80,\n\"endPort\": 83\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.80/29\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\",\n\"port\": 442,\n\"endPort\": 442\n},\n{\n\"protocol\": \"tcp\"\n}\n]\n},\n{\n\"cidr\": \"2001:db8::100/123\"\n},\n{\n\"to\": [\n{\n\"cidr\": \"250.118.72.143\"\n},\n{\n\"cidr\": \"10.0.3.152/029\"\n},\n{\n\"cidr\": \"::ffff:228.235.82.160\"\n},\n{\n\"cidr\": \"10.0.1.28/32\"\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.0/24\"\n},\n{\n\"cidr\": \"2001:db8::80/121\",\n\"except\": [\n\"2001:db8::e1/126\"\n]\n},\n{\n\"cidr\": \"10.0.3.128/25\",\n\"except\": [\n\"10.0.3.217\",\n\"10.0.3.196/30\",\n\"10.0.3.172/30\"\n]\n},\n{\n\"cidr\": \"2001:db8::1d0/127\"\n}\n]\n}\n]\n}"}],"expected":{"allow":["10.0.1.28/32","10.0.2.0/24","10.0.3.80/29:tcp","10.0.3.80/29:udp:442","10.0.3.128/27","10.0.3.160/29","10.0.3.168/30","10.0.3.176/28","10.0.3.192/30","10.0.3.200/29","10.0.3.208/28:udp:80-83","10.0.3.208/29","10.0.3.216/32","10.0.3.218/31","10.0.3.220/30","10.0.3.224/27","250.118.72.143/32","::ffff:228.235.82.160/128","2001:db8::80/122","2001:db8::c0/123","2001:db8::e4/126","2001:db8::e8/125","2001:db8::f0/124","2001:db8::100/123","2001:db8::1d0/127"],"deny":[]}}, + {"name":"random #164","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"cfa4:7eb4:5dda:1262:1120:c53a:77b:b00e/127\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 445\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::90/125\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01c3/126\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.29/30\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.41/027\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"73.176.0.0/12\",\n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 65535,\n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["2001:db8::90/125:tcp","cfa4:7eb4:5dda:1262:1120:c53a:77b:b00e/127:icmp","cfa4:7eb4:5dda:1262:1120:c53a:77b:b00e/127:tcp:445"],"deny":["10.0.2.28/30:icmp","10.0.2.28/30:tcp:65535","10.0.2.32/27:icmp","10.0.2.32/27:tcp:65535","73.176.0.0/12:icmp","73.176.0.0/12:tcp:65535","2001:db8::1c0/126:icmp","2001:db8::1c0/126:tcp:65535"]}}, + {"name":"random #165","input":[{"text":"{\n\t\"allow\":[],\n\t\"deny\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.224/27\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.3.238/255.255.255.254\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":443,\n\t\t\t\t\t\"endPort\":445\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.16/32\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"port\":78,\n\t\t\t\t\t\"endPort\":78\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.3.202/32\",\n\t\t\t\"protocol\":\"icmp\"\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.2.16/0.0.0.15\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":6,\n\t\t\t\"endPort\":11\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.148/31\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::e4/127\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:db8::e4/127\",\n\t\t\t\t\t\t\"2001:db8::e4/127\",\n\t\t\t\t\t\t\"2001:db8::e4/127\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::1f7/123\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:db8::1f6/125\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::e0/0123\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":12\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":82\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":1\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.128/25\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":440\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"0.0.0.0/0\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":80,\n\t\t\t\t\t\"endPort\":84\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":[],"deny":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:1","0.0.0.0/0:tcp:12","0.0.0.0/0:tcp:78","0.0.0.0/0:tcp:82","0.0.0.0/0:udp:1","0.0.0.0/0:udp:12","0.0.0.0/0:udp:78","0.0.0.0/0:udp:80-84","10.0.0.128/25:udp:440","10.0.0.148/31","10.0.2.16/28:tcp:6-12","10.0.3.224/29:udp:443-445","10.0.3.232/30:udp:443-445","10.0.3.236/31:udp:443-445","10.0.3.240/28:udp:443-445","::/0:icmp","::/0:tcp:1","::/0:tcp:12","::/0:tcp:82","::/0:udp:1","::/0:udp:12","2001:db8::e0/123","2001:db8::1e0/124","2001:db8::1f8/125"]}}, + {"name":"random #166","input":[{"text":"{\n \"allow\": [\n {\n \"to\": [\n {\n \"cidr\": \"a1b3:24d4:add8:ba25::/64\"\n }, \n {\n \"cidr\": \"10.0.3.192/255.255.255.192\"\n }, \n {\n \"cidr\": \"8000:0000:0000:0000:0000:0000:0000:0000/1\", \n \"except\": [\n \"ac00::/8\"\n ]\n }, \n {\n \"cidr\": \"2001:db8::280/122\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 445, \n \"endPort\": 449\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 12, \n \"endPort\": 18\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"::ffff:0.0.0.0/104\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 440\n }, \n {}, \n {\n \"protocol\": \"tcp\", \n \"port\": 65535\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.0/118\", \n \"except\": []\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 444, \n \"endPort\": 445\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 65534, \n \"endPort\": 65535\n }, \n {\n \"protocol\": \"any\", \n \"port\": 65531\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::33\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 4\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 80, \n \"endPort\": 81\n }, \n {\n \"protocol\": \"udp\"\n }\n ]\n }, \n {\n \"cidr\": \"10.0.3.233/255.255.255.254\", \n \"protocol\": \"any\"\n }, \n {\n \"cidr\": \"10.0.1.117/32\", \n \"except\": [\n \"10.0.1.117/32\"\n ], \n \"protocol\": \"any\"\n }\n ], \n \"deny\": [\n {\n \"cidr\": \"10.0.0.192/27\", \n \"except\": [\n \"10.0.0.208/31\", \n \"10.0.0.195/30\", \n \"10.0.0.220/28\"\n ], \n \"protocol\": \"tcp\", \n \"port\": 441\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::200/120\", \n \"except\": []\n }, \n {\n \"cidr\": \"10.0.2.71/26\"\n }, \n {\n \"cidr\": \"10.0.0.0/24\"\n }, \n {\n \"cidr\": \"2001:db8::d4/000127\"\n }\n ], \n \"ports\": [\n {\n \"port\": 444\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 6, \n \"endPort\": 5271\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 65530, \n \"endPort\": 65535\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.148/30\"\n }, \n {\n \"cidr\": \"10.0.1.19/22\"\n }, \n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0372/121\"\n }, \n {\n \"cidr\": \"10.0.1.65\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"udp\"\n }, \n {\n \"port\": 443\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.3.181\"\n }, \n {\n \"cidr\": \"10.0.0.26/31\"\n }, \n {\n \"cidr\": \"2001:db8::1b4/126\"\n }, \n {\n \"cidr\": \"6.187.146.3/4\", \n \"except\": [\n \"10.96.0.0/12\", \n \"4.0.0.0/6\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 82, \n \"endPort\": 87\n }\n ]\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"52.1.135.254/31\", \n \"except\": [\n \"52.1.135.255/31\", \n \"52.1.135.255\", \n \"52.1.135.254/31\"\n ]\n }, \n {\n \"cidr\": \"17f8:64b3:854:e286:d856:5bc2::/96\", \n \"except\": [\n \"17f8:64b3:0854:e286:d856:5bc2:7000:0000/101\", \n \"17f8:64b3:854:e286:d856:5bc2::/97\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"any\", \n \"port\": 80, \n \"endPort\": 18065\n }, \n {\n \"protocol\": \"icmp\"\n }, \n {\n \"port\": 440\n }, \n {\n \"protocol\": \"tcp\"\n }\n ]\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 65532\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.48/31\"\n }, \n {\n \"cidr\": \"2001:db8::280/121\"\n }, \n {\n \"cidr\": \"2001:db8::168/127\"\n }, \n {\n \"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0080/121\", \n \"except\": [\n \"2001:0db8:0000:0000:0000:0000:0000:00b1\", \n \"2001:db8::96/123\", \n \"2001:db8::fa/127\"\n ]\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"icmp\"\n }, \n {\n \"protocol\": \"tcp\"\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["10.0.3.192/26:tcp:12-18","10.0.3.192/26:tcp:445-449","10.0.3.232/31","::ffff:0.0.0.0/104","2001:db8::/118:tcp:444-445","2001:db8::/118:tcp:65531","2001:db8::/118:tcp:65534-65535","2001:db8::/118:udp:65531","2001:db8::33/128:tcp:4","2001:db8::33/128:tcp:80-81","2001:db8::33/128:udp","2001:db8::280/122:tcp:12-18","2001:db8::280/122:tcp:446-449","8000::/3:tcp:12-18","8000::/3:tcp:445-449","a000::/5:tcp:12-18","a000::/5:tcp:445-449","a800::/6:tcp:12-18","a800::/6:tcp:445-449","ad00::/8:tcp:12-18","ad00::/8:tcp:445-449","ae00::/7:tcp:12-18","ae00::/7:tcp:445-449","b000::/4:tcp:12-18","b000::/4:tcp:445-449","c000::/2:tcp:12-18","c000::/2:tcp:445-449"],"deny":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:65532","0.0.0.0/6:tcp:82-87","8.0.0.0/7:tcp:82-87","10.0.0.0/10:tcp:82-87","10.0.0.0/22:tcp:443","10.0.0.0/22:udp","10.0.0.0/24:tcp:6-5271","10.0.0.0/24:tcp:65530-65535","10.0.0.48/31:tcp","10.0.2.64/26:tcp:6-5271","10.0.2.64/26:tcp:65530-65535","10.64.0.0/11:tcp:82-87","10.112.0.0/12:tcp:82-87","10.128.0.0/9:tcp:82-87","11.0.0.0/8:tcp:82-87","12.0.0.0/6:tcp:82-87","::/0:icmp","::/0:tcp:65532","17f8:64b3:854:e286:d856:5bc2:8000:0/97:tcp","17f8:64b3:854:e286:d856:5bc2:8000:0/97:udp:80-18065","2001:db8::a0/124:tcp","2001:db8::b0/128:tcp","2001:db8::b2/127:tcp","2001:db8::b4/126:tcp","2001:db8::b8/125:tcp","2001:db8::c0/123:tcp","2001:db8::d4/127:udp:444","2001:db8::e0/124:tcp","2001:db8::f0/125:tcp","2001:db8::f8/127:tcp","2001:db8::fc/126:tcp","2001:db8::168/127:tcp","2001:db8::1b4/126:tcp:82-87","2001:db8::200/120:tcp:6-5271","2001:db8::200/120:tcp:65530-65535","2001:db8::200/120:udp:444","2001:db8::280/121:tcp","2001:db8::300/121:tcp:443","2001:db8::300/121:udp"]}}, + {"name":"random #167","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::/118\", \"protocol\": \"tcp\", \"port\": 65531}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.0.224/28\"}, {\"cidr\": \"10.0.3.0/25\"}, {\"cidr\": \"2001:DB8::140/122\", \"except\": [\"2001:db8::170/125\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0190/127\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:0190/127\", \"2001:db8::190/127\"]}]}, {\"to\": [{\"cidr\": \"6.107.20.162/0\"}]}]}"}],"expected":{"allow":["2001:db8::/118:tcp:65531"],"deny":["0.0.0.0/0","2001:db8::140/123","2001:db8::160/124","2001:db8::178/125"]}}, + {"name":"random #168","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0030/118\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.0/23\"\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.3.112/124\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 2\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0238/125\"\n}, \n{\n\"cidr\": \"10.0.1.53\"\n}, \n{\n\"cidr\": \"10.0.2.15/26\"\n}\n], \n\"ports\": [\n{}, \n{\n\"protocol\": \"udp\", \n\"port\": 65532\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65534\n}, \n{\n\"protocol\": \"any\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.244/29\"\n}, \n{\n\"cidr\": \"c826:ec3b:c713:157b:bfff:a036:691a:c9da/127\", \n\"except\": [\n\"C826:EC3B:C713:157B:BFFF:A036:691A:C9DA\", \n\"c826:ec3b:c713:157b:bfff:a036:691a:c9db/127\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 6\n}\n]\n}\n], \n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.64/26\", \n\"except\": [\n\"10.0.2.64/26\", \n\"10.0.2.67/26\", \n\"10.0.2.87/28\"\n]\n}, \n{\n\"cidr\": \"2001:db8::130/124\"\n}, \n{\n\"cidr\": \"2001:db8::e0/122\"\n}, \n{\n\"cidr\": \"2001:db8::100/120\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 442\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/23\"\n}, \n{\n\"cidr\": \"::ffff:120.0.0.0/104\", \n\"except\": [\n\"::ffff:120.119.166.15/108\", \n\"0000:0000:0000:0000:0000:ffff:120.16.0.0/108\", \n\"::ffff:120.16.0.0/108\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 1\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65535\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"210.37.0.0/255.255.0.0\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 65531\n}, \n{\n\"protocol\": \"any\", \n\"port\": 65531, \n\"endPort\": 65535\n}\n]\n}, \n{\n\"cidr\": \"::644e:d548:e18f:f035:a2f7:3f4a/32\", \n\"protocol\": \"any\", \n\"port\": 80, \n\"endPort\": 86\n}, \n{\n\"cidr\": \"0.0.0.0/0\", \n\"protocol\": \"tcp\", \n\"port\": 78\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.88/29\", \n\"except\": [\n\"10.0.2.92/0032\", \n\"10.0.2.88/29\"\n]\n}\n]\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"any\"\n}\n]\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.2.252/128\", \n\"except\": [\n\"2001:0db8:0000:0000:0000:0000:0000:02fc\", \n\"2001:db8::2fc/128\"\n], \n\"protocol\": \"any\", \n\"port\": 443\n}\n]\n}"}],"expected":{"allow":["10.0.0.240/29:tcp:6","10.0.1.53/32","10.0.2.0/23:icmp","10.0.2.0/23:udp:2","10.0.2.0/26","2001:db8::/118"],"deny":["0.0.0.0/0","::/0"]}}, + {"name":"random #169","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01bd/121\"\n}, \n{\n\"cidr\": \"10.0.3.120/29\", \n\"except\": []\n}, \n{\n\"cidr\": \"10.0.0.161/255.255.255.248\", \n\"except\": [\n\"10.0.0.160/0030\"\n]\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::3eb/128\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\"\n}, \n{}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::200/119\", \n\"except\": [\n\"2001:0db8:0000:0000:0000:0000:0000:0200/119\", \n\"2001:db8::318/000125\", \n\"2001:db8::3e0/123\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 442\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65533\n}, \n{\n\"protocol\": \"any\", \n\"port\": 79, \n\"endPort\": 81\n}\n]\n}\n], \n\"deny\": []\n}"}],"expected":{"allow":["10.0.0.164/30","10.0.3.120/29","2001:db8::180/121","2001:db8::3eb/128"],"deny":[]}}, + {"name":"random #170","input":[{"text":"{\"allow\":[{\"cidr\":\"10.0.3.31/29\",\"protocol\":\"udp\",\"port\":5,\"endPort\":6},{\"cidr\":\"10.0.1.160/28\",\"protocol\":\"tcp\"}],\"deny\":[{\"cidr\":\"28.164.95.147/32\",\"protocol\":\"icmp\"}]}"}],"expected":{"allow":["10.0.1.160/28:tcp","10.0.3.24/29:udp:5-6"],"deny":["28.164.95.147/32:icmp"]}}, + {"name":"random #171","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::274/126\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":3,\n\t\t\t\"endPort\":8\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:008c/126\",\n\t\t\t\"except\":[\n\t\t\t\t\"2001:DB8::8E/127\",\n\t\t\t\t\"2001:db8::8e/00127\",\n\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0.0.0.143/126\"\n\t\t\t],\n\t\t\t\"protocol\":\"udp\"\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::40/122\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::353/123\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.0/22\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":3\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.128/25\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.3.192/0026\",\n\t\t\t\t\t\t\"10.0.3.230/31\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":441,\n\t\t\t\t\t\"endPort\":445\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":65535\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:DB8::6D/118\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.1.0/24\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.28/255.255.255.254\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.3.28/255.255.255.254\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.203/255.255.255.254\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":441\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::/121\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":3\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"0000:0000:0000:0000:0000:ffff:123.204.82.35/000127\",\n\t\t\t\"except\":[\n\t\t\t\t\"0000:0000:0000:0000:0000:ffff:123.204.82.34\",\n\t\t\t\t\"::ffff:123.204.82.34\"\n\t\t\t],\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":2\n\t\t},\n\t\t{\n\t\t\t\"to\":\"192.0.2.0/24\"\n\t\t}\n\t],\n\t\"deny\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"88bd:807f:f73c:8470:7051:1896::/96\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"88bd:807f:f73c:8470:7051:1896::/99\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:01a0/124\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"2001:db8::1ad/128\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.112/27\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":440,\n\t\t\t\t\t\"endPort\":440\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"b81f:d565:925d:6cf9:05dc:5510:d9d4:3dc9/32\",\n\t\t\t\"protocol\":\"any\"\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"2001:db8::200/119\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"any\",\n\t\t\t\t\t\"port\":80\n\t\t\t\t},\n\t\t\t\t{}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.97/32\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\",\n\t\t\t\t\t\"port\":441,\n\t\t\t\t\t\"endPort\":51586\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::2d3/125\",\n\t\t\t\"protocol\":\"tcp\",\n\t\t\t\"port\":81\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"::/32\",\n\t\t\t\t\t\"except\":[]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.2.127/25\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.2.64/26\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"allow[7].to must be an array"}}, + {"name":"random #172","input":[{"text":"{\"allow\": []}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #173","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"192.0.0.0/4\"}, {\"cidr\": \"2001:db8::/118\"}, {\"cidr\": \"2001:db8::229/128\"}]}, {\"to\": [{\"cidr\": \"2001:DB8::90\"}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 82}]}, {\"cidr\": \"10.0.2.0/23\", \"protocol\": \"udp\", \"port\": 80, \"endPort\": 85}, {\"cidr\": \"10.0.2.0/23\"}, {\"to\": [{\"cidr\": \"10.0.3.196/31\"}, {\"cidr\": \"10.0.0.0/22\"}]}, {\"to\": [{\"cidr\": \"10.0.3.0/255.255.255.128\", \"except\": [\"10.0.3.42/26\"]}, {\"cidr\": \"10.0.1.128/26\", \"except\": []}]}], \"deny\": [{\"to\": [{\"cidr\": \"10.0.1.0/25\"}, {\"cidr\": \"2001:db8::331/126\"}, {\"cidr\": \"8000::/1\", \"except\": [\"969c:9ef6:341:c381:cbfb:20d9:8b39:e594/9\", \"f0c8:839d:f0b:e0b6:8dd6:988:4418:541a/2\", \"dc00::/7\"]}]}, {\"to\": [{\"cidr\": \"587a:9b56:a8a8:9f8c:472e:979e:f3ea:f700/120\", \"except\": [\"587a:9b56:a8a8:9f8c:472e:979e:f3ea:f720/123\", \"587a:9b56:a8a8:9f8c:472e:979e:f3ea:f713/128\", \"587a:9b56:a8a8:9f8c:472e:979e:f3ea:f710/124\"]}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"tcp\"}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\", \"port\": 79, \"endPort\": 84}]}, {\"cidr\": \"2001:db8::3bc/122\", \"except\": [\"2001:db8::387\"], \"protocol\": \"icmp\"}, {\"cidr\": \"10.0.0.202/29\", \"except\": [\"10.0.0.204/30\", \"10.0.0.200/30\"]}, {\"to\": [{\"cidr\": \"10.0.0.112/29\"}], \"ports\": [{\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"9e24:fd81:5ef5:f590:3c9:e095:e607:e937/32\", \"except\": []}]}]}"}],"expected":{"allow":["10.0.0.0/22","192.0.0.0/4","2001:db8::/118"],"deny":["10.0.0.112/29:tcp","10.0.1.0/25","2001:db8::330/126","2001:db8::380/126:icmp","2001:db8::384/127:icmp","2001:db8::386/128:icmp","2001:db8::388/125:icmp","2001:db8::390/124:icmp","2001:db8::3a0/123:icmp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f700/124:icmp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f700/124:tcp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f700/124:udp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f740/122:icmp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f740/122:tcp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f740/122:udp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f780/121:icmp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f780/121:tcp","587a:9b56:a8a8:9f8c:472e:979e:f3ea:f780/121:udp","8000::/4","9000::/6","9400::/7","9600::/9","9700::/8","9800::/5","a000::/3"]}}, + {"name":"random #174","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.48/28\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\",\n\t\t\t\t\t\"port\":441\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::10/124\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":65534\n\t\t}\n\t],\n\t\"deny\":[\n\t\t{\n\t\t\t\"cidr\":\"10.0.0.0/023\",\n\t\t\t\"protocol\":\"icmp\",\n\t\t\t\"port\":443\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[0].port is not supported with icmp"}}, + {"name":"random #175","input":[{"text":"{\n\t\"deny\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::/118\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.104/31\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"22ff:42f:b495:a7b3::/64\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 440, \n\t\t\t\t\t\"endPort\": 444\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 65534\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 80\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 445\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0383\", \n\t\t\t\"except\": [\n\t\t\t\t\"2001:db8::383\", \n\t\t\t\t\"2001:db8::383/128\"\n\t\t\t], \n\t\t\t\"protocol\": \"udp\", \n\t\t\t\"port\": 65532, \n\t\t\t\"endPort\": 65535\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:02e0/125\", \n\t\t\t\"except\": [\n\t\t\t\t\"2001:db8::2e5/126\", \n\t\t\t\t\"2001:db8::2e4/000128\", \n\t\t\t\t\"x\"\n\t\t\t], \n\t\t\t\"protocol\": \"tcp\", \n\t\t\t\"port\": 3\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.136/29\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.7/28\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.240/23\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.41/29\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 441\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.32/30\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"::ffff:0.0.0.0/96\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.0.0/120\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.140/23\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 11, \n\t\t\t\t\t\"endPort\": 12\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"0000:0000:0000:0000:0000:ffff:72.137.106.237/96\", \n\t\t\t\"protocol\": \"tcp\"\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::140/122\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.0/25\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0240/122\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"f3a0:dc63:e2c9:3a1f:472b:5aff:261:e3c/127\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65530, \n\t\t\t\t\t\"endPort\": 65532\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 81, \n\t\t\t\t\t\"endPort\": 85\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 9, \n\t\t\t\t\t\"endPort\": 13076\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::311\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:0311/128\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::3e0/00126\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 1\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 6, \n\t\t\t\t\t\"endPort\": 52291\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 65531\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"error":"deny[2].except[2] is not a valid IPv4 or IPv6 CIDR: x"}}, + {"name":"random #176","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"2001:db8::3bd/128\",\"except\":[]},{\"cidr\":\"2001:DB8::86\",\"except\":[\"2001:db8::86/128\",\"2001:db8::86\",\"2001:db8::86\"]},{\"cidr\":\"10.0.2.144/29\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":5},{\"protocol\":\"icmp\"},{\"protocol\":\"udp\"}]},{\"cidr\":\"2001:db8::42/123\",\"except\":[\"2001:db8::50/124\"],\"protocol\":\"udp\",\"port\":65535,\"endPort\":65535},{\"cidr\":\"10.0.0.128/25\",\"except\":[],\"protocol\":\"udp\"},{\"to\":[{\"cidr\":\"10.0.0.199/26\"}],\"ports\":[{\"protocol\":\"any\"},{\"protocol\":\"udp\"}]},{\"cidr\":\"10.0.3.240/29\",\"except\":[\"10.0.3.247/31\"],\"protocol\":\"icmp\"},{\"to\":[{\"cidr\":\"215.171.134.127/1\"},{\"cidr\":\"10.0.3.0/26\"},{\"cidr\":\"::/0\",\"except\":[]}],\"ports\":[{\"protocol\":\"tcp\",\"port\":440},{\"protocol\":\"tcp\"},{\"protocol\":\"udp\"}]},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0.0.1.0/121\"},{\"cidr\":\"10.0.3.72/26\",\"except\":[]},{\"cidr\":\"10.0.0.224/255.255.255.252\"}]},{\"to\":[{\"cidr\":\"10.0.2.37/29\",\"except\":[]},{\"cidr\":\"B6FA:AF44::/32\"}],\"ports\":[{\"protocol\":\"any\",\"port\":81,\"endPort\":82},{\"protocol\":\"udp\"},{\"protocol\":\"any\",\"port\":79}]}]}"}],"expected":{"allow":["10.0.0.128/25:udp","10.0.0.192/26","10.0.2.32/29:tcp:79","10.0.2.32/29:tcp:81-82","10.0.2.32/29:udp","10.0.2.144/29:icmp","10.0.2.144/29:tcp:5","10.0.2.144/29:udp","10.0.3.0/25:tcp","10.0.3.0/25:udp","10.0.3.64/26","10.0.3.240/30:icmp","10.0.3.244/31:icmp","128.0.0.0/1:tcp","128.0.0.0/1:udp","::/0:tcp","::/0:udp","2001:db8::100/121","2001:db8::3bd/128:icmp"],"deny":[]}}, + {"name":"random #177","input":[{"text":"{\n\"allow\": [\n{\n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 11\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 441, \n\"endPort\": 444\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.128/28\", \n\"except\": [\n\"10.0.1.132/31\", \n\"10.0.1.136/255.255.255.252\"\n]\n}, \n{\n\"cidr\": \"10.0.1.241/255.255.255.0\"\n}, \n{\n\"cidr\": \"10.0.0.25/31\", \n\"except\": [\n\"10.0.0.24/0031\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 65530, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65531, \n\"endPort\": 65535\n}\n]\n}, \n{\n\"cidr\": \"10.0.0.0/22\", \n\"protocol\": \"tcp\", \n\"port\": 4\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::3c0/123\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"97.126.129.53/16\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 65535\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65533\n}\n]\n}, \n{\n\"cidr\": \"2001:db8::22f/128\", \n\"except\": [], \n\"protocol\": \"any\", \n\"port\": 445\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.200/29\"\n}, \n{\n\"cidr\": \"10.0.2.220/31\", \n\"except\": [\n\"10.0.2.220/255.255.255.254\"\n]\n}, \n{\n\"cidr\": \"2001:db8::398/124\"\n}, \n{\n\"cidr\": \"10.0.2.0/27\", \n\"except\": [\n\"10.0.2.12/30\", \n\"10.0.2.0/27\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 65533, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 12, \n\"endPort\": 17\n}, \n{\n\"protocol\": \"any\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::349/119\"\n}, \n{\n\"cidr\": \"::ffff:218.114.245.79/128\"\n}, \n{\n\"cidr\": \"10.0.3.64/26\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"tcp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"38.176.0.0/12\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 442\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 5, \n\"endPort\": 5\n}\n]\n}, \n{\n\"cidr\": \"10.0.3.96/30\", \n\"except\": [\n\"10.0.3.96/255.255.255.254\", \n\"10.0.3.96/031\"\n], \n\"protocol\": \"tcp\", \n\"port\": 6, \n\"endPort\": 20816\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::110/00124\", \n\"except\": [\n\"2001:db8::11a/128\", \n\"2001:db8::113/126\", \n\"2001:db8::11e/00127\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65535, \n\"endPort\": 65535\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:00f7/125\", \n\"except\": []\n}\n], \n\"ports\": [\n{\n\"port\": 2, \n\"endPort\": 6\n}, \n{\n\"protocol\": \"any\", \n\"port\": 443\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 82, \n\"endPort\": 84\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.232\", \n\"except\": [\n\"10.0.3.232\"\n]\n}, \n{\n\"cidr\": \"10.0.1.0/0024\"\n}, \n{\n\"cidr\": \"2001:db8::2e8/127\", \n\"except\": [\n\"2001:db8::2e8/127\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 11\n}\n]\n}, \n{\n\"cidr\": \"10.0.3.77/25\", \n\"except\": [\n\"10.0.3.104/0.0.0.7\"\n], \n\"protocol\": \"tcp\", \n\"port\": 80, \n\"endPort\": 86\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/255.255.255.0\"\n}, \n{\n\"cidr\": \"2001:DB8::340/122\"\n}, \n{\n\"cidr\": \"10.0.0.31/24\"\n}, \n{\n\"cidr\": \"10.0.3.144/31\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 65534\n}, \n{\n\"protocol\": \"udp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/24\", \n\"except\": []\n}, \n{\n\"cidr\": \"10.0.2.183/25\"\n}, \n{\n\"cidr\": \"2001:db8::180/122\", \n\"except\": [\n\"2001:db8::180/122\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 65534\n}, \n{\n\"port\": 1, \n\"endPort\": 7\n}, \n{\n\"protocol\": \"any\", \n\"port\": 3\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::100/126\", \n\"except\": [\n\"2001:db8::103/000127\", \n\"2001:DB8::102/127\", \n\"2001:DB8::100/126\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 65533, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 441, \n\"endPort\": 441\n}\n]\n}, \n{\n\"cidr\": \"2001:db8::239/118\", \n\"protocol\": \"icmp\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::174/000127\"\n}, \n{\n\"cidr\": \"10.0.3.168/31\"\n}, \n{\n\"cidr\": \"10.0.2.0/25\", \n\"except\": []\n}, \n{\n\"cidr\": \"2001:db8::160/126\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 12, \n\"endPort\": 18\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 81\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 65535, \n\"endPort\": 65535\n}\n]\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"udp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::28/000125\", \n\"except\": [\n\"2001:db8::28/127\"\n]\n}, \n{\n\"cidr\": \"3c49:0773:0000:0000:0000:0000:0000:0000/32\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 65532, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"any\", \n\"port\": 81\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"cidr\": \"10.0.2.0/00023\", \n\"protocol\": \"udp\"\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:00a4/00119\", \n\"protocol\": \"any\", \n\"port\": 65532\n}, \n{\n\"cidr\": \"2001:db8::c0/124\", \n\"protocol\": \"tcp\", \n\"port\": 8\n}, \n{\n\"cidr\": \"246.240.0.0/255.240.0.0\", \n\"except\": [\n\"246.252.0.0/15\"\n], \n\"protocol\": \"tcp\", \n\"port\": 443\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.3.41/120\"\n}, \n{\n\"cidr\": \"10.0.1.64/00026\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 65533, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 82, \n\"endPort\": 83\n}, \n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 65532\n}\n]\n}, \n{\n\"cidr\": \"::ffff:155.211.100.240/0128\", \n\"protocol\": \"any\", \n\"port\": 81, \n\"endPort\": 85\n}, \n{\n\"cidr\": \"2001:db8::b4/121\", \n\"protocol\": \"tcp\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:019f/128\"\n}, \n{\n\"cidr\": \"10.0.3.192/26\", \n\"except\": [\n\"10.0.3.193/29\"\n]\n}, \n{\n\"cidr\": \"96.0.0.0/8\"\n}, \n{\n\"cidr\": \"10.0.3.253/29\"\n}\n], \n\"ports\": [\n{\n\"port\": 442\n}\n]\n}, \n{\n\"cidr\": \"10.0.3.192/32\", \n\"protocol\": \"udp\", \n\"port\": 10, \n\"endPort\": 11\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.48/00028\", \n\"except\": [\n\"10.0.2.60/0.0.0.3\", \n\"10.0.2.54/31\", \n\"10.0.2.57/00028\"\n]\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0000/0121\"\n}\n], \n\"ports\": [\n{}\n]\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 65533, \n\"endPort\": 65533\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:DB8::AF/127\", \n\"except\": [\n\"2001:db8::af/128\", \n\"2001:db8::ae/127\", \n\"2001:db8::ae/127\"\n]\n}, \n{\n\"cidr\": \"2001:db8::140/122\"\n}, \n{\n\"cidr\": \"::/3\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\"\n}\n]\n}, \n{}, \n{\n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 445\n}, \n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"cidr\": \"2001:db8::340/123\", \n\"except\": [\n\"2001:db8::356/123\"\n], \n\"protocol\": \"tcp\", \n\"port\": 442, \n\"endPort\": 445\n}, \n{\n\"cidr\": \"10.0.0.0/255.255.254.0\", \n\"protocol\": \"tcp\", \n\"port\": 78, \n\"endPort\": 83\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/0.0.3.255\"\n}, \n{\n\"cidr\": \"10.0.2.43/0.0.0.15\"\n}, \n{\n\"cidr\": \"10.0.3.192/26\"\n}, \n{\n\"cidr\": \"8000::/3\", \n\"except\": [\n\"9000::/4\", \n\"88C0::/11\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"9b7d:98a0:af68:ca7b:e8c1:e385::/96\"\n}, \n{\n\"cidr\": \"41.0.0.0/8\"\n}, \n{\n\"cidr\": \"2001:db8::90/125\", \n\"except\": [\n\"2001:db8::90/125\"\n]\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"6238:3e86:3901:a97b:5a2f:6754:8ea7:bda8/1\"\n}, \n{\n\"cidr\": \"10.0.2.148/28\"\n}\n]\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"any\", \n\"port\": 65532\n}, \n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 4, \n\"endPort\": 8\n}\n]\n}, \n{\n\"cidr\": \"10.0.2.64/24\", \n\"except\": []\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.0/0.0.0.255\"\n}, \n{\n\"cidr\": \"49.53.57.212/0016\"\n}, \n{\n\"cidr\": \"10.0.0.0/22\"\n}, \n{\n\"cidr\": \"2001:db8::/119\", \n\"except\": [\n\"2001:db8::15a/121\", \n\"2001:db8::b0/124\", \n\"2001:db8::40/122\"\n]\n}\n]\n}, \n{\n\"cidr\": \"2001:db8::3e0/123\", \n\"except\": [], \n\"protocol\": \"udp\", \n\"port\": 9\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"udp\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 444, \n\"endPort\": 56756\n}\n]\n}\n], \n\"deny\": [\n{\n\"cidr\": \"2000::/3\", \n\"protocol\": \"any\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.100/22\", \n\"except\": [\n\"10.0.0.134/24\"\n]\n}, \n{\n\"cidr\": \"b60c:21d6:c79e:83c2:381d:c4c7:1229:9235/32\"\n}, \n{\n\"cidr\": \"10.0.0.0/25\"\n}\n], \n\"ports\": [\n{\n\"port\": 65531, \n\"endPort\": 65532\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 441\n}\n]\n}, \n{\n\"cidr\": \"10.0.1.160/27\", \n\"except\": [\n\"10.0.1.184/32\", \n\"10.0.1.176/29\"\n], \n\"protocol\": \"any\", \n\"port\": 440\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.192/26\"\n}, \n{\n\"cidr\": \"64.0.0.0/2\", \n\"except\": [\n\"76.0.0.0/6\", \n\"84.0.0.0/6\", \n\"89.128.0.0/10\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\"\n}, \n{\n\"protocol\": \"any\", \n\"port\": 65534\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:03b8/125\", \n\"except\": [\n\"2001:db8::3bc/126\"\n]\n}, \n{\n\"cidr\": \"10.0.0.0/22\"\n}, \n{\n\"cidr\": \"10.0.0.0/22\"\n}, \n{\n\"cidr\": \"10.0.3.56/29\", \n\"except\": [\n\"10.0.3.58/31\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"tcp\", \n\"port\": 65532, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 11, \n\"endPort\": 49274\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}, \n{\n\"cidr\": \"10.0.1.32/28\", \n\"protocol\": \"any\", \n\"port\": 65530\n}, \n{\n\"to\": [\n{\n\"cidr\": \"0.0.0.0/0.0.0.0\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.2.0/255.255.254.0\"\n}, \n{\n\"cidr\": \"2001:db8::226/127\"\n}, \n{\n\"cidr\": \"10.0.2.8/30\", \n\"except\": [\n\"10.0.2.9\", \n\"10.0.2.10/30\", \n\"10.0.2.10/0032\"\n]\n}, \n{\n\"cidr\": \"10.0.2.250/24\"\n}\n], \n\"ports\": [\n{\n\"protocol\": \"udp\", \n\"port\": 65532, \n\"endPort\": 65535\n}, \n{\n\"protocol\": \"any\", \n\"port\": 11, \n\"endPort\": 15\n}\n]\n}\n]\n}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":["0.0.0.0/0","2000::/3","b60c:21d6::/32:tcp:65531-65532","b60c:21d6::/32:udp:441","b60c:21d6::/32:udp:65531-65532"]}}, + {"name":"random #178","input":[{"text":"{\n \"allow\": [\n {\n \"ports\": [\n {\n \"protocol\": \"any\",\n \"port\": 441,\n \"endPort\": 447\n },\n {\n \"protocol\": \"udp\"\n }\n ]\n }\n ]\n}"}],"expected":{"allow":["0.0.0.0/0:tcp:441-447","0.0.0.0/0:udp","::/0:tcp:441-447","::/0:udp"],"deny":[]}}, + {"name":"random #179","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 441, \n\t\t\t\t\t\"endPort\": 446\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65532\n\t\t\t\t}, \n\t\t\t\t{}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 442\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["0.0.0.0/0","::/0"],"deny":[]}}, + {"name":"random #180","input":[{"text":"{\n\t\"allow\":[\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::300/120\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":9\n\t\t},\n\t\t{\n\t\t\t\"to\":[\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.131/28\",\n\t\t\t\t\t\"except\":[\n\t\t\t\t\t\t\"10.0.0.141\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.0.212/30\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\":\"10.0.3.72/30\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\":[\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"udp\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\":\"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\":\"2001:db8::240/122\",\n\t\t\t\"protocol\":\"udp\",\n\t\t\t\"port\":9\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.0.128/29:icmp","10.0.0.128/29:udp","10.0.0.136/30:icmp","10.0.0.136/30:udp","10.0.0.140/32:icmp","10.0.0.140/32:udp","10.0.0.142/31:icmp","10.0.0.142/31:udp","10.0.0.212/30:icmp","10.0.0.212/30:udp","10.0.3.72/30:icmp","10.0.3.72/30:udp","2001:db8::240/122:udp:9","2001:db8::300/120:udp:9"],"deny":[]}}, + {"name":"random #181","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"10.0.3.132/255.255.252.0\", \"except\": []}, {\"cidr\": \"2001:db8::d/126\"}]}, {\"to\": [{\"cidr\": \"10.0.0.109/32\"}, {\"cidr\": \"10.0.1.0/25\"}, {\"cidr\": \"10.0.0.152/30\"}, {\"cidr\": \"::ffff:185.242.110.69/128\"}], \"ports\": [{\"protocol\": \"udp\"}]}, {\"cidr\": \"2001:db8::358/125\", \"except\": [], \"protocol\": \"tcp\", \"port\": 81, \"endPort\": 81}, {\"ports\": [{\"protocol\": \"udp\", \"port\": 65533}, {\"protocol\": \"udp\"}, {\"protocol\": \"tcp\"}]}], \"deny\": [{\"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"udp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"any\", \"port\": 82, \"endPort\": 87}]}, {\"cidr\": \"167.64.167.38/12\", \"protocol\": \"any\", \"port\": 3}, {\"cidr\": \"10.0.0.0/22\", \"except\": [\"10.0.2.120/29\", \"10.0.3.160/255.255.255.224\", \"10.0.0.160/027\"], \"protocol\": \"any\", \"port\": 3}]}"}],"expected":{"allow":["0.0.0.0/0:tcp","0.0.0.0/0:udp","10.0.0.0/22","::/0:tcp","::/0:udp","2001:db8::c/126"],"deny":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:82-87","0.0.0.0/0:udp","10.0.0.0/25:tcp:3","10.0.0.128/27:tcp:3","10.0.0.192/26:tcp:3","10.0.1.0/24:tcp:3","10.0.2.0/26:tcp:3","10.0.2.64/27:tcp:3","10.0.2.96/28:tcp:3","10.0.2.112/29:tcp:3","10.0.2.128/25:tcp:3","10.0.3.0/25:tcp:3","10.0.3.128/27:tcp:3","10.0.3.192/26:tcp:3","167.64.0.0/12:tcp:3","::/0:icmp","::/0:tcp:82-87","::/0:udp"]}}, + {"name":"random #182","input":[{"text":"{\"allow\":[{\"ports\":[{\"protocol\":\"tcp\"},{\"protocol\":\"tcp\",\"port\":80},{\"protocol\":\"udp\"}]},{\"to\":[{\"cidr\":\"0.0.0.0/1\",\"except\":[]},{\"cidr\":\"2001:db8::1a/126\"},{\"cidr\":\"0.0.0.0/001\"},{\"cidr\":\"10.0.1.114/30\"}],\"ports\":[{\"protocol\":\"udp\",\"port\":79},{\"port\":81,\"endPort\":4510},{\"protocol\":\"any\",\"port\":65534}]},{\"cidr\":\"184.0.0.0/255.0.0.0\",\"except\":[\"184.0.0.0/8\"],\"protocol\":\"tcp\",\"port\":9},{\"to\":[{\"cidr\":\"10.0.1.128/25\"},{\"cidr\":\"2001:DB8::/118\",\"except\":[]}],\"ports\":[{\"protocol\":\"icmp\"}]}],\"deny\":[{\"to\":[{\"cidr\":\"10.0.1.253/32\"},{\"cidr\":\"0.0.0.0/1\"},{\"cidr\":\"::ffff:0.0.0.0/96\"},{\"cidr\":\"2001:db8::/122\"}],\"ports\":[{\"protocol\":\"tcp\"}]},{\"cidr\":\"10.0.0.0/22\",\"protocol\":\"any\"},{\"to\":[{\"cidr\":\"2001:0db8:0000:0000:0000:0000:0000:0200/120\"},{\"cidr\":\"71.93.0.0/16\"}]}]}"}],"expected":{"allow":["0.0.0.0/0:tcp","0.0.0.0/0:udp","10.0.1.128/25:icmp","::/0:tcp","::/0:udp","2001:db8::/118:icmp"],"deny":["0.0.0.0/1:tcp","10.0.0.0/22","71.93.0.0/16","::ffff:0.0.0.0/96:tcp","2001:db8::/122:tcp","2001:db8::200/120"]}}, + {"name":"random #183","input":[{"text":"{\"deny\": []}"}],"expected":{"allow":[],"deny":[]}}, + {"name":"random #184","input":[{"text":"{\n \"allow\": [\n {\n \"cidr\": \"2001:db8::ca/127\", \n \"protocol\": \"any\", \n \"port\": 78\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::/119\", \n \"except\": [\n \"2001:0db8:0000:0000:0000:0000:0000:00c0/00122\"\n ]\n }, \n {\n \"cidr\": \"10.0.2.24/0029\", \n \"except\": []\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"tcp\", \n \"port\": 65534, \n \"endPort\": 65535\n }, \n {\n \"protocol\": \"tcp\"\n }, \n {\n \"protocol\": \"any\", \n \"port\": 3\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }, \n {\n \"ports\": [\n {\n \"protocol\": \"any\", \n \"port\": 65535\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 81, \n \"endPort\": 83\n }, \n {\n \"protocol\": \"udp\", \n \"port\": 445\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::274/127\", \n \"except\": [\n \"2001:db8::274\", \n \"2001:db8::275/128\", \n \"2001:0db8:0000:0000:0000:0000:0000:0275\"\n ]\n }, \n {\n \"cidr\": \"5ac9:371c:13e7:b7f5:64f6:eede:0000:0000/96\"\n }, \n {\n \"cidr\": \"10.0.3.128/25\"\n }\n ], \n \"ports\": [\n {\n \"port\": 2, \n \"endPort\": 3\n }\n ]\n }, \n {\n \"cidr\": \"2001:db8::3d5/128\", \n \"protocol\": \"any\", \n \"port\": 19\n }, \n {\n \"cidr\": \"10.0.1.224/31\", \n \"protocol\": \"any\", \n \"port\": 137\n }, \n {\n \"cidr\": \"10.0.2.188/31\", \n \"protocol\": \"udp\", \n \"port\": 275\n }, \n {\n \"cidr\": \"10.0.1.220/31\", \n \"protocol\": \"tcp\", \n \"port\": 319\n }, \n {\n \"cidr\": \"10.0.3.0/31\", \n \"protocol\": \"tcp\", \n \"port\": 167\n }, \n {\n \"cidr\": \"2001:db8::3e7/128\", \n \"protocol\": \"any\", \n \"port\": 13, \n \"endPort\": 15\n }, \n {\n \"cidr\": \"10.0.2.240/30\", \n \"protocol\": \"udp\", \n \"port\": 27, \n \"endPort\": 30\n }, \n {\n \"cidr\": \"10.0.1.32/30\", \n \"protocol\": \"udp\", \n \"port\": 277, \n \"endPort\": 277\n }, \n {\n \"cidr\": \"10.0.3.212/30\", \n \"protocol\": \"tcp\", \n \"port\": 45\n }, \n {\n \"cidr\": \"10.0.0.184/30\", \n \"protocol\": \"udp\", \n \"port\": 155\n }, \n {\n \"cidr\": \"2001:db8::39f/128\", \n \"protocol\": \"tcp\", \n \"port\": 125\n }, \n {\n \"cidr\": \"10.0.0.102/32\", \n \"protocol\": \"tcp\", \n \"port\": 233\n }, \n {\n \"cidr\": \"10.0.2.246/31\", \n \"protocol\": \"udp\", \n \"port\": 29\n }, \n {\n \"cidr\": \"10.0.0.244/30\", \n \"protocol\": \"tcp\", \n \"port\": 121\n }, \n {\n \"cidr\": \"10.0.3.124/30\", \n \"protocol\": \"tcp\", \n \"port\": 301\n }, \n {\n \"cidr\": \"10.0.0.184/32\", \n \"protocol\": \"udp\", \n \"port\": 175\n }, \n {\n \"cidr\": \"2001:db8::384/128\", \n \"protocol\": \"udp\", \n \"port\": 73\n }, \n {\n \"cidr\": \"10.0.2.99/32\", \n \"protocol\": \"tcp\", \n \"port\": 377, \n \"endPort\": 379\n }, \n {\n \"cidr\": \"10.0.1.204/30\", \n \"protocol\": \"udp\", \n \"port\": 243, \n \"endPort\": 245\n }, \n {\n \"cidr\": \"10.0.3.26/31\", \n \"protocol\": \"any\", \n \"port\": 365, \n \"endPort\": 365\n }, \n {\n \"cidr\": \"2001:db8::18/128\", \n \"protocol\": \"any\", \n \"port\": 37, \n \"endPort\": 40\n }, \n {\n \"cidr\": \"10.0.3.142/31\", \n \"protocol\": \"any\", \n \"port\": 77\n }, \n {\n \"cidr\": \"10.0.3.246/32\", \n \"protocol\": \"udp\", \n \"port\": 157\n }, \n {\n \"cidr\": \"10.0.2.151/32\", \n \"protocol\": \"udp\", \n \"port\": 203\n }, \n {\n \"cidr\": \"10.0.3.100/30\", \n \"protocol\": \"udp\", \n \"port\": 111, \n \"endPort\": 115\n }, \n {\n \"cidr\": \"2001:db8::14c/128\", \n \"protocol\": \"tcp\", \n \"port\": 161, \n \"endPort\": 161\n }, \n {\n \"cidr\": \"2001:db8::2db/128\", \n \"protocol\": \"udp\", \n \"port\": 317\n }, \n {\n \"cidr\": \"10.0.1.78/31\", \n \"protocol\": \"udp\", \n \"port\": 161\n }, \n {\n \"cidr\": \"10.0.0.248/32\", \n \"protocol\": \"udp\", \n \"port\": 309, \n \"endPort\": 311\n }, \n {\n \"cidr\": \"10.0.3.20/31\", \n \"protocol\": \"tcp\", \n \"port\": 29, \n \"endPort\": 31\n }, \n {\n \"cidr\": \"10.0.0.158/31\", \n \"protocol\": \"any\", \n \"port\": 185, \n \"endPort\": 186\n }, \n {\n \"cidr\": \"10.0.1.216/32\", \n \"protocol\": \"tcp\", \n \"port\": 195, \n \"endPort\": 195\n }, \n {\n \"cidr\": \"10.0.2.236/31\", \n \"protocol\": \"tcp\", \n \"port\": 295\n }, \n {\n \"cidr\": \"2001:db8::261/128\", \n \"protocol\": \"any\", \n \"port\": 113, \n \"endPort\": 116\n }, \n {\n \"cidr\": \"10.0.2.247/32\", \n \"protocol\": \"tcp\", \n \"port\": 35\n }, \n {\n \"cidr\": \"10.0.2.200/30\", \n \"protocol\": \"udp\", \n \"port\": 147, \n \"endPort\": 151\n }, \n {\n \"cidr\": \"10.0.2.184/30\", \n \"protocol\": \"udp\", \n \"port\": 363\n }, \n {\n \"cidr\": \"2001:db8::321/128\", \n \"protocol\": \"tcp\", \n \"port\": 7\n }, \n {\n \"cidr\": \"10.0.0.88/30\", \n \"protocol\": \"tcp\", \n \"port\": 93\n }, \n {\n \"cidr\": \"2001:db8::231/128\", \n \"protocol\": \"tcp\", \n \"port\": 319\n }, \n {\n \"cidr\": \"10.0.3.122/31\", \n \"protocol\": \"udp\", \n \"port\": 379\n }, \n {\n \"cidr\": \"10.0.1.8/30\", \n \"protocol\": \"tcp\", \n \"port\": 361\n }, \n {\n \"cidr\": \"2001:db8::1e5/128\", \n \"protocol\": \"tcp\", \n \"port\": 177\n }, \n {\n \"cidr\": \"10.0.0.51/32\", \n \"protocol\": \"udp\", \n \"port\": 287, \n \"endPort\": 288\n }, \n {\n \"cidr\": \"10.0.0.172/30\", \n \"protocol\": \"tcp\", \n \"port\": 121\n }, \n {\n \"cidr\": \"10.0.1.24/30\", \n \"protocol\": \"udp\", \n \"port\": 211, \n \"endPort\": 214\n }, \n {\n \"cidr\": \"10.0.3.207/32\", \n \"protocol\": \"any\", \n \"port\": 149\n }, \n {\n \"cidr\": \"2001:db8::2b3/128\", \n \"protocol\": \"udp\", \n \"port\": 27\n }, \n {\n \"cidr\": \"2001:db8::395/128\", \n \"protocol\": \"udp\", \n \"port\": 123, \n \"endPort\": 125\n }, \n {\n \"cidr\": \"2001:db8::5f/128\", \n \"protocol\": \"any\", \n \"port\": 107\n }, \n {\n \"cidr\": \"10.0.0.187/32\", \n \"protocol\": \"udp\", \n \"port\": 25\n }, \n {\n \"cidr\": \"10.0.3.87/32\", \n \"protocol\": \"udp\", \n \"port\": 45\n }, \n {\n \"cidr\": \"10.0.1.0/30\", \n \"protocol\": \"any\", \n \"port\": 123\n }, \n {\n \"cidr\": \"10.0.2.152/31\", \n \"protocol\": \"tcp\", \n \"port\": 353\n }, \n {\n \"cidr\": \"2001:db8::370/128\", \n \"protocol\": \"udp\", \n \"port\": 297\n }, \n {\n \"cidr\": \"10.0.0.158/31\", \n \"protocol\": \"tcp\", \n \"port\": 317\n }, \n {\n \"cidr\": \"10.0.1.149/32\", \n \"protocol\": \"any\", \n \"port\": 263\n }, \n {\n \"cidr\": \"10.0.2.52/30\", \n \"protocol\": \"udp\", \n \"port\": 363\n }, \n {\n \"cidr\": \"10.0.3.254/31\", \n \"protocol\": \"udp\", \n \"port\": 153\n }, \n {\n \"cidr\": \"10.0.2.226/32\", \n \"protocol\": \"udp\", \n \"port\": 15\n }, \n {\n \"cidr\": \"2001:db8::2d4/128\", \n \"protocol\": \"tcp\", \n \"port\": 255\n }, \n {\n \"cidr\": \"2001:db8::335/128\", \n \"protocol\": \"any\", \n \"port\": 203, \n \"endPort\": 204\n }, \n {\n \"cidr\": \"10.0.3.44/30\", \n \"protocol\": \"tcp\", \n \"port\": 395\n }, \n {\n \"cidr\": \"10.0.0.241/32\", \n \"protocol\": \"tcp\", \n \"port\": 183\n }, \n {\n \"cidr\": \"2001:db8::269/128\", \n \"protocol\": \"tcp\", \n \"port\": 239, \n \"endPort\": 239\n }, \n {\n \"cidr\": \"2001:db8::2b5/128\", \n \"protocol\": \"tcp\", \n \"port\": 173\n }, \n {\n \"cidr\": \"2001:db8::122/128\", \n \"protocol\": \"tcp\", \n \"port\": 291\n }, \n {\n \"cidr\": \"10.0.2.40/30\", \n \"protocol\": \"any\", \n \"port\": 105, \n \"endPort\": 108\n }, \n {\n \"cidr\": \"10.0.1.252/30\", \n \"protocol\": \"tcp\", \n \"port\": 177\n }, \n {\n \"cidr\": \"2001:db8::370/128\", \n \"protocol\": \"udp\", \n \"port\": 281\n }, \n {\n \"cidr\": \"10.0.3.204/30\", \n \"protocol\": \"any\", \n \"port\": 269, \n \"endPort\": 271\n }, \n {\n \"cidr\": \"10.0.1.56/31\", \n \"protocol\": \"udp\", \n \"port\": 349\n }, \n {\n \"cidr\": \"2001:db8::36b/128\", \n \"protocol\": \"udp\", \n \"port\": 17\n }, \n {\n \"cidr\": \"10.0.3.152/31\", \n \"protocol\": \"any\", \n \"port\": 177\n }, \n {\n \"cidr\": \"2001:db8::36b/128\", \n \"protocol\": \"tcp\", \n \"port\": 307\n }, \n {\n \"cidr\": \"10.0.0.94/31\", \n \"protocol\": \"udp\", \n \"port\": 327\n }, \n {\n \"cidr\": \"10.0.2.168/30\", \n \"protocol\": \"udp\", \n \"port\": 355\n }, \n {\n \"cidr\": \"10.0.3.160/32\", \n \"protocol\": \"udp\", \n \"port\": 19\n }, \n {\n \"cidr\": \"10.0.2.98/32\", \n \"protocol\": \"any\", \n \"port\": 177\n }, \n {\n \"cidr\": \"10.0.1.137/32\", \n \"protocol\": \"any\", \n \"port\": 41, \n \"endPort\": 42\n }, \n {\n \"cidr\": \"2001:db8::250/128\", \n \"protocol\": \"tcp\", \n \"port\": 95, \n \"endPort\": 97\n }, \n {\n \"cidr\": \"10.0.0.195/32\", \n \"protocol\": \"udp\", \n \"port\": 173\n }, \n {\n \"cidr\": \"10.0.2.245/32\", \n \"protocol\": \"tcp\", \n \"port\": 353\n }, \n {\n \"cidr\": \"10.0.0.32/30\", \n \"protocol\": \"any\", \n \"port\": 83\n }, \n {\n \"cidr\": \"10.0.0.188/32\", \n \"protocol\": \"any\", \n \"port\": 121\n }, \n {\n \"cidr\": \"10.0.0.236/30\", \n \"protocol\": \"any\", \n \"port\": 233\n }, \n {\n \"cidr\": \"2001:db8::137/128\", \n \"protocol\": \"any\", \n \"port\": 45\n }, \n {\n \"cidr\": \"2001:db8::a6/128\", \n \"protocol\": \"udp\", \n \"port\": 235, \n \"endPort\": 237\n }, \n {\n \"cidr\": \"10.0.3.162/31\", \n \"protocol\": \"tcp\", \n \"port\": 271, \n \"endPort\": 274\n }, \n {\n \"cidr\": \"10.0.2.98/31\", \n \"protocol\": \"any\", \n \"port\": 165, \n \"endPort\": 167\n }, \n {\n \"cidr\": \"10.0.3.164/32\", \n \"protocol\": \"any\", \n \"port\": 367\n }, \n {\n \"cidr\": \"10.0.1.204/31\", \n \"protocol\": \"any\", \n \"port\": 295\n }, \n {\n \"cidr\": \"2001:db8::1ac/128\", \n \"protocol\": \"any\", \n \"port\": 347\n }, \n {\n \"cidr\": \"10.0.3.200/30\", \n \"protocol\": \"udp\", \n \"port\": 119\n }, \n {\n \"cidr\": \"2001:db8::6b/128\", \n \"protocol\": \"any\", \n \"port\": 307\n }, \n {\n \"cidr\": \"10.0.1.90/31\", \n \"protocol\": \"any\", \n \"port\": 259, \n \"endPort\": 261\n }, \n {\n \"cidr\": \"10.0.3.80/30\", \n \"protocol\": \"any\", \n \"port\": 63\n }, \n {\n \"cidr\": \"10.0.2.34/31\", \n \"protocol\": \"any\", \n \"port\": 43\n }, \n {\n \"cidr\": \"10.0.1.104/32\", \n \"protocol\": \"tcp\", \n \"port\": 55\n }, \n {\n \"cidr\": \"10.0.3.184/30\", \n \"protocol\": \"tcp\", \n \"port\": 271\n }, \n {\n \"cidr\": \"10.0.1.198/31\", \n \"protocol\": \"udp\", \n \"port\": 389\n }, \n {\n \"cidr\": \"10.0.2.153/32\", \n \"protocol\": \"any\", \n \"port\": 65, \n \"endPort\": 68\n }, \n {\n \"cidr\": \"2001:db8::15d/128\", \n \"protocol\": \"any\", \n \"port\": 345, \n \"endPort\": 349\n }, \n {\n \"cidr\": \"10.0.3.104/30\", \n \"protocol\": \"any\", \n \"port\": 275, \n \"endPort\": 275\n }, \n {\n \"cidr\": \"10.0.2.12/30\", \n \"protocol\": \"any\", \n \"port\": 293\n }, \n {\n \"cidr\": \"2001:db8::15c/128\", \n \"protocol\": \"udp\", \n \"port\": 37\n }, \n {\n \"cidr\": \"2001:db8::337/128\", \n \"protocol\": \"tcp\", \n \"port\": 203\n }, \n {\n \"cidr\": \"10.0.1.236/30\", \n \"protocol\": \"udp\", \n \"port\": 167\n }, \n {\n \"cidr\": \"10.0.3.250/31\", \n \"protocol\": \"udp\", \n \"port\": 231\n }, \n {\n \"cidr\": \"10.0.1.224/31\", \n \"protocol\": \"any\", \n \"port\": 399, \n \"endPort\": 399\n }, \n {\n \"cidr\": \"10.0.1.215/32\", \n \"protocol\": \"any\", \n \"port\": 71\n }, \n {\n \"cidr\": \"2001:db8::1d2/128\", \n \"protocol\": \"tcp\", \n \"port\": 349\n }, \n {\n \"cidr\": \"10.0.1.234/32\", \n \"protocol\": \"udp\", \n \"port\": 93\n }, \n {\n \"cidr\": \"2001:db8::39b/128\", \n \"protocol\": \"udp\", \n \"port\": 15\n }, \n {\n \"cidr\": \"10.0.1.36/31\", \n \"protocol\": \"any\", \n \"port\": 265, \n \"endPort\": 266\n }, \n {\n \"cidr\": \"10.0.2.92/31\", \n \"protocol\": \"udp\", \n \"port\": 315\n }, \n {\n \"cidr\": \"10.0.3.88/30\", \n \"protocol\": \"udp\", \n \"port\": 223\n }, \n {\n \"cidr\": \"10.0.3.212/30\", \n \"protocol\": \"any\", \n \"port\": 267\n }, \n {\n \"cidr\": \"10.0.0.65/32\", \n \"protocol\": \"tcp\", \n \"port\": 193\n }, \n {\n \"cidr\": \"10.0.3.1/32\", \n \"protocol\": \"tcp\", \n \"port\": 337\n }, \n {\n \"cidr\": \"10.0.2.105/32\", \n \"protocol\": \"any\", \n \"port\": 205\n }, \n {\n \"cidr\": \"2001:db8::7b/128\", \n \"protocol\": \"any\", \n \"port\": 205\n }, \n {\n \"cidr\": \"10.0.3.42/32\", \n \"protocol\": \"udp\", \n \"port\": 1\n }, \n {\n \"cidr\": \"10.0.1.11/32\", \n \"protocol\": \"tcp\", \n \"port\": 31\n }, \n {\n \"cidr\": \"10.0.2.233/32\", \n \"protocol\": \"udp\", \n \"port\": 45\n }, \n {\n \"cidr\": \"10.0.1.244/31\", \n \"protocol\": \"udp\", \n \"port\": 175\n }, \n {\n \"cidr\": \"10.0.2.4/31\", \n \"protocol\": \"tcp\", \n \"port\": 323\n }, \n {\n \"cidr\": \"10.0.3.136/30\", \n \"protocol\": \"tcp\", \n \"port\": 7\n }, \n {\n \"cidr\": \"2001:db8::155/128\", \n \"protocol\": \"any\", \n \"port\": 93, \n \"endPort\": 95\n }, \n {\n \"cidr\": \"10.0.1.220/32\", \n \"protocol\": \"tcp\", \n \"port\": 263, \n \"endPort\": 266\n }, \n {\n \"cidr\": \"2001:db8::11b/128\", \n \"protocol\": \"udp\", \n \"port\": 333\n }, \n {\n \"cidr\": \"10.0.0.92/31\", \n \"protocol\": \"any\", \n \"port\": 27, \n \"endPort\": 27\n }, \n {\n \"cidr\": \"10.0.0.63/32\", \n \"protocol\": \"any\", \n \"port\": 61\n }, \n {\n \"cidr\": \"10.0.2.64/30\", \n \"protocol\": \"any\", \n \"port\": 259\n }, \n {\n \"cidr\": \"10.0.0.76/30\", \n \"protocol\": \"tcp\", \n \"port\": 215\n }, \n {\n \"cidr\": \"10.0.3.198/31\", \n \"protocol\": \"tcp\", \n \"port\": 371, \n \"endPort\": 373\n }, \n {\n \"cidr\": \"2001:db8::18/128\", \n \"protocol\": \"udp\", \n \"port\": 169\n }, \n {\n \"cidr\": \"10.0.0.44/32\", \n \"protocol\": \"tcp\", \n \"port\": 219\n }, \n {\n \"cidr\": \"10.0.2.168/30\", \n \"protocol\": \"tcp\", \n \"port\": 123\n }, \n {\n \"cidr\": \"2001:db8::46/128\", \n \"protocol\": \"udp\", \n \"port\": 177\n }, \n {\n \"cidr\": \"10.0.2.28/32\", \n \"protocol\": \"tcp\", \n \"port\": 217\n }, \n {\n \"cidr\": \"2001:db8::23e/128\", \n \"protocol\": \"tcp\", \n \"port\": 263\n }, \n {\n \"cidr\": \"2001:db8::2bd/128\", \n \"protocol\": \"udp\", \n \"port\": 189, \n \"endPort\": 191\n }, \n {\n \"cidr\": \"10.0.2.8/31\", \n \"protocol\": \"any\", \n \"port\": 127, \n \"endPort\": 131\n }, \n {\n \"cidr\": \"10.0.3.2/31\", \n \"protocol\": \"udp\", \n \"port\": 199, \n \"endPort\": 199\n }, \n {\n \"cidr\": \"2001:db8::9d/128\", \n \"protocol\": \"udp\", \n \"port\": 41\n }, \n {\n \"cidr\": \"10.0.0.52/30\", \n \"protocol\": \"udp\", \n \"port\": 371, \n \"endPort\": 372\n }, \n {\n \"cidr\": \"10.0.0.180/30\", \n \"protocol\": \"tcp\", \n \"port\": 297, \n \"endPort\": 298\n }, \n {\n \"cidr\": \"2001:db8::3b9/128\", \n \"protocol\": \"any\", \n \"port\": 225\n }, \n {\n \"cidr\": \"10.0.0.238/31\", \n \"protocol\": \"tcp\", \n \"port\": 335\n }, \n {\n \"cidr\": \"10.0.3.191/32\", \n \"protocol\": \"tcp\", \n \"port\": 287, \n \"endPort\": 288\n }, \n {\n \"cidr\": \"2001:db8::1c4/128\", \n \"protocol\": \"any\", \n \"port\": 335\n }, \n {\n \"cidr\": \"10.0.2.222/31\", \n \"protocol\": \"tcp\", \n \"port\": 83\n }, \n {\n \"cidr\": \"10.0.3.38/31\", \n \"protocol\": \"udp\", \n \"port\": 43\n }, \n {\n \"cidr\": \"2001:db8::1e7/128\", \n \"protocol\": \"tcp\", \n \"port\": 161, \n \"endPort\": 162\n }, \n {\n \"cidr\": \"10.0.0.84/31\", \n \"protocol\": \"udp\", \n \"port\": 267\n }, \n {\n \"cidr\": \"2001:db8::42/128\", \n \"protocol\": \"udp\", \n \"port\": 343\n }, \n {\n \"cidr\": \"10.0.1.26/31\", \n \"protocol\": \"tcp\", \n \"port\": 89\n }, \n {\n \"cidr\": \"10.0.2.232/30\", \n \"protocol\": \"any\", \n \"port\": 269\n }, \n {\n \"cidr\": \"10.0.3.171/32\", \n \"protocol\": \"any\", \n \"port\": 373\n }, \n {\n \"cidr\": \"2001:db8::34e/128\", \n \"protocol\": \"udp\", \n \"port\": 319\n }, \n {\n \"cidr\": \"10.0.2.100/30\", \n \"protocol\": \"udp\", \n \"port\": 255, \n \"endPort\": 255\n }, \n {\n \"cidr\": \"10.0.3.20/30\", \n \"protocol\": \"tcp\", \n \"port\": 399\n }, \n {\n \"cidr\": \"10.0.0.96/31\", \n \"protocol\": \"udp\", \n \"port\": 245\n }, \n {\n \"cidr\": \"10.0.0.44/31\", \n \"protocol\": \"any\", \n \"port\": 233, \n \"endPort\": 235\n }, \n {\n \"cidr\": \"10.0.0.208/31\", \n \"protocol\": \"tcp\", \n \"port\": 131\n }, \n {\n \"cidr\": \"10.0.0.96/31\", \n \"protocol\": \"tcp\", \n \"port\": 71\n }, \n {\n \"cidr\": \"10.0.1.12/30\", \n \"protocol\": \"any\", \n \"port\": 325, \n \"endPort\": 329\n }, \n {\n \"cidr\": \"10.0.2.114/31\", \n \"protocol\": \"any\", \n \"port\": 11, \n \"endPort\": 15\n }, \n {\n \"cidr\": \"10.0.2.160/31\", \n \"protocol\": \"udp\", \n \"port\": 35\n }, \n {\n \"cidr\": \"2001:db8::d6/128\", \n \"protocol\": \"udp\", \n \"port\": 283, \n \"endPort\": 286\n }, \n {\n \"cidr\": \"10.0.3.20/31\", \n \"protocol\": \"any\", \n \"port\": 215\n }, \n {\n \"cidr\": \"2001:db8::2e6/128\", \n \"protocol\": \"any\", \n \"port\": 137, \n \"endPort\": 139\n }, \n {\n \"cidr\": \"10.0.0.220/30\", \n \"protocol\": \"udp\", \n \"port\": 283\n }, \n {\n \"cidr\": \"2001:db8::3d0/128\", \n \"protocol\": \"tcp\", \n \"port\": 175\n }, \n {\n \"cidr\": \"10.0.3.114/32\", \n \"protocol\": \"any\", \n \"port\": 89\n }, \n {\n \"cidr\": \"10.0.0.8/30\", \n \"protocol\": \"tcp\", \n \"port\": 29\n }, \n {\n \"cidr\": \"2001:db8::3da/128\", \n \"protocol\": \"tcp\", \n \"port\": 251\n }, \n {\n \"cidr\": \"10.0.2.14/31\", \n \"protocol\": \"tcp\", \n \"port\": 231, \n \"endPort\": 234\n }, \n {\n \"cidr\": \"2001:db8::89/128\", \n \"protocol\": \"any\", \n \"port\": 113\n }, \n {\n \"cidr\": \"10.0.1.37/32\", \n \"protocol\": \"udp\", \n \"port\": 219, \n \"endPort\": 222\n }, \n {\n \"cidr\": \"10.0.2.78/32\", \n \"protocol\": \"any\", \n \"port\": 303\n }, \n {\n \"cidr\": \"10.0.0.36/30\", \n \"protocol\": \"any\", \n \"port\": 67, \n \"endPort\": 67\n }, \n {\n \"cidr\": \"10.0.0.148/30\", \n \"protocol\": \"any\", \n \"port\": 209\n }, \n {\n \"cidr\": \"10.0.1.251/32\", \n \"protocol\": \"udp\", \n \"port\": 307, \n \"endPort\": 311\n }, \n {\n \"cidr\": \"10.0.0.9/32\", \n \"protocol\": \"udp\", \n \"port\": 323, \n \"endPort\": 324\n }, \n {\n \"cidr\": \"10.0.1.19/32\", \n \"protocol\": \"udp\", \n \"port\": 337\n }, \n {\n \"cidr\": \"10.0.2.226/32\", \n \"protocol\": \"udp\", \n \"port\": 9\n }, \n {\n \"cidr\": \"2001:db8::3be/128\", \n \"protocol\": \"any\", \n \"port\": 193, \n \"endPort\": 194\n }, \n {\n \"cidr\": \"10.0.3.111/32\", \n \"protocol\": \"tcp\", \n \"port\": 241, \n \"endPort\": 245\n }, \n {\n \"cidr\": \"10.0.2.232/31\", \n \"protocol\": \"any\", \n \"port\": 311\n }, \n {\n \"cidr\": \"10.0.3.195/32\", \n \"protocol\": \"tcp\", \n \"port\": 341\n }, \n {\n \"cidr\": \"10.0.2.110/31\", \n \"protocol\": \"tcp\", \n \"port\": 185\n }, \n {\n \"cidr\": \"10.0.3.234/31\", \n \"protocol\": \"udp\", \n \"port\": 183\n }, \n {\n \"cidr\": \"10.0.1.92/32\", \n \"protocol\": \"any\", \n \"port\": 163\n }, \n {\n \"cidr\": \"10.0.1.44/32\", \n \"protocol\": \"udp\", \n \"port\": 51, \n \"endPort\": 51\n }, \n {\n \"cidr\": \"2001:db8::246/128\", \n \"protocol\": \"any\", \n \"port\": 243\n }, \n {\n \"cidr\": \"2001:db8::51/128\", \n \"protocol\": \"udp\", \n \"port\": 263, \n \"endPort\": 264\n }, \n {\n \"cidr\": \"10.0.3.88/30\", \n \"protocol\": \"udp\", \n \"port\": 219, \n \"endPort\": 220\n }, \n {\n \"cidr\": \"2001:db8::193/128\", \n \"protocol\": \"udp\", \n \"port\": 285\n }, \n {\n \"cidr\": \"10.0.0.32/30\", \n \"protocol\": \"any\", \n \"port\": 305\n }, \n {\n \"cidr\": \"10.0.3.88/30\", \n \"protocol\": \"tcp\", \n \"port\": 43, \n \"endPort\": 45\n }, \n {\n \"cidr\": \"2001:db8::107/128\", \n \"protocol\": \"tcp\", \n \"port\": 263\n }, \n {\n \"cidr\": \"10.0.1.112/31\", \n \"protocol\": \"any\", \n \"port\": 19\n }, \n {\n \"cidr\": \"2001:db8::3d5/128\", \n \"protocol\": \"udp\", \n \"port\": 159\n }, \n {\n \"cidr\": \"2001:db8::3d/128\", \n \"protocol\": \"udp\", \n \"port\": 121\n }, \n {\n \"cidr\": \"10.0.1.92/32\", \n \"protocol\": \"tcp\", \n \"port\": 81, \n \"endPort\": 82\n }, \n {\n \"cidr\": \"10.0.2.82/32\", \n \"protocol\": \"udp\", \n \"port\": 75\n }, \n {\n \"cidr\": \"10.0.3.120/32\", \n \"protocol\": \"udp\", \n \"port\": 71\n }, \n {\n \"cidr\": \"10.0.0.155/32\", \n \"protocol\": \"udp\", \n \"port\": 87\n }, \n {\n \"cidr\": \"10.0.0.140/32\", \n \"protocol\": \"any\", \n \"port\": 249\n }, \n {\n \"cidr\": \"10.0.3.184/30\", \n \"protocol\": \"udp\", \n \"port\": 239, \n \"endPort\": 240\n }, \n {\n \"cidr\": \"10.0.3.84/30\", \n \"protocol\": \"tcp\", \n \"port\": 215\n }, \n {\n \"cidr\": \"10.0.0.184/30\", \n \"protocol\": \"any\", \n \"port\": 249\n }, \n {\n \"cidr\": \"10.0.0.20/30\", \n \"protocol\": \"tcp\", \n \"port\": 153\n }, \n {\n \"cidr\": \"10.0.0.68/30\", \n \"protocol\": \"udp\", \n \"port\": 389, \n \"endPort\": 389\n }, \n {\n \"cidr\": \"10.0.2.136/31\", \n \"protocol\": \"tcp\", \n \"port\": 189\n }, \n {\n \"cidr\": \"2001:db8::161/128\", \n \"protocol\": \"any\", \n \"port\": 293\n }, \n {\n \"cidr\": \"10.0.2.197/32\", \n \"protocol\": \"tcp\", \n \"port\": 81, \n \"endPort\": 83\n }, \n {\n \"cidr\": \"10.0.2.74/31\", \n \"protocol\": \"tcp\", \n \"port\": 389\n }, \n {\n \"cidr\": \"10.0.2.152/30\", \n \"protocol\": \"udp\", \n \"port\": 327, \n \"endPort\": 328\n }, \n {\n \"cidr\": \"10.0.1.114/31\", \n \"protocol\": \"any\", \n \"port\": 145\n }\n ], \n \"deny\": [\n {\n \"cidr\": \"10.0.2.0/25\", \n \"except\": [\n \"10.0.2.96/027\", \n \"10.0.2.103/31\"\n ], \n \"protocol\": \"tcp\", \n \"port\": 82, \n \"endPort\": 86\n }, \n {\n \"cidr\": \"2001:db8::284/126\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"10.0.0.86/255.255.255.254\"\n }, \n {\n \"cidr\": \"10.0.1.0/255.255.255.0\", \n \"except\": [\n \"10.0.1.58\", \n \"0.0.0.0/0\"\n ]\n }, \n {\n \"cidr\": \"10.0.3.88/29\"\n }, \n {\n \"cidr\": \"540a:fe1:4f51:3550:347f:4f7b:b4b2:6bd8/0\", \n \"except\": []\n }\n ]\n }, \n {\n \"to\": [\n {\n \"cidr\": \"2001:db8::398/127\"\n }, \n {\n \"cidr\": \"2001:db8::77/119\"\n }\n ]\n }, \n {\n \"cidr\": \"2001:db8::36/128\", \n \"except\": [\n \"2001:db8::36\"\n ], \n \"protocol\": \"tcp\"\n }, \n {\n \"to\": [\n {\n \"cidr\": \"0.0.0.0/0\"\n }\n ], \n \"ports\": [\n {\n \"protocol\": \"udp\"\n }, \n {\n \"protocol\": \"tcp\", \n \"port\": 81, \n \"endPort\": 86\n }, \n {\n \"protocol\": \"any\"\n }, \n {\n \"protocol\": \"icmp\"\n }\n ]\n }\n ]\n}"}],"expected":{"error":"allow emits at most 256 native rules"}}, + {"name":"random #185","input":[{"text":"{\"allow\": [{\"cidr\": \"2001:db8::2d0/124\", \"except\": [], \"protocol\": \"tcp\"}, {\"to\": [{\"cidr\": \"176.2.200.102/4\", \"except\": [\"177.97.93.166/255.0.0.0\", \"184.0.0.0/7\"]}, {\"cidr\": \"2001:db8::/121\"}, {\"cidr\": \"110.91.37.163/32\", \"except\": [\"110.91.37.163/255.255.255.255\"]}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0260/0127\", \"except\": [\"2001:db8::261/127\", \"2001:0db8:0000:0000:0000:0000:0000:0260/127\"]}], \"ports\": [{\"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65534}, {\"protocol\": \"udp\"}]}, {\"cidr\": \"2001:db8::180/0123\", \"protocol\": \"udp\"}, {\"to\": [{\"cidr\": \"10.0.0.0/23\", \"except\": []}, {\"cidr\": \"10.0.0.0/24\", \"except\": []}]}]}"}],"expected":{"allow":["10.0.0.0/23","176.0.0.0/8:tcp:65530-65534","176.0.0.0/8:udp","178.0.0.0/7:tcp:65530-65534","178.0.0.0/7:udp","180.0.0.0/6:tcp:65530-65534","180.0.0.0/6:udp","186.0.0.0/7:tcp:65530-65534","186.0.0.0/7:udp","188.0.0.0/6:tcp:65530-65534","188.0.0.0/6:udp","2001:db8::/121:tcp:65530-65534","2001:db8::/121:udp","2001:db8::180/123:udp","2001:db8::2d0/124:tcp"],"deny":[]}}, + {"name":"random #186","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"2001:db8::1b4/127\", \n\t\t\t\"except\": [\n\t\t\t\t\"2001:db8::1b5\"\n\t\t\t], \n\t\t\t\"protocol\": \"tcp\", \n\t\t\t\"port\": 6\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"143.0.0.0/08\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"143.119.152.25/00013\", \n\t\t\t\t\t\t\"143.128.0.0/255.192.0.0\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"36.32.0.0/12\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"36.32.0.0/12\", \n\t\t\t\t\t\t\"36.44.0.0/14\", \n\t\t\t\t\t\t\"36.34.33.169/17\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.212/22\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::/119\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::/118\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::f/124\", \n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0000:01a9/0120\", \n\t\t\t\t\t\t\"2001:db8::220/121\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 442\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"}],"expected":{"allow":["10.0.0.0/22:tcp","143.0.0.0/10","143.64.0.0/11","143.96.0.0/12","143.120.0.0/13","143.192.0.0/10","2001:db8::/119:tcp","2001:db8::280/121:tcp","2001:db8::300/120:tcp"],"deny":[]}}, + {"name":"random #187","input":[{"text":"{\"allow\":[{\"to\":[{\"cidr\":\"10.0.0.0/22\"},{\"cidr\":\"10.0.3.90/31\",\"except\":[]},{\"cidr\":\"2001:db8::280/0123\"},{\"cidr\":\"192.0.0.0/2\",\"except\":[\"212.128.0.0/9\"]}],\"ports\":[{\"protocol\":\"any\"},{\"protocol\":\"tcp\"},{\"protocol\":\"any\",\"port\":444},{\"protocol\":\"any\"}]},{\"ports\":[{\"protocol\":\"any\"},{\"protocol\":\"tcp\",\"port\":7,\"endPort\":13}]},{\"cidr\":\"10.0.3.208/28\",\"protocol\":\"udp\",\"port\":445},{\"to\":[{\"cidr\":\"10.0.2.0/23\"},{\"cidr\":\"10.0.2.0/255.255.254.0\",\"except\":[\"10.0.2.128/26\"]},{\"cidr\":\"2001:db8::/123\",\"except\":[]},{\"cidr\":\"2001:db8::/118\"}],\"ports\":[{\"protocol\":\"tcp\",\"port\":79}]}],\"deny\":[{\"cidr\":\"2001:db8::28f/00124\",\"protocol\":\"icmp\",\"ports\":[]},{\"to\":[{\"cidr\":\"::ffff:15.224.251.152/128\"},{\"cidr\":\"2001:db8::c0/122\"},{\"cidr\":\"225.30.0.0/16\"}]},{\"cidr\":\"10.0.1.0/25\",\"protocol\":\"tcp\",\"port\":80}]}"}],"expected":{"error":"deny[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #188","input":[{"text":"{\n \"allow\": [], \n \"deny\": [\n {\n \"cidr\": \"128.0.0.0/2\", \n \"except\": [], \n \"protocol\": \"tcp\", \n \"port\": 9, \n \"endPort\": 12, \n \"to\": []\n }\n ]\n}"}],"expected":{"error":"deny[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #189","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0380/123\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"2001:db8::394/127\", \n\t\t\t\t\t\t\"2001:0db8:0000:0000:0000:0000:0.0.3.128/124\", \n\t\t\t\t\t\t\"2001:db8::380/123\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"64.0.0.0/4\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"66.0.0.0/8\", \n\t\t\t\t\t\t\"69.236.148.130/10\", \n\t\t\t\t\t\t\"77.241.88.244/6\"\n\t\t\t\t\t]\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.65/32\", \n\t\t\t\t\t\"except\": []\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 82\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 1, \n\t\t\t\t\t\"endPort\": 6\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 65534, \n\t\t\t\t\t\"endPort\": 65535\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"cidr\": \"2001:db8::/121\", \n\t\t\t\"protocol\": \"icmp\"\n\t\t}, \n\t\t{\n\t\t\t\"to\": \"192.0.2.0/24\"\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.3.84/030\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::e0/124\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 445\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"port\": 65530, \n\t\t\t\t\t\"endPort\": 65530\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"icmp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\", \n\t\t\t\t\t\"port\": 442, \n\t\t\t\t\t\"endPort\": 444\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\"\n\t\t\t\t}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.0/25\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"20.201.181.0/24\", \n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"20.201.181.16/29\", \n\t\t\t\t\t\t\"20.201.181.220/30\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{}\n\t\t\t]\n\t\t}, \n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::ea/125\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01f7/127\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:db8::/118\"\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"128.0.0.0/1\"\n\t\t\t\t}\n\t\t\t], \n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 443\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 79\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\", \n\t\t\t\t\t\"port\": 79\n\t\t\t\t}, \n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"any\", \n\t\t\t\t\t\"port\": 442, \n\t\t\t\t\t\"endPort\": 447\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t], \n\t\"deny\": []\n}"}],"expected":{"error":"allow[2].to must be an array"}}, + {"name":"random #190","input":[{"text":"{\"deny\": [{\"cidr\": \"10.0.1.126/32\", \"protocol\": \"tcp\", \"port\": 65531}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:006c/125\", \"protocol\": \"tcp\", \"port\": 65530, \"endPort\": 65535}, {\"cidr\": \"2001:db8::e0/123\", \"except\": [\"2001:db8::ea/127\"], \"protocol\": \"any\"}, {\"to\": [{\"cidr\": \"164.149.0.0/16\"}, {\"cidr\": \"10.0.1.18/255.255.254.0\"}], \"ports\": [{\"port\": 65532}]}, {\"ports\": [{\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"2001:db8::3cc/126\", \"except\": [\"2001:0db8:0000:0000:0000:0000:0000:03ce/127\", \"2001:db8::3cc/128\"]}, {\"cidr\": \"10.0.3.24/30\"}], \"ports\": [{}, {\"protocol\": \"tcp\", \"port\": 1}]}, {\"cidr\": \"32.0.0.0/4\", \"except\": [\"32.0.0.0/9\"], \"protocol\": \"icmp\"}, {\"to\": [{\"cidr\": \"2001:db8::3d0/124\", \"except\": [\"2001:db8::3d8/128\", \"2001:db8::3d0/124\", \"2001:db8::3d0/124\"]}, {\"cidr\": \"10.0.0.0/24\"}]}]}"}],"expected":{"allow":[],"deny":["0.0.0.0/0:tcp","10.0.0.0/23:udp:65532","10.0.0.0/24","10.0.3.24/30","32.128.0.0/9:icmp","33.0.0.0/8:icmp","34.0.0.0/7:icmp","36.0.0.0/6:icmp","40.0.0.0/5:icmp","164.149.0.0/16:udp:65532","::/0:tcp","2001:db8::e0/125","2001:db8::e8/127","2001:db8::ec/126","2001:db8::f0/124","2001:db8::3cd/128"]}}, + {"name":"random #191","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.249/30\"\n},\n{\n\"cidr\": \"10.0.1.216/30\"\n},\n{\n\"cidr\": \"2001:db8::360/124\",\n\"except\": [\n\"2001:DB8::362/124\",\n\"2001:db8::200/121\"\n]\n}\n]\n}\n],\n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"195.5.0.0/16\"\n},\n{\n\"cidr\": \"10.0.2.0/026\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"udp\",\n\"port\": 440\n},\n{\n\"protocol\": \"any\",\n\"port\": 6\n},\n{\n\"protocol\": \"any\",\n\"port\": 65531,\n\"endPort\": 65534\n},\n{\n\"protocol\": \"tcp\",\n\"port\": 440\n}\n]\n},\n{\n\"cidr\": \"2001:db8::278/125\",\n\"except\": [],\n\"protocol\": \"any\",\n\"port\": true\n},\n{\n\"cidr\": \"10.0.3.192/26\",\n\"protocol\": \"tcp\"\n},\n{\n\"to\": [\n{\n\"cidr\": \"0000:0000:0000:0000:0000:0000:0000:0000/0\"\n},\n{\n\"cidr\": \"10.0.3.158/31\",\n\"except\": [\n\"10.0.3.158/31\",\n\"10.0.3.159/32\",\n\"10.0.3.158/31\"\n]\n},\n{\n\"cidr\": \"10.0.3.104/30\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\",\n\"port\": 442,\n\"endPort\": 442\n},\n{\n\"protocol\": \"any\",\n\"port\": 81\n},\n{\n\"protocol\": \"any\"\n},\n{\n\"protocol\": \"tcp\",\n\"port\": 5,\n\"endPort\": 6\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::60/125\"\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"3.32.0.0/12\"\n},\n{\n\"cidr\": \"10.0.0.116\"\n},\n{\n\"cidr\": \"10.0.1.8/0029\"\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.0/22\",\n\"except\": [\n\"10.0.2.32/27\",\n\"10.0.0.60/23\"\n]\n},\n{\n\"cidr\": \"10.0.1.104/29\",\n\"except\": []\n},\n{\n\"cidr\": \"10.0.0.255/0.0.0.1\"\n},\n{\n\"cidr\": \"0000:0000:0000:0000:0000:ffff:238.120.161.0/120\"\n}\n],\n\"ports\": [\n{},\n{\n\"protocol\": \"tcp\",\n\"port\": 81\n}\n]\n},\n{\n\"cidr\": \"0.0.0.0/1\",\n\"except\": [\n\"0.0.0.0/2\",\n\"0.0.0.0/1\"\n],\n\"protocol\": \"tcp\"\n}\n]\n}"}],"expected":{"error":"allow[0].to[2].except[1] must be contained in 2001:db8::360/124"}}, + {"name":"random #192","input":[{"text":""}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #193","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.135/00022\",\n\"except\": [\n\"10.0.0.64/27\",\n\"10.0.0.136/30\",\n\"10.0.3.68/30\"\n]\n},\n{\n\"cidr\": \"39.213.0.0/16\"\n},\n{\n\"cidr\": \"22ec:3cf2:61e7:925e:b54:6cbb:9133:9a3a/32\"\n},\n{\n\"cidr\": \"172.162.230.0/24\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"icmp\"\n},\n{\n\"protocol\": \"tcp\"\n},\n{\n\"protocol\": \"any\",\n\"port\": 12\n},\n{\n\"protocol\": \"udp\",\n\"port\": 65531,\n\"endPort\": 65535\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"::/0\",\n\"except\": [\n\"9000::/4\",\n\"4e00::/7\"\n]\n},\n{\n\"cidr\": \"2001:db8::bb/122\"\n},\n{\n\"cidr\": \"2001:DB8::300/122\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"tcp\"\n},\n{\n\"protocol\": \"tcp\"\n},\n{\n\"protocol\": \"udp\",\n\"port\": 442,\n\"endPort\": 447\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"0.0.0.0/0\",\n\"except\": [\n\"88.0.0.0/5\",\n\"0.0.0.0/00\",\n\"107.54.244.201/2\"\n]\n},\n{\n\"cidr\": \"10.0.0.0/22\"\n},\n{\n\"cidr\": \"10.0.0.208/30\"\n}\n],\n\"ports\": [\n{\n\"port\": 65535\n},\n{\n\"protocol\": \"any\",\n\"port\": 65534\n}\n]\n},\n{\n\"to\": [\n{\n\"cidr\": \"2001:db8::3e8/126\"\n},\n{\n\"cidr\": \"e034:404:dd8c:7901:3e3b:dd4:4000:0/104\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"any\",\n\"port\": 5,\n\"endPort\": 10\n},\n{\n\"protocol\": \"tcp\",\n\"port\": 65534,\n\"endPort\": 65534\n},\n{\n\"protocol\": \"udp\",\n\"port\": 79,\n\"endPort\": 80\n}\n]\n}\n],\n\"deny\": []\n}"}],"expected":{"allow":["10.0.0.0/22:tcp:65534-65535","10.0.0.0/22:udp:65534-65535","10.0.0.0/26:icmp","10.0.0.0/26:tcp","10.0.0.0/26:udp:12","10.0.0.0/26:udp:65531-65535","10.0.0.96/27:icmp","10.0.0.96/27:tcp","10.0.0.96/27:udp:12","10.0.0.96/27:udp:65531-65535","10.0.0.128/29:icmp","10.0.0.128/29:tcp","10.0.0.128/29:udp:12","10.0.0.128/29:udp:65531-65535","10.0.0.140/30:icmp","10.0.0.140/30:tcp","10.0.0.140/30:udp:12","10.0.0.140/30:udp:65531-65535","10.0.0.144/28:icmp","10.0.0.144/28:tcp","10.0.0.144/28:udp:12","10.0.0.144/28:udp:65531-65535","10.0.0.160/27:icmp","10.0.0.160/27:tcp","10.0.0.160/27:udp:12","10.0.0.160/27:udp:65531-65535","10.0.0.192/26:icmp","10.0.0.192/26:tcp","10.0.0.192/26:udp:12","10.0.0.192/26:udp:65531-65535","10.0.1.0/24:icmp","10.0.1.0/24:tcp","10.0.1.0/24:udp:12","10.0.1.0/24:udp:65531-65535","10.0.2.0/24:icmp","10.0.2.0/24:tcp","10.0.2.0/24:udp:12","10.0.2.0/24:udp:65531-65535","10.0.3.0/26:icmp","10.0.3.0/26:tcp","10.0.3.0/26:udp:12","10.0.3.0/26:udp:65531-65535","10.0.3.64/30:icmp","10.0.3.64/30:tcp","10.0.3.64/30:udp:12","10.0.3.64/30:udp:65531-65535","10.0.3.72/29:icmp","10.0.3.72/29:tcp","10.0.3.72/29:udp:12","10.0.3.72/29:udp:65531-65535","10.0.3.80/28:icmp","10.0.3.80/28:tcp","10.0.3.80/28:udp:12","10.0.3.80/28:udp:65531-65535","10.0.3.96/27:icmp","10.0.3.96/27:tcp","10.0.3.96/27:udp:12","10.0.3.96/27:udp:65531-65535","10.0.3.128/25:icmp","10.0.3.128/25:tcp","10.0.3.128/25:udp:12","10.0.3.128/25:udp:65531-65535","39.213.0.0/16:icmp","39.213.0.0/16:tcp","39.213.0.0/16:udp:12","39.213.0.0/16:udp:65531-65535","172.162.230.0/24:icmp","172.162.230.0/24:tcp","172.162.230.0/24:udp:12","172.162.230.0/24:udp:65531-65535","::/2:tcp","::/2:udp:442-447","2001:db8::3e8/126:udp:5-10","2001:db8::3e8/126:udp:79-80","22ec:3cf2::/32:icmp","22ec:3cf2::/32:udp:12","22ec:3cf2::/32:udp:65531-65535","4000::/5:tcp","4000::/5:udp:442-447","4800::/6:tcp","4800::/6:udp:442-447","4c00::/7:tcp","4c00::/7:udp:442-447","5000::/4:tcp","5000::/4:udp:442-447","6000::/3:tcp","6000::/3:udp:442-447","8000::/4:tcp","8000::/4:udp:442-447","a000::/3:tcp","a000::/3:udp:442-447","c000::/2:tcp","c000::/2:udp:442-447","e034:404:dd8c:7901:3e3b:dd4:4000:0/104:udp:5-10","e034:404:dd8c:7901:3e3b:dd4:4000:0/104:udp:79-80"],"deny":[]}}, + {"name":"random #194","input":[{"text":"{\n\"allow\": [\n{\n\"to\": [\n{\n\"cidr\": \"18.2.73.194/31\"\n},\n{\n\"cidr\": \"2001:DB8::B4/00126\"\n},\n{\n\"cidr\": \"10.0.2.185/22\",\n\"except\": [\n\"10.0.3.208/28\"\n]\n}\n],\n\"ports\": [\n{\n\"port\": 65531,\n\"endPort\": 65531\n},\n{\n\"protocol\": \"udp\",\n\"port\": 82,\n\"endPort\": 84\n},\n{\n\"protocol\": \"tcp\",\n\"port\": 80,\n\"endPort\": 44049\n}\n]\n},\n{\n\"cidr\": \"2001:db8::2bd/121\",\n\"protocol\": \"tcp\",\n\"port\": 81,\n\"endPort\": 87\n}\n],\n\"deny\": [\n{\n\"cidr\": \"::ffff:84.255.47.0/120\",\n\"protocol\": \"any\",\n\"port\": 5,\n\"endPort\": 9\n},\n{\n\"cidr\": \"2001:db8::302/127\",\n\"protocol\": \"any\",\n\"port\": true\n}\n]\n}"}],"expected":{"error":"deny[1].port must be an integer"}}, + {"name":"random #195","input":[{"text":"{\"allow\":[{\"cidr\":\"10.0.2.216/31\",\"protocol\":\"udp\",\"port\":79,\"endPort\":80,\"to\":[]}]}"}],"expected":{"error":"allow[0] cannot mix MXC to/ports fields with legacy flat fields"}}, + {"name":"random #196","input":[{"text":"{\"allow\": [{\"to\": [{\"cidr\": \"136.192.0.0/0012\"}, {\"cidr\": \"8b0e:92c6:e873:1168:9c33:7673:9c31:ec40/127\", \"except\": []}, {\"cidr\": \"faee:0f51:7481:ae48:15fe:ece0:18.222.77.207/128\"}], \"ports\": [{\"protocol\": \"any\"}]}, {\"cidr\": \"10.0.0.0/22\", \"protocol\": \"tcp\", \"port\": 9999999999999999999999999999999999999999999999999999999999999999}, {\"cidr\": \"2001:db8::116/126\", \"except\": [\"2001:db8::117/127\", \"2001:0db8:0000:0000:0000:0000:0000:0115/000126\"], \"protocol\": \"any\"}], \"deny\": [{\"cidr\": \"10.0.1.192/26\", \"protocol\": \"udp\", \"port\": 65534, \"endPort\": 65535}, {\"to\": [{\"cidr\": \"10.0.0.101/29\", \"except\": [\"10.0.0.96/29\", \"10.0.0.96/30\", \"10.0.0.102/31\"]}], \"ports\": [{\"protocol\": \"udp\", \"port\": 65530, \"endPort\": 65534}, {\"port\": 65534}, {\"protocol\": \"any\", \"port\": 440, \"endPort\": 440}]}, {\"to\": [{\"cidr\": \"10.0.0.0/23\"}, {\"cidr\": \"10.0.0.40/24\"}], \"ports\": [{\"protocol\": \"any\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"udp\", \"port\": 65532}, {\"protocol\": \"tcp\"}]}, {\"to\": [{\"cidr\": \"2001:db8::12e/126\"}, {\"cidr\": \"2001:0db8:0000:0000:0000:0000:0.0.2.192/123\"}], \"ports\": [{\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"any\", \"port\": 78}]}, {\"cidr\": \"10.0.2.66/255.255.255.254\", \"protocol\": \"udp\", \"port\": 78}, {\"to\": [{\"cidr\": \"2001:db8::/121\"}, {\"cidr\": \"2001:db8::2ae/000119\"}, {\"cidr\": \"10.0.2.0/24\"}, {\"cidr\": \"10.0.0.0/23\"}], \"ports\": [{\"port\": 444}, {\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}, {\"protocol\": \"icmp\"}]}, {}, {\"to\": [{\"cidr\": \"2001:DB8::38E/128\", \"except\": [\"2001:db8::38e\", \"2001:db8::38e/128\", \"2001:db8::38e/128\"]}, {\"cidr\": \"10.0.3.201/32\", \"except\": [\"10.0.3.201\", \"10.0.3.201\"]}, {\"cidr\": \"2001:db8::1b9/123\"}, {\"cidr\": \"2001:db8::200/120\"}], \"ports\": [{\"protocol\": \"tcp\"}, {\"protocol\": \"tcp\", \"port\": 65532}, {\"protocol\": \"udp\"}]}]}"}],"expected":{"error":"allow[1].port must be between 1 and 65535"}}, + {"name":"random #197","input":[{"text":"{\n\"allow\": [],\n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"2001:DB8::280/121\"\n},\n{\n\"cidr\": \"26.57.158.174/0000\"\n}\n],\n\"ports\": [\n{\n\"protocol\": \"udp\",\n\"port\": 442\n},\n{\n\"port\": 9,\n\"endPort\": 14\n},\n{\n\"protocol\": \"icmp\"\n}\n]\n},\n{\n\"cidr\": \"108.147.67.94/16\",\n\"except\": [\n\"108.147.151.11/24\",\n\"108.147.192.0/255.255.192.0\"\n],\n\"protocol\": \"icmp\"\n},\n{\n\"to\": [\n{\n\"cidr\": \"10.0.0.164/30\"\n},\n{\n\"cidr\": \"176.32.156.242/32\"\n},\n{\n\"cidr\": \"10.0.3.4/255.255.255.252\",\n\"except\": [\n\"10.0.3.4/31\",\n\"10.0.3.6/32\"\n]\n}\n],\n\"ports\": [\n{\n\"protocol\": \"udp\",\n\"port\": 82,\n\"endPort\": 83\n}\n]\n}\n]\n}"}],"expected":{"allow":[],"deny":["0.0.0.0/0:icmp","0.0.0.0/0:tcp:9-14","0.0.0.0/0:udp:9-14","0.0.0.0/0:udp:442","10.0.0.164/30:udp:82-83","10.0.3.7/32:udp:82-83","176.32.156.242/32:udp:82-83","2001:db8::280/121:icmp","2001:db8::280/121:tcp:9-14","2001:db8::280/121:udp:9-14","2001:db8::280/121:udp:442"]}}, + {"name":"random #198","input":[{"text":"{\n\t\"allow\": [\n\t\t{\n\t\t\t\"cidr\": \"10.0.1.232/29\",\n\t\t\t\"except\": [\n\t\t\t\t\"10.0.1.232/29\",\n\t\t\t\t\"10.0.1.232/30\"\n\t\t\t],\n\t\t\t\"protocol\": \"udp\"\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:DB8::200/121\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.32/27\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:0326/127\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.60/27\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.232/0.0.3.255\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.240/00031\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.240/31\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 80,\n\t\t\t\t\t\"endPort\": 86\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"port\": 442,\n\t\t\t\t\t\"endPort\": 442\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.2.160/28\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.0.129/23\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 3,\n\t\t\t\t\t\"endPort\": 5\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"tcp\",\n\t\t\t\t\t\"port\": 65531,\n\t\t\t\t\t\"endPort\": 65532\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t\t\t\"cidr\": \"10.0.1.0/00024\",\n\t\t\t\t\t\"except\": [\n\t\t\t\t\t\t\"10.0.1.140/30\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"ports\": [\n\t\t\t\t{\n\t\t\t\t\t\"protocol\": \"udp\",\n\t\t\t\t\t\"port\": 7,\n\t\t\t\t\t\"endPort\": 10\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"cidr\": \"0ff3:68ab:9816:4ccc:c343:b540:73.151.255.80/32\",\n\t\t\t\"protocol\": \"tcp\"\n\t\t}\n\t],\n\t\"deny\": [\n\t\t{\n\t\t\t\"cidr\": \"7e7a:e248:9cbd:4ca8:a684:96ce:715f:2ef8/104\"\n\t\t},\n\t\t{\n\t\t\t\"to\": [\n\t\t\t\t{\n\t\t\t"}],"expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"random #199","input":[{"text":"{\n\"allow\": [\n{\n\"cidr\": \"2001:db8::/00118\", \n\"except\": [\n\"2001:DB8::350/125\", \n\"2001:db8::109/125\", \n\"2001:db8::380/121\"\n], \n\"protocol\": \"tcp\"\n}, \n{\n\"to\": [\n{\n\"cidr\": \"0.0.0.0/0002\"\n}, \n{\n\"cidr\": \"2001:db8::fa/127\"\n}, \n{\n\"cidr\": \"10.0.1.222/27\"\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:01c0/122\", \n\"except\": []\n}\n]\n}\n], \n\"deny\": [\n{\n\"to\": [\n{\n\"cidr\": \"10.0.1.0/25\"\n}, \n{\n\"cidr\": \"85.3.238.37/32\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:036c/127\"\n}, \n{\n\"cidr\": \"2001:db8::320/124\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"087a:2cc3:0000:0000:0000:0000:0000:0000/00032\"\n}, \n{\n\"cidr\": \"10.0.0.224/27\"\n}, \n{\n\"cidr\": \"10.0.2.0/23\", \n\"except\": [\n\"10.0.3.41/27\", \n\"10.0.2.0/23\", \n\"10.0.3.42/31\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"any\", \n\"port\": 440\n}\n]\n}, \n{\n\"ports\": [\n{\n\"protocol\": \"tcp\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 9\n}, \n{\n\"protocol\": \"tcp\"\n}, \n{\n\"protocol\": \"tcp\", \n\"port\": 2, \n\"endPort\": 4\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"30d5:8b1b:4b5e:5c9b:35ee:bd04:50a9:c870/1\", \n\"except\": [\n\"4c00::/7\", \n\"2D80::/09\"\n]\n}, \n{\n\"cidr\": \"ec75:6447:0000:0000:0000:0000:0000:0000/32\"\n}\n]\n}, \n{\n\"to\": [\n{\n\"cidr\": \"10.0.3.104/30\"\n}, \n{\n\"cidr\": \"2001:0db8:0000:0000:0000:0000:0000:00a0/126\", \n\"except\": [\n\"2001:0db8:0000:0000:0000:0000:0000:00a0/127\", \n\"2001:db8::a0/128\", \n\"2001:db8::a0/128\"\n]\n}\n], \n\"ports\": [\n{\n\"protocol\": \"any\"\n}, \n{\n\"protocol\": \"icmp\"\n}, \n{\n\"protocol\": \"udp\", \n\"port\": 9\n}, \n{\n\"protocol\": \"icmp\"\n}\n]\n}\n]\n}"}],"expected":{"allow":["0.0.0.0/2","2001:db8::/120:tcp","2001:db8::fa/127","2001:db8::100/125:tcp","2001:db8::110/124:tcp","2001:db8::120/123:tcp","2001:db8::140/122:tcp","2001:db8::180/121:tcp","2001:db8::1c0/122","2001:db8::200/120:tcp","2001:db8::300/122:tcp","2001:db8::340/124:tcp","2001:db8::358/125:tcp","2001:db8::360/123:tcp"],"deny":["0.0.0.0/0:tcp","0.0.0.0/0:udp:9","10.0.0.224/27","10.0.1.0/25","10.0.3.104/30","85.3.238.37/32","::/0:tcp","::/0:udp:9","::/3","2000::/5","2800::/6","2c00::/8","2d00::/9","2e00::/7","3000::/4","4000::/5","4800::/6","4e00::/7","5000::/4","6000::/3","ec75:6447::/32"]}} + ], + "files": [ + {"name":"missing","expected":{"error":"failed to read egress policy file: {path}"}}, + {"name":"directory","expected":{"error":"failed to read egress policy file: {path}"}} + ], + "paths": [ + {"path":"","posix":".","windows":"."}, + {"path":".","posix":".","windows":"."}, + {"path":"./","posix":".","windows":"."}, + {"path":"a","posix":"a","windows":"a"}, + {"path":"a/","posix":"a","windows":"a"}, + {"path":"a//b","posix":"a/b","windows":"a\\b"}, + {"path":"a/./b/.","posix":"a/b","windows":"a\\b"}, + {"path":"./a","posix":"a","windows":"a"}, + {"path":"../a","posix":"../a","windows":"..\\a"}, + {"path":"a/../b","posix":"a/../b","windows":"a\\..\\b"}, + {"path":"/","posix":"/","windows":"\\"}, + {"path":"//","posix":"//","windows":"\\\\"}, + {"path":"///","posix":"/","windows":"\\\\\\"}, + {"path":"////a","posix":"/a","windows":"\\\\\\\\a"}, + {"path":"//a/b","posix":"//a/b","windows":"\\\\a\\b\\"}, + {"path":"///a/b","posix":"/a/b","windows":"\\\\\\a\\b"}, + {"path":"a\\b","posix":"a\\b","windows":"a\\b"}, + {"path":"c:","posix":"c:","windows":"c:"}, + {"path":"c:a","posix":"c:a","windows":"c:a"}, + {"path":"c:/a","posix":"c:/a","windows":"c:\\a"}, + {"path":"c:\\a\\","posix":"c:\\a\\","windows":"c:\\a"}, + {"path":"C:/","posix":"C:","windows":"C:\\"}, + {"path":"/a","posix":"/a","windows":"\\a"}, + {"path":"\\a","posix":"\\a","windows":"\\a"}, + {"path":"\\\\server","posix":"\\\\server","windows":"\\\\server"}, + {"path":"\\\\server\\share","posix":"\\\\server\\share","windows":"\\\\server\\share\\"}, + {"path":"\\\\server\\share\\x","posix":"\\\\server\\share\\x","windows":"\\\\server\\share\\x"}, + {"path":"//server/share/x/","posix":"//server/share/x","windows":"\\\\server\\share\\x"}, + {"path":"\\\\?\\c:\\x","posix":"\\\\?\\c:\\x","windows":"\\\\?\\c:\\x"}, + {"path":"\\\\?\\UNC\\server\\share\\x","posix":"\\\\?\\UNC\\server\\share\\x","windows":"\\\\?\\UNC\\server\\share\\x"}, + {"path":"\\\\?\\unc\\server\\share","posix":"\\\\?\\unc\\server\\share","windows":"\\\\?\\unc\\server\\share\\"}, + {"path":"\\\\.\\device\\x","posix":"\\\\.\\device\\x","windows":"\\\\.\\device\\x"}, + {"path":"\\\\\\share","posix":"\\\\\\share","windows":"\\\\\\share"}, + {"path":"\\\\?.\\x\\y","posix":"\\\\?.\\x\\y","windows":"\\\\?.\\x\\y"}, + {"path":"./c:foo","posix":"c:foo","windows":".\\c:foo"}, + {"path":"c:foo/./bar","posix":"c:foo/bar","windows":"c:foo\\bar"}, + {"path":"1:x","posix":"1:x","windows":"1:x"}, + {"path":"::x","posix":"::x","windows":"::x"}, + {"path":"a:b:c","posix":"a:b:c","windows":"a:b:c"}, + {"path":"\\\\server\\share\\","posix":"\\\\server\\share\\","windows":"\\\\server\\share\\"}, + {"path":"//?/UNC/a","posix":"//?/UNC/a","windows":"\\\\?\\UNC\\a"}, + {"path":"//?/UNC/a/b","posix":"//?/UNC/a/b","windows":"\\\\?\\UNC\\a\\b\\"}, + {"path":"//?/UNC/a/b/c","posix":"//?/UNC/a/b/c","windows":"\\\\?\\UNC\\a\\b\\c"}, + {"path":"\\\\?\\","posix":"\\\\?\\","windows":"\\\\?\\"}, + {"path":"\\\\.\\","posix":"\\\\.\\","windows":"\\\\.\\"}, + {"path":"\\\\?\\x","posix":"\\\\?\\x","windows":"\\\\?\\x"}, + {"path":"a/.","posix":"a","windows":"a"}, + {"path":".\\.\\","posix":".\\.\\","windows":"."}, + {"path":"x\\.\\y","posix":"x\\.\\y","windows":"x\\y"}, + {"path":"\\\\a\\b\\c\\d\\e","posix":"\\\\a\\b\\c\\d\\e","windows":"\\\\a\\b\\c\\d\\e"}, + {"path":" a ","posix":" a ","windows":" a "}, + {"path":"a /b","posix":"a /b","windows":"a \\b"}, + {"path":"\u00e9:x","posix":"\u00e9:x","windows":"\u00e9:x"}, + {"path":"dir/\u00e9.json","posix":"dir/\u00e9.json","windows":"dir\\\u00e9.json"}, + {"path":"policy.json","posix":"policy.json","windows":"policy.json"} + ] +} diff --git a/nvx-rs/tests/fixtures/python.json b/nvx-rs/tests/fixtures/python.json new file mode 100644 index 000000000..cfb4af047 --- /dev/null +++ b/nvx-rs/tests/fixtures/python.json @@ -0,0 +1,2753 @@ +{ +"generator": "scripts/nvx_rs_fixtures.py", +"python": "3.14", +"unicode": "16.0.0", +"space": [[9, 13], [28, 32], [133, 133], [160, 160], [5760, 5760], [8192, 8202], [8232, 8233], [8239, 8239], [8287, 8287], [12288, 12288]], +"decimal_starts": [48, 1632, 1776, 1984, 2406, 2534, 2662, 2790, 2918, 3046, 3174, 3302, 3430, 3558, 3664, 3792, 3872, 4160, 4240, 6112, 6160, 6470, 6608, 6784, 6800, 6992, 7088, 7232, 7248, 42528, 43216, 43264, 43472, 43504, 43600, 44016, 65296, 66720, 68912, 68928, 69734, 69872, 69942, 70096, 70384, 70736, 70864, 71248, 71360, 71376, 71386, 71472, 71904, 72016, 72688, 72784, 73040, 73120, 73552, 90416, 92768, 92864, 93008, 93552, 118000, 120782, 120792, 120802, 120812, 120822, 123200, 123632, 124144, 124401, 125264, 130032], +"non_printable": [[128, 160], [173, 173], [888, 889], [896, 899], [907, 907], [909, 909], [930, 930], [1328, 1328], [1367, 1368], [1419, 1420], [1424, 1424], [1480, 1487], [1515, 1518], [1525, 1541], [1564, 1564], [1757, 1757], [1806, 1807], [1867, 1868], [1970, 1983], [2043, 2044], [2094, 2095], [2111, 2111], [2140, 2141], [2143, 2143], [2155, 2159], [2191, 2198], [2274, 2274], [2436, 2436], [2445, 2446], [2449, 2450], [2473, 2473], [2481, 2481], [2483, 2485], [2490, 2491], [2501, 2502], [2505, 2506], [2511, 2518], [2520, 2523], [2526, 2526], [2532, 2533], [2559, 2560], [2564, 2564], [2571, 2574], [2577, 2578], [2601, 2601], [2609, 2609], [2612, 2612], [2615, 2615], [2618, 2619], [2621, 2621], [2627, 2630], [2633, 2634], [2638, 2640], [2642, 2648], [2653, 2653], [2655, 2661], [2679, 2688], [2692, 2692], [2702, 2702], [2706, 2706], [2729, 2729], [2737, 2737], [2740, 2740], [2746, 2747], [2758, 2758], [2762, 2762], [2766, 2767], [2769, 2783], [2788, 2789], [2802, 2808], [2816, 2816], [2820, 2820], [2829, 2830], [2833, 2834], [2857, 2857], [2865, 2865], [2868, 2868], [2874, 2875], [2885, 2886], [2889, 2890], [2894, 2900], [2904, 2907], [2910, 2910], [2916, 2917], [2936, 2945], [2948, 2948], [2955, 2957], [2961, 2961], [2966, 2968], [2971, 2971], [2973, 2973], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3005], [3011, 3013], [3017, 3017], [3022, 3023], [3025, 3030], [3032, 3045], [3067, 3071], [3085, 3085], [3089, 3089], [3113, 3113], [3130, 3131], [3141, 3141], [3145, 3145], [3150, 3156], [3159, 3159], [3163, 3164], [3166, 3167], [3172, 3173], [3184, 3190], [3213, 3213], [3217, 3217], [3241, 3241], [3252, 3252], [3258, 3259], [3269, 3269], [3273, 3273], [3278, 3284], [3287, 3292], [3295, 3295], [3300, 3301], [3312, 3312], [3316, 3327], [3341, 3341], [3345, 3345], [3397, 3397], [3401, 3401], [3408, 3411], [3428, 3429], [3456, 3456], [3460, 3460], [3479, 3481], [3506, 3506], [3516, 3516], [3518, 3519], [3527, 3529], [3531, 3534], [3541, 3541], [3543, 3543], [3552, 3557], [3568, 3569], [3573, 3584], [3643, 3646], [3676, 3712], [3715, 3715], [3717, 3717], [3723, 3723], [3748, 3748], [3750, 3750], [3774, 3775], [3781, 3781], [3783, 3783], [3791, 3791], [3802, 3803], [3808, 3839], [3912, 3912], [3949, 3952], [3992, 3992], [4029, 4029], [4045, 4045], [4059, 4095], [4294, 4294], [4296, 4300], [4302, 4303], [4681, 4681], [4686, 4687], [4695, 4695], [4697, 4697], [4702, 4703], [4745, 4745], [4750, 4751], [4785, 4785], [4790, 4791], [4799, 4799], [4801, 4801], [4806, 4807], [4823, 4823], [4881, 4881], [4886, 4887], [4955, 4956], [4989, 4991], [5018, 5023], [5110, 5111], [5118, 5119], [5760, 5760], [5789, 5791], [5881, 5887], [5910, 5918], [5943, 5951], [5972, 5983], [5997, 5997], [6001, 6001], [6004, 6015], [6110, 6111], [6122, 6127], [6138, 6143], [6158, 6158], [6170, 6175], [6265, 6271], [6315, 6319], [6390, 6399], [6431, 6431], [6444, 6447], [6460, 6463], [6465, 6467], [6510, 6511], [6517, 6527], [6572, 6575], [6602, 6607], [6619, 6621], [6684, 6685], [6751, 6751], [6781, 6782], [6794, 6799], [6810, 6815], [6830, 6831], [6863, 6911], [6989, 6989], [7156, 7163], [7224, 7226], [7242, 7244], [7307, 7311], [7355, 7356], [7368, 7375], [7419, 7423], [7958, 7959], [7966, 7967], [8006, 8007], [8014, 8015], [8024, 8024], [8026, 8026], [8028, 8028], [8030, 8030], [8062, 8063], [8117, 8117], [8133, 8133], [8148, 8149], [8156, 8156], [8176, 8177], [8181, 8181], [8191, 8207], [8232, 8239], [8287, 8303], [8306, 8307], [8335, 8335], [8349, 8351], [8385, 8399], [8433, 8447], [8588, 8591], [9258, 9279], [9291, 9311], [11124, 11125], [11158, 11158], [11508, 11512], [11558, 11558], [11560, 11564], [11566, 11567], [11624, 11630], [11633, 11646], [11671, 11679], [11687, 11687], [11695, 11695], [11703, 11703], [11711, 11711], [11719, 11719], [11727, 11727], [11735, 11735], [11743, 11743], [11870, 11903], [11930, 11930], [12020, 12031], [12246, 12271], [12288, 12288], [12352, 12352], [12439, 12440], [12544, 12548], [12592, 12592], [12687, 12687], [12774, 12782], [12831, 12831], [42125, 42127], [42183, 42191], [42540, 42559], [42744, 42751], [42958, 42959], [42962, 42962], [42964, 42964], [42973, 42993], [43053, 43055], [43066, 43071], [43128, 43135], [43206, 43213], [43226, 43231], [43348, 43358], [43389, 43391], [43470, 43470], [43482, 43485], [43519, 43519], [43575, 43583], [43598, 43599], [43610, 43611], [43715, 43738], [43767, 43776], [43783, 43784], [43791, 43792], [43799, 43807], [43815, 43815], [43823, 43823], [43884, 43887], [44014, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 63743], [64110, 64111], [64218, 64255], [64263, 64274], [64280, 64284], [64311, 64311], [64317, 64317], [64319, 64319], [64322, 64322], [64325, 64325], [64451, 64466], [64912, 64913], [64968, 64974], [64976, 65007], [65050, 65055], [65107, 65107], [65127, 65127], [65132, 65135], [65141, 65141], [65277, 65280], [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497], [65501, 65503], [65511, 65511], [65519, 65531], [65534, 65535], [65548, 65548], [65575, 65575], [65595, 65595], [65598, 65598], [65614, 65615], [65630, 65663], [65787, 65791], [65795, 65798], [65844, 65846], [65935, 65935], [65949, 65951], [65953, 65999], [66046, 66175], [66205, 66207], [66257, 66271], [66300, 66303], [66340, 66348], [66379, 66383], [66427, 66431], [66462, 66462], [66500, 66503], [66518, 66559], [66718, 66719], [66730, 66735], [66772, 66775], [66812, 66815], [66856, 66863], [66916, 66926], [66939, 66939], [66955, 66955], [66963, 66963], [66966, 66966], [66978, 66978], [66994, 66994], [67002, 67002], [67005, 67007], [67060, 67071], [67383, 67391], [67414, 67423], [67432, 67455], [67462, 67462], [67505, 67505], [67515, 67583], [67590, 67591], [67593, 67593], [67638, 67638], [67641, 67643], [67645, 67646], [67670, 67670], [67743, 67750], [67760, 67807], [67827, 67827], [67830, 67834], [67868, 67870], [67898, 67902], [67904, 67967], [68024, 68027], [68048, 68049], [68100, 68100], [68103, 68107], [68116, 68116], [68120, 68120], [68150, 68151], [68155, 68158], [68169, 68175], [68185, 68191], [68256, 68287], [68327, 68330], [68343, 68351], [68406, 68408], [68438, 68439], [68467, 68471], [68498, 68504], [68509, 68520], [68528, 68607], [68681, 68735], [68787, 68799], [68851, 68857], [68904, 68911], [68922, 68927], [68966, 68968], [68998, 69005], [69008, 69215], [69247, 69247], [69290, 69290], [69294, 69295], [69298, 69313], [69317, 69371], [69416, 69423], [69466, 69487], [69514, 69551], [69580, 69599], [69623, 69631], [69710, 69713], [69750, 69758], [69821, 69821], [69827, 69839], [69865, 69871], [69882, 69887], [69941, 69941], [69960, 69967], [70007, 70015], [70112, 70112], [70133, 70143], [70162, 70162], [70210, 70271], [70279, 70279], [70281, 70281], [70286, 70286], [70302, 70302], [70314, 70319], [70379, 70383], [70394, 70399], [70404, 70404], [70413, 70414], [70417, 70418], [70441, 70441], [70449, 70449], [70452, 70452], [70458, 70458], [70469, 70470], [70473, 70474], [70478, 70479], [70481, 70486], [70488, 70492], [70500, 70501], [70509, 70511], [70517, 70527], [70538, 70538], [70540, 70541], [70543, 70543], [70582, 70582], [70593, 70593], [70595, 70596], [70598, 70598], [70603, 70603], [70614, 70614], [70617, 70624], [70627, 70655], [70748, 70748], [70754, 70783], [70856, 70863], [70874, 71039], [71094, 71095], [71134, 71167], [71237, 71247], [71258, 71263], [71277, 71295], [71354, 71359], [71370, 71375], [71396, 71423], [71451, 71452], [71468, 71471], [71495, 71679], [71740, 71839], [71923, 71934], [71943, 71944], [71946, 71947], [71956, 71956], [71959, 71959], [71990, 71990], [71993, 71994], [72007, 72015], [72026, 72095], [72104, 72105], [72152, 72153], [72165, 72191], [72264, 72271], [72355, 72367], [72441, 72447], [72458, 72639], [72674, 72687], [72698, 72703], [72713, 72713], [72759, 72759], [72774, 72783], [72813, 72815], [72848, 72849], [72872, 72872], [72887, 72959], [72967, 72967], [72970, 72970], [73015, 73017], [73019, 73019], [73022, 73022], [73032, 73039], [73050, 73055], [73062, 73062], [73065, 73065], [73103, 73103], [73106, 73106], [73113, 73119], [73130, 73439], [73465, 73471], [73489, 73489], [73531, 73533], [73563, 73647], [73649, 73663], [73714, 73726], [74650, 74751], [74863, 74863], [74869, 74879], [75076, 77711], [77811, 77823], [78896, 78911], [78934, 78943], [82939, 82943], [83527, 90367], [90426, 92159], [92729, 92735], [92767, 92767], [92778, 92781], [92863, 92863], [92874, 92879], [92910, 92911], [92918, 92927], [92998, 93007], [93018, 93018], [93026, 93026], [93048, 93052], [93072, 93503], [93562, 93759], [93851, 93951], [94027, 94030], [94088, 94094], [94112, 94175], [94181, 94191], [94194, 94207], [100344, 100351], [101590, 101630], [101641, 110575], [110580, 110580], [110588, 110588], [110591, 110591], [110883, 110897], [110899, 110927], [110931, 110932], [110934, 110947], [110952, 110959], [111356, 113663], [113771, 113775], [113789, 113791], [113801, 113807], [113818, 113819], [113824, 117759], [118010, 118015], [118452, 118527], [118574, 118575], [118599, 118607], [118724, 118783], [119030, 119039], [119079, 119080], [119155, 119162], [119275, 119295], [119366, 119487], [119508, 119519], [119540, 119551], [119639, 119647], [119673, 119807], [119893, 119893], [119965, 119965], [119968, 119969], [119971, 119972], [119975, 119976], [119981, 119981], [119994, 119994], [119996, 119996], [120004, 120004], [120070, 120070], [120075, 120076], [120085, 120085], [120093, 120093], [120122, 120122], [120127, 120127], [120133, 120133], [120135, 120137], [120145, 120145], [120486, 120487], [120780, 120781], [121484, 121498], [121504, 121504], [121520, 122623], [122655, 122660], [122667, 122879], [122887, 122887], [122905, 122906], [122914, 122914], [122917, 122917], [122923, 122927], [122990, 123022], [123024, 123135], [123181, 123183], [123198, 123199], [123210, 123213], [123216, 123535], [123567, 123583], [123642, 123646], [123648, 124111], [124154, 124367], [124411, 124414], [124416, 124895], [124903, 124903], [124908, 124908], [124911, 124911], [124927, 124927], [125125, 125126], [125143, 125183], [125260, 125263], [125274, 125277], [125280, 126064], [126133, 126208], [126270, 126463], [126468, 126468], [126496, 126496], [126499, 126499], [126501, 126502], [126504, 126504], [126515, 126515], [126520, 126520], [126522, 126522], [126524, 126529], [126531, 126534], [126536, 126536], [126538, 126538], [126540, 126540], [126544, 126544], [126547, 126547], [126549, 126550], [126552, 126552], [126554, 126554], [126556, 126556], [126558, 126558], [126560, 126560], [126563, 126563], [126565, 126566], [126571, 126571], [126579, 126579], [126584, 126584], [126589, 126589], [126591, 126591], [126602, 126602], [126620, 126624], [126628, 126628], [126634, 126634], [126652, 126703], [126706, 126975], [127020, 127023], [127124, 127135], [127151, 127152], [127168, 127168], [127184, 127184], [127222, 127231], [127406, 127461], [127491, 127503], [127548, 127551], [127561, 127567], [127570, 127583], [127590, 127743], [128728, 128731], [128749, 128751], [128765, 128767], [128887, 128890], [128986, 128991], [129004, 129007], [129009, 129023], [129036, 129039], [129096, 129103], [129114, 129119], [129160, 129167], [129198, 129199], [129212, 129215], [129218, 129279], [129620, 129631], [129646, 129647], [129661, 129663], [129674, 129678], [129735, 129741], [129757, 129758], [129770, 129775], [129785, 129791], [129939, 129939], [130042, 131071], [173792, 173823], [177978, 177983], [178206, 178207], [183970, 183983], [191457, 191471], [192094, 194559], [195102, 196607], [201547, 201551], [205744, 917759], [918000, 1114111]], +"strings": [ +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":" ","repr":"' '","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ' '"}, +{"text":"007","repr":"'007'","split":["007"],"strip":"007","uuid":null,"int":"7"}, +{"text":"+5","repr":"'+5'","split":["+5"],"strip":"+5","uuid":null,"int":"5"}, +{"text":"-5","repr":"'-5'","split":["-5"],"strip":"-5","uuid":null,"int":"-5"}, +{"text":" 5 ","repr":"' 5 '","split":["5"],"strip":"5","uuid":null,"int":"5"}, +{"text":"5_000","repr":"'5_000'","split":["5_000"],"strip":"5_000","uuid":null,"int":"5000"}, +{"text":"_5","repr":"'_5'","split":["_5"],"strip":"_5","uuid":null,"int_error":"invalid literal for int() with base 10: '_5'"}, +{"text":"5_","repr":"'5_'","split":["5_"],"strip":"5_","uuid":null,"int_error":"invalid literal for int() with base 10: '5_'"}, +{"text":"5__0","repr":"'5__0'","split":["5__0"],"strip":"5__0","uuid":null,"int_error":"invalid literal for int() with base 10: '5__0'"}, +{"text":"0x5","repr":"'0x5'","split":["0x5"],"strip":"0x5","uuid":null,"int_error":"invalid literal for int() with base 10: '0x5'"}, +{"text":"1e3","repr":"'1e3'","split":["1e3"],"strip":"1e3","uuid":null,"int_error":"invalid literal for int() with base 10: '1e3'"}, +{"text":"\u20035\u3000","repr":"'\\u20035\\u3000'","split":["5"],"strip":"5","uuid":null,"int":"5"}, +{"text":"\uff15","repr":"'\uff15'","split":["\uff15"],"strip":"\uff15","uuid":null,"int":"5"}, +{"text":"\u0664\u0662","repr":"'\u0664\u0662'","split":["\u0664\u0662"],"strip":"\u0664\u0662","uuid":null,"int":"42"}, +{"text":"\u001c42\u001f","repr":"'\\x1c42\\x1f'","split":["42"],"strip":"42","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1c42\\x1f'"}, +{"text":"1\u00002","repr":"'1\\x002'","split":["1\u00002"],"strip":"1\u00002","uuid":null,"int_error":"invalid literal for int() with base 10: '1\\x002'"}, +{"text":"it's","repr":"\"it's\"","split":["it's"],"strip":"it's","uuid":null,"int_error":"invalid literal for int() with base 10: \"it's\""}, +{"text":"say \"hi\"","repr":"'say \"hi\"'","split":["say","\"hi\""],"strip":"say \"hi\"","uuid":null,"int_error":"invalid literal for int() with base 10: 'say \"hi\"'"}, +{"text":"both ' and \"","repr":"'both \\' and \"'","split":["both","'","and","\""],"strip":"both ' and \"","uuid":null,"int_error":"invalid literal for int() with base 10: 'both \\' and \"'"}, +{"text":"a\\b","repr":"'a\\\\b'","split":["a\\b"],"strip":"a\\b","uuid":null,"int_error":"invalid literal for int() with base 10: 'a\\\\b'"}, +{"text":"\t\n\r\u0000\u007f\u001b","repr":"'\\t\\n\\r\\x00\\x7f\\x1b'","split":["\u0000\u007f\u001b"],"strip":"\u0000\u007f\u001b","uuid":null,"int_error":"invalid literal for int() with base 10: '\\t\\n\\r\\x00\\x7f\\x1b'"}, +{"text":"\u00e9\u00a0\u00ad","repr":"'\u00e9\\xa0\\xad'","split":["\u00e9","\u00ad"],"strip":"\u00e9\u00a0\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '\u00e9\\xa0\\xad'"}, +{"text":"\u2028\u200b\u0378","repr":"'\\u2028\\u200b\\u0378'","split":["\u200b\u0378"],"strip":"\u200b\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2028\\u200b\\u0378'"}, +{"text":"\ud83d\ude00\udb40\udc01","repr":"'\ud83d\ude00\\U000e0001'","split":["\ud83d\ude00\udb40\udc01"],"strip":"\ud83d\ude00\udb40\udc01","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud83d\ude00\\U000e0001'"}, +{"text":"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111","repr":"'1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'","split":["1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"],"strip":"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111","uuid":null,"int":"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"}, +{"text":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001","repr":"'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001'","split":["00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"],"strip":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001","uuid":null,"int_error":"Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit"}, +{"text":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","repr":"'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'","split":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"strip":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","uuid":null,"int_error":"invalid literal for int() with base 10: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"uuuuid:id:12345678123456781234567812345678","repr":"'uuuuid:id:12345678123456781234567812345678'","split":["uuuuid:id:12345678123456781234567812345678"],"strip":"uuuuid:id:12345678123456781234567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'uuuuid:id:12345678123456781234567812345678'"}, +{"text":"{urn:uuid:12345678-1234-5678-1234-567812345678}","repr":"'{urn:uuid:12345678-1234-5678-1234-567812345678}'","split":["{urn:uuid:12345678-1234-5678-1234-567812345678}"],"strip":"{urn:uuid:12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{urn:uuid:12345678-1234-5678-1234-567812345678}'"}, +{"text":"0x111111111111111111111111111111","repr":"'0x111111111111111111111111111111'","split":["0x111111111111111111111111111111"],"strip":"0x111111111111111111111111111111","uuid":"00111111-1111-1111-1111-111111111111","int_error":"invalid literal for int() with base 10: '0x111111111111111111111111111111'"}, +{"text":"+1111111111111111111111111111111","repr":"'+1111111111111111111111111111111'","split":["+1111111111111111111111111111111"],"strip":"+1111111111111111111111111111111","uuid":"01111111-1111-1111-1111-111111111111","int":"1111111111111111111111111111111"}, +{"text":"-1111111111111111111111111111111","repr":"'-1111111111111111111111111111111'","split":["-1111111111111111111111111111111"],"strip":"-1111111111111111111111111111111","uuid":null,"int":"-1111111111111111111111111111111"}, +{"text":" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","repr":"' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'","split":["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],"strip":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","uuid":"0aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","int_error":"invalid literal for int() with base 10: ' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'"}, +{"text":"1_111111111111111111111111111111","repr":"'1_111111111111111111111111111111'","split":["1_111111111111111111111111111111"],"strip":"1_111111111111111111111111111111","uuid":"01111111-1111-1111-1111-111111111111","int":"1111111111111111111111111111111"}, +{"text":"\u06610000000000000000000000000000000","repr":"'\u06610000000000000000000000000000000'","split":["\u06610000000000000000000000000000000"],"strip":"\u06610000000000000000000000000000000","uuid":"10000000-0000-0000-0000-000000000000","int":"10000000000000000000000000000000"}, +{"text":"0b111111111111111111111111111111","repr":"'0b111111111111111111111111111111'","split":["0b111111111111111111111111111111"],"strip":"0b111111111111111111111111111111","uuid":"0b111111-1111-1111-1111-111111111111","int_error":"invalid literal for int() with base 10: '0b111111111111111111111111111111'"}, +{"text":"\ufeff","repr":"'\\ufeff'","split":["\ufeff"],"strip":"\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff'"}, +{"text":"}\udb40\udc01{\u00a0\u007f1","repr":"'}\\U000e0001{\\xa0\\x7f1'","split":["}\udb40\udc01{","\u007f1"],"strip":"}\udb40\udc01{\u00a0\u007f1","uuid":null,"int_error":"invalid literal for int() with base 10: '}\\U000e0001{\\xa0\\x7f1'"}, +{"text":"Fuuid:x\t7=0x9","repr":"'Fuuid:x\\t7=0x9'","split":["Fuuid:x","7=0x9"],"strip":"Fuuid:x\t7=0x9","uuid":null,"int_error":"invalid literal for int() with base 10: 'Fuuid:x\\t7=0x9'"}, +{"text":"\u2028+9}\"urn:\n\ud835\udfce\u001c\t\\","repr":"'\\u2028+9}\"urn:\\n\ud835\udfce\\x1c\\t\\\\'","split":["+9}\"urn:","\ud835\udfce","\\"],"strip":"+9}\"urn:\n\ud835\udfce\u001c\t\\","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2028+9}\"urn:\\n\ud835\udfce\\x1c\\t\\\\'"}, +{"text":",uuid:\u00ad\uff11uuid:F\ud835\udfce0'\n\u30000x","repr":"\",uuid:\\xad\uff11uuid:F\ud835\udfce0'\\n\\u30000x\"","split":[",uuid:\u00ad\uff11uuid:F\ud835\udfce0'","0x"],"strip":",uuid:\u00ad\uff11uuid:F\ud835\udfce0'\n\u30000x","uuid":null,"int_error":"invalid literal for int() with base 10: \",uuid:\\xad\uff11uuid:F\ud835\udfce0'\\n\\u30000x\""}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"urn:0uuid:{x","repr":"'urn:0uuid:{x'","split":["urn:0uuid:{x"],"strip":"urn:0uuid:{x","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:0uuid:{x'"}, +{"text":"_\u03780 ","repr":"'_\\u03780 '","split":["_\u03780"],"strip":"_\u03780","uuid":null,"int_error":"invalid literal for int() with base 10: '_\\u03780 '"}, +{"text":"\u0085\u00e90x\u00ad\u00a0,=","repr":"'\\x85\u00e90x\\xad\\xa0,='","split":["\u00e90x\u00ad",",="],"strip":"\u00e90x\u00ad\u00a0,=","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85\u00e90x\\xad\\xa0,='"}, +{"text":"1\ud835\udfce\udb40\udc01\u0663\u00a0","repr":"'1\ud835\udfce\\U000e0001\u0663\\xa0'","split":["1\ud835\udfce\udb40\udc01\u0663"],"strip":"1\ud835\udfce\udb40\udc01\u0663","uuid":null,"int_error":"invalid literal for int() with base 10: '1\ud835\udfce\\U000e0001\u0663\\xa0'"}, +{"text":"\ufeff0x\nX\u200b","repr":"'\\ufeff0x\\nX\\u200b'","split":["\ufeff0x","X\u200b"],"strip":"\ufeff0x\nX\u200b","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff0x\\nX\\u200b'"}, +{"text":"\u3000\u2028\\\\\ufeff","repr":"'\\u3000\\u2028\\\\\\\\\\ufeff'","split":["\\\\\ufeff"],"strip":"\\\\\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\u2028\\\\\\\\\\ufeff'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"'\u00007-x0xF\udb40\udc01\uff11","repr":"\"'\\x007-x0xF\\U000e0001\uff11\"","split":["'\u00007-x0xF\udb40\udc01\uff11"],"strip":"'\u00007-x0xF\udb40\udc01\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: \"'\\x007-x0xF\\U000e0001\uff11\""}, +{"text":"a=\ud835\udfce\u000b\u00e9urn:9\ufeff}\u0378","repr":"'a=\ud835\udfce\\x0b\u00e9urn:9\\ufeff}\\u0378'","split":["a=\ud835\udfce","\u00e9urn:9\ufeff}\u0378"],"strip":"a=\ud835\udfce\u000b\u00e9urn:9\ufeff}\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: 'a=\ud835\udfce\\x0b\u00e9urn:9\\ufeff}\\u0378'"}, +{"text":"\u0085\u0663-1\uff11\udb40\udc01\u000b1\\X","repr":"'\\x85\u0663-1\uff11\\U000e0001\\x0b1\\\\X'","split":["\u0663-1\uff11\udb40\udc01","1\\X"],"strip":"\u0663-1\uff11\udb40\udc01\u000b1\\X","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85\u0663-1\uff11\\U000e0001\\x0b1\\\\X'"}, +{"text":"\"{\t\u00e9\u001c\u00e9","repr":"'\"{\\t\u00e9\\x1c\u00e9'","split":["\"{","\u00e9","\u00e9"],"strip":"\"{\t\u00e9\u001c\u00e9","uuid":null,"int_error":"invalid literal for int() with base 10: '\"{\\t\u00e9\\x1c\u00e9'"}, +{"text":"\u00e9}\u007f\u00a0\ud83d\ude00 \t\u000b","repr":"'\u00e9}\\x7f\\xa0\ud83d\ude00 \\t\\x0b'","split":["\u00e9}\u007f","\ud83d\ude00"],"strip":"\u00e9}\u007f\u00a0\ud83d\ude00","uuid":null,"int_error":"invalid literal for int() with base 10: '\u00e9}\\x7f\\xa0\ud83d\ude00 \\t\\x0b'"}, +{"text":"\uff11\u0085\u2003urn:x","repr":"'\uff11\\x85\\u2003urn:x'","split":["\uff11","urn:x"],"strip":"\uff11\u0085\u2003urn:x","uuid":null,"int_error":"invalid literal for int() with base 10: '\uff11\\x85\\u2003urn:x'"}, +{"text":"\u001c\ud83d\ude00=\u2028}0X\u200burn:a\u00e91","repr":"'\\x1c\ud83d\ude00=\\u2028}0X\\u200burn:a\u00e91'","split":["\ud83d\ude00=","}0X\u200burn:a\u00e91"],"strip":"\ud83d\ude00=\u2028}0X\u200burn:a\u00e91","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1c\ud83d\ude00=\\u2028}0X\\u200burn:a\u00e91'"}, +{"text":"uuid:-0x\u0378","repr":"'uuid:-0x\\u0378'","split":["uuid:-0x\u0378"],"strip":"uuid:-0x\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: 'uuid:-0x\\u0378'"}, +{"text":"F'9\u000b0\u0378+\u001c\u007f-","repr":"\"F'9\\x0b0\\u0378+\\x1c\\x7f-\"","split":["F'9","0\u0378+","\u007f-"],"strip":"F'9\u000b0\u0378+\u001c\u007f-","uuid":null,"int_error":"invalid literal for int() with base 10: \"F'9\\x0b0\\u0378+\\x1c\\x7f-\""}, +{"text":"F-\u2003\ud835\udfce\u0378:}Xa","repr":"'F-\\u2003\ud835\udfce\\u0378:}Xa'","split":["F-","\ud835\udfce\u0378:}Xa"],"strip":"F-\u2003\ud835\udfce\u0378:}Xa","uuid":null,"int_error":"invalid literal for int() with base 10: 'F-\\u2003\ud835\udfce\\u0378:}Xa'"}, +{"text":"{\u2028\ud835\udfce7=","repr":"'{\\u2028\ud835\udfce7='","split":["{","\ud835\udfce7="],"strip":"{\u2028\ud835\udfce7=","uuid":null,"int_error":"invalid literal for int() with base 10: '{\\u2028\ud835\udfce7='"}, +{"text":"{\ud835\udfce\ud83d\ude00uuid:x\u2028","repr":"'{\ud835\udfce\ud83d\ude00uuid:x\\u2028'","split":["{\ud835\udfce\ud83d\ude00uuid:x"],"strip":"{\ud835\udfce\ud83d\ude00uuid:x","uuid":null,"int_error":"invalid literal for int() with base 10: '{\ud835\udfce\ud83d\ude00uuid:x\\u2028'"}, +{"text":"7X\u00ad\u001f9\ufeff\u000b\ufeff","repr":"'7X\\xad\\x1f9\\ufeff\\x0b\\ufeff'","split":["7X\u00ad","9\ufeff","\ufeff"],"strip":"7X\u00ad\u001f9\ufeff\u000b\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '7X\\xad\\x1f9\\ufeff\\x0b\\ufeff'"}, +{"text":"\n\u2003=X","repr":"'\\n\\u2003=X'","split":["=X"],"strip":"=X","uuid":null,"int_error":"invalid literal for int() with base 10: '\\n\\u2003=X'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"\u007f\n\u200b\u007f\u2003a_\u0085\u0663\u0663a","repr":"'\\x7f\\n\\u200b\\x7f\\u2003a_\\x85\u0663\u0663a'","split":["\u007f","\u200b\u007f","a_","\u0663\u0663a"],"strip":"\u007f\n\u200b\u007f\u2003a_\u0085\u0663\u0663a","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f\\n\\u200b\\x7f\\u2003a_\\x85\u0663\u0663a'"}, +{"text":"\u3000\u000bF","repr":"'\\u3000\\x0bF'","split":["F"],"strip":"F","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\x0bF'"}, +{"text":"}=","repr":"'}='","split":["}="],"strip":"}=","uuid":null,"int_error":"invalid literal for int() with base 10: '}='"}, +{"text":"a\udb40\udc01uuid:\u0085 \ud83d\ude00\\{","repr":"'a\\U000e0001uuid:\\x85 \ud83d\ude00\\\\{'","split":["a\udb40\udc01uuid:","\ud83d\ude00\\{"],"strip":"a\udb40\udc01uuid:\u0085 \ud83d\ude00\\{","uuid":null,"int_error":"invalid literal for int() with base 10: 'a\\U000e0001uuid:\\x85 \ud83d\ude00\\\\{'"}, +{"text":"\\}=","repr":"'\\\\}='","split":["\\}="],"strip":"\\}=","uuid":null,"int_error":"invalid literal for int() with base 10: '\\\\}='"}, +{"text":"\u0378:0x,\u00a0\u3000=","repr":"'\\u0378:0x,\\xa0\\u3000='","split":["\u0378:0x,","="],"strip":"\u0378:0x,\u00a0\u3000=","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u0378:0x,\\xa0\\u3000='"}, +{"text":",x:\u0000\udb40\udc019'}\u20289","repr":"\",x:\\x00\\U000e00019'}\\u20289\"","split":[",x:\u0000\udb40\udc019'}","9"],"strip":",x:\u0000\udb40\udc019'}\u20289","uuid":null,"int_error":"invalid literal for int() with base 10: \",x:\\x00\\U000e00019'}\\u20289\""}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"x\n\u001f","repr":"'x\\n\\x1f'","split":["x"],"strip":"x","uuid":null,"int_error":"invalid literal for int() with base 10: 'x\\n\\x1f'"}, +{"text":":\u200b=\u2003}\u3000X:\n\u0085","repr":"':\\u200b=\\u2003}\\u3000X:\\n\\x85'","split":[":\u200b=","}","X:"],"strip":":\u200b=\u2003}\u3000X:","uuid":null,"int_error":"invalid literal for int() with base 10: ':\\u200b=\\u2003}\\u3000X:\\n\\x85'"}, +{"text":"-0x\ufeff\u2028","repr":"'-0x\\ufeff\\u2028'","split":["-0x\ufeff"],"strip":"-0x\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '-0x\\ufeff\\u2028'"}, +{"text":"\ud835\udfce\u2028}\t","repr":"'\ud835\udfce\\u2028}\\t'","split":["\ud835\udfce","}"],"strip":"\ud835\udfce\u2028}","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud835\udfce\\u2028}\\t'"}, +{"text":"\ud83d\ude00,\ud83d\ude00 a,\"7\\9+\u0378","repr":"'\ud83d\ude00,\ud83d\ude00 a,\"7\\\\9+\\u0378'","split":["\ud83d\ude00,\ud83d\ude00","a,\"7\\9+\u0378"],"strip":"\ud83d\ude00,\ud83d\ude00 a,\"7\\9+\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud83d\ude00,\ud83d\ude00 a,\"7\\\\9+\\u0378'"}, +{"text":"9\uff11\u200b","repr":"'9\uff11\\u200b'","split":["9\uff11\u200b"],"strip":"9\uff11\u200b","uuid":null,"int_error":"invalid literal for int() with base 10: '9\uff11\\u200b'"}, +{"text":",","repr":"','","split":[","],"strip":",","uuid":null,"int_error":"invalid literal for int() with base 10: ','"}, +{"text":"0\u00a0F{\u2028uuid:\u0663\\\u00ad","repr":"'0\\xa0F{\\u2028uuid:\u0663\\\\\\xad'","split":["0","F{","uuid:\u0663\\\u00ad"],"strip":"0\u00a0F{\u2028uuid:\u0663\\\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '0\\xa0F{\\u2028uuid:\u0663\\\\\\xad'"}, +{"text":"\u001c\u00a0{'\ud83d\ude00","repr":"\"\\x1c\\xa0{'\ud83d\ude00\"","split":["{'\ud83d\ude00"],"strip":"{'\ud83d\ude00","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\x1c\\xa0{'\ud83d\ude00\""}, +{"text":"\u001c\u3000=\"X\u3000\u0378\t\n}\udb40\udc01","repr":"'\\x1c\\u3000=\"X\\u3000\\u0378\\t\\n}\\U000e0001'","split":["=\"X","\u0378","}\udb40\udc01"],"strip":"=\"X\u3000\u0378\t\n}\udb40\udc01","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1c\\u3000=\"X\\u3000\\u0378\\t\\n}\\U000e0001'"}, +{"text":"\u0085,\u0085 \u007f\ud83d\ude00\u2003Xuuid:',","repr":"\"\\x85,\\x85 \\x7f\ud83d\ude00\\u2003Xuuid:',\"","split":[",","\u007f\ud83d\ude00","Xuuid:',"],"strip":",\u0085 \u007f\ud83d\ude00\u2003Xuuid:',","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\x85,\\x85 \\x7f\ud83d\ude00\\u2003Xuuid:',\""}, +{"text":"urn:\u200burn:uuid:\u00ad}'-","repr":"\"urn:\\u200burn:uuid:\\xad}'-\"","split":["urn:\u200burn:uuid:\u00ad}'-"],"strip":"urn:\u200burn:uuid:\u00ad}'-","uuid":null,"int_error":"invalid literal for int() with base 10: \"urn:\\u200burn:uuid:\\xad}'-\""}, +{"text":"{F\u007f\uff11\u0378","repr":"'{F\\x7f\uff11\\u0378'","split":["{F\u007f\uff11\u0378"],"strip":"{F\u007f\uff11\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: '{F\\x7f\uff11\\u0378'"}, +{"text":"\nF\n\u200b\u001f\u0000:\n\u0085\u20280x\u001c","repr":"'\\nF\\n\\u200b\\x1f\\x00:\\n\\x85\\u20280x\\x1c'","split":["F","\u200b","\u0000:","0x"],"strip":"F\n\u200b\u001f\u0000:\n\u0085\u20280x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\nF\\n\\u200b\\x1f\\x00:\\n\\x85\\u20280x\\x1c'"}, +{"text":"xa\u0663\u2028-{\u0663\u000b90x+","repr":"'xa\u0663\\u2028-{\u0663\\x0b90x+'","split":["xa\u0663","-{\u0663","90x+"],"strip":"xa\u0663\u2028-{\u0663\u000b90x+","uuid":null,"int_error":"invalid literal for int() with base 10: 'xa\u0663\\u2028-{\u0663\\x0b90x+'"}, +{"text":"\u0085\u00a0urn:{=\udb40\udc01\u2028\u200b\uff11\udb40\udc01uuid:","repr":"'\\x85\\xa0urn:{=\\U000e0001\\u2028\\u200b\uff11\\U000e0001uuid:'","split":["urn:{=\udb40\udc01","\u200b\uff11\udb40\udc01uuid:"],"strip":"urn:{=\udb40\udc01\u2028\u200b\uff11\udb40\udc01uuid:","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85\\xa0urn:{=\\U000e0001\\u2028\\u200b\uff11\\U000e0001uuid:'"}, +{"text":"\turn:_',Xurn:\u0378\ud835\udfce\u0085x","repr":"\"\\turn:_',Xurn:\\u0378\ud835\udfce\\x85x\"","split":["urn:_',Xurn:\u0378\ud835\udfce","x"],"strip":"urn:_',Xurn:\u0378\ud835\udfce\u0085x","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\turn:_',Xurn:\\u0378\ud835\udfce\\x85x\""}, +{"text":"\ta\"1\n\u0663\uff11\u00adF0x","repr":"'\\ta\"1\\n\u0663\uff11\\xadF0x'","split":["a\"1","\u0663\uff11\u00adF0x"],"strip":"a\"1\n\u0663\uff11\u00adF0x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ta\"1\\n\u0663\uff11\\xadF0x'"}, +{"text":"\u0000uuid:\ud83d\ude00=\u2003\u000b\ufeff\u3000x \t\u001c","repr":"'\\x00uuid:\ud83d\ude00=\\u2003\\x0b\\ufeff\\u3000x \\t\\x1c'","split":["\u0000uuid:\ud83d\ude00=","\ufeff","x"],"strip":"\u0000uuid:\ud83d\ude00=\u2003\u000b\ufeff\u3000x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x00uuid:\ud83d\ude00=\\u2003\\x0b\\ufeff\\u3000x \\t\\x1c'"}, +{"text":"\u00000x","repr":"'\\x000x'","split":["\u00000x"],"strip":"\u00000x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x000x'"}, +{"text":" ","repr":"' '","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ' '"}, +{"text":"\t1x9\u00e9\udb40\udc01\n0\u0663","repr":"'\\t1x9\u00e9\\U000e0001\\n0\u0663'","split":["1x9\u00e9\udb40\udc01","0\u0663"],"strip":"1x9\u00e9\udb40\udc01\n0\u0663","uuid":null,"int_error":"invalid literal for int() with base 10: '\\t1x9\u00e9\\U000e0001\\n0\u0663'"}, +{"text":",uuid:{\ud83d\ude00\u007f\n","repr":"',uuid:{\ud83d\ude00\\x7f\\n'","split":[",uuid:{\ud83d\ude00\u007f"],"strip":",uuid:{\ud83d\ude00\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: ',uuid:{\ud83d\ude00\\x7f\\n'"}, +{"text":"\ud835\udfcea -\u001c_","repr":"'\ud835\udfcea -\\x1c_'","split":["\ud835\udfcea","-","_"],"strip":"\ud835\udfcea -\u001c_","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud835\udfcea -\\x1c_'"}, +{"text":"\u0378urn:0x","repr":"'\\u0378urn:0x'","split":["\u0378urn:0x"],"strip":"\u0378urn:0x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u0378urn:0x'"}, +{"text":"\"9F\u000b\ud835\udfce'\u00a0{=urn:\u00e9","repr":"'\"9F\\x0b\ud835\udfce\\'\\xa0{=urn:\u00e9'","split":["\"9F","\ud835\udfce'","{=urn:\u00e9"],"strip":"\"9F\u000b\ud835\udfce'\u00a0{=urn:\u00e9","uuid":null,"int_error":"invalid literal for int() with base 10: '\"9F\\x0b\ud835\udfce\\'\\xa0{=urn:\u00e9'"}, +{"text":" \uff11:\\","repr":"' \uff11:\\\\'","split":["\uff11:\\"],"strip":"\uff11:\\","uuid":null,"int_error":"invalid literal for int() with base 10: ' \uff11:\\\\'"}, +{"text":"'-x \u0000\ufeff\uff11","repr":"\"'-x \\x00\\ufeff\uff11\"","split":["'-x","\u0000\ufeff\uff11"],"strip":"'-x \u0000\ufeff\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: \"'-x \\x00\\ufeff\uff11\""}, +{"text":"\u001f70x\ud83d\ude00","repr":"'\\x1f70x\ud83d\ude00'","split":["70x\ud83d\ude00"],"strip":"70x\ud83d\ude00","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f70x\ud83d\ude00'"}, +{"text":"\u00ad\u00a00x\udb40\udc01\u2028\u007f\u001c\u000b,","repr":"'\\xad\\xa00x\\U000e0001\\u2028\\x7f\\x1c\\x0b,'","split":["\u00ad","0x\udb40\udc01","\u007f",","],"strip":"\u00ad\u00a00x\udb40\udc01\u2028\u007f\u001c\u000b,","uuid":null,"int_error":"invalid literal for int() with base 10: '\\xad\\xa00x\\U000e0001\\u2028\\x7f\\x1c\\x0b,'"}, +{"text":"\u000b7\u000b-,F\u00a07\u001c\u00e99","repr":"'\\x0b7\\x0b-,F\\xa07\\x1c\u00e99'","split":["7","-,F","7","\u00e99"],"strip":"7\u000b-,F\u00a07\u001c\u00e99","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x0b7\\x0b-,F\\xa07\\x1c\u00e99'"}, +{"text":"x0,_9:=urn:urn::\ud83d\ude00\ud83d\ude00","repr":"'x0,_9:=urn:urn::\ud83d\ude00\ud83d\ude00'","split":["x0,_9:=urn:urn::\ud83d\ude00\ud83d\ude00"],"strip":"x0,_9:=urn:urn::\ud83d\ude00\ud83d\ude00","uuid":null,"int_error":"invalid literal for int() with base 10: 'x0,_9:=urn:urn::\ud83d\ude00\ud83d\ude00'"}, +{"text":"\u200b{\udb40\udc01\udb40\udc01X1}","repr":"'\\u200b{\\U000e0001\\U000e0001X1}'","split":["\u200b{\udb40\udc01\udb40\udc01X1}"],"strip":"\u200b{\udb40\udc01\udb40\udc01X1}","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u200b{\\U000e0001\\U000e0001X1}'"}, +{"text":"\u00a0\u001c","repr":"'\\xa0\\x1c'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\xa0\\x1c'"}, +{"text":"\u2003\u0085\u3000\u0663\ud835\udfce\u0085_\n\u0378a\\0","repr":"'\\u2003\\x85\\u3000\u0663\ud835\udfce\\x85_\\n\\u0378a\\\\0'","split":["\u0663\ud835\udfce","_","\u0378a\\0"],"strip":"\u0663\ud835\udfce\u0085_\n\u0378a\\0","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2003\\x85\\u3000\u0663\ud835\udfce\\x85_\\n\\u0378a\\\\0'"}, +{"text":"\ud835\udfce\uff11urn:","repr":"'\ud835\udfce\uff11urn:'","split":["\ud835\udfce\uff11urn:"],"strip":"\ud835\udfce\uff11urn:","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud835\udfce\uff11urn:'"}, +{"text":"F_XXuuid:\u200b","repr":"'F_XXuuid:\\u200b'","split":["F_XXuuid:\u200b"],"strip":"F_XXuuid:\u200b","uuid":null,"int_error":"invalid literal for int() with base 10: 'F_XXuuid:\\u200b'"}, +{"text":"\u007f\u00009\u007f\u00ada0\u000b\uff11","repr":"'\\x7f\\x009\\x7f\\xada0\\x0b\uff11'","split":["\u007f\u00009\u007f\u00ada0","\uff11"],"strip":"\u007f\u00009\u007f\u00ada0\u000b\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f\\x009\\x7f\\xada0\\x0b\uff11'"}, +{"text":"\u001f\u007f\u0378:F\ud835\udfce\\uuid:\u200b\u00a0\"\u000b","repr":"'\\x1f\\x7f\\u0378:F\ud835\udfce\\\\uuid:\\u200b\\xa0\"\\x0b'","split":["\u007f\u0378:F\ud835\udfce\\uuid:\u200b","\""],"strip":"\u007f\u0378:F\ud835\udfce\\uuid:\u200b\u00a0\"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f\\x7f\\u0378:F\ud835\udfce\\\\uuid:\\u200b\\xa0\"\\x0b'"}, +{"text":":\ud83d\ude00\u007f\u007fX0 }0\u0000\u200bX","repr":"':\ud83d\ude00\\x7f\\x7fX0 }0\\x00\\u200bX'","split":[":\ud83d\ude00\u007f\u007fX0","}0\u0000\u200bX"],"strip":":\ud83d\ude00\u007f\u007fX0 }0\u0000\u200bX","uuid":null,"int_error":"invalid literal for int() with base 10: ':\ud83d\ude00\\x7f\\x7fX0 }0\\x00\\u200bX'"}, +{"text":"\ufeff\u0000x\u0000\ud835\udfce\u2003{","repr":"'\\ufeff\\x00x\\x00\ud835\udfce\\u2003{'","split":["\ufeff\u0000x\u0000\ud835\udfce","{"],"strip":"\ufeff\u0000x\u0000\ud835\udfce\u2003{","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff\\x00x\\x00\ud835\udfce\\u2003{'"}, +{"text":"\u00ad\u00e9\u3000x\u00ad","repr":"'\\xad\u00e9\\u3000x\\xad'","split":["\u00ad\u00e9","x\u00ad"],"strip":"\u00ad\u00e9\u3000x\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '\\xad\u00e9\\u3000x\\xad'"}, +{"text":"\\\u3000\u0378\u0663\n\u00aduuid:,","repr":"'\\\\\\u3000\\u0378\u0663\\n\\xaduuid:,'","split":["\\","\u0378\u0663","\u00aduuid:,"],"strip":"\\\u3000\u0378\u0663\n\u00aduuid:,","uuid":null,"int_error":"invalid literal for int() with base 10: '\\\\\\u3000\\u0378\u0663\\n\\xaduuid:,'"}, +{"text":"1}\u00e9\u2003\ud83d\ude00\u00a0\"_\u00ad=9","repr":"'1}\u00e9\\u2003\ud83d\ude00\\xa0\"_\\xad=9'","split":["1}\u00e9","\ud83d\ude00","\"_\u00ad=9"],"strip":"1}\u00e9\u2003\ud83d\ude00\u00a0\"_\u00ad=9","uuid":null,"int_error":"invalid literal for int() with base 10: '1}\u00e9\\u2003\ud83d\ude00\\xa0\"_\\xad=9'"}, +{"text":"7\u001fa\u001c0}","repr":"'7\\x1fa\\x1c0}'","split":["7","a","0}"],"strip":"7\u001fa\u001c0}","uuid":null,"int_error":"invalid literal for int() with base 10: '7\\x1fa\\x1c0}'"}, +{"text":"}\u0378","repr":"'}\\u0378'","split":["}\u0378"],"strip":"}\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: '}\\u0378'"}, +{"text":"urn:\u00001\u00ad\u001c \u3000+","repr":"'urn:\\x001\\xad\\x1c \\u3000+'","split":["urn:\u00001\u00ad","+"],"strip":"urn:\u00001\u00ad\u001c \u3000+","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:\\x001\\xad\\x1c \\u3000+'"}, +{"text":"\u2028\u0085\u000bx9\u001c\u001f","repr":"'\\u2028\\x85\\x0bx9\\x1c\\x1f'","split":["x9"],"strip":"x9","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2028\\x85\\x0bx9\\x1c\\x1f'"}, +{"text":"7\"\u2028}\u3000","repr":"'7\"\\u2028}\\u3000'","split":["7\"","}"],"strip":"7\"\u2028}","uuid":null,"int_error":"invalid literal for int() with base 10: '7\"\\u2028}\\u3000'"}, +{"text":"-\u001f0x7-\u200b\ufeff\u000b\\\"\u0378","repr":"'-\\x1f0x7-\\u200b\\ufeff\\x0b\\\\\"\\u0378'","split":["-","0x7-\u200b\ufeff","\\\"\u0378"],"strip":"-\u001f0x7-\u200b\ufeff\u000b\\\"\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: '-\\x1f0x7-\\u200b\\ufeff\\x0b\\\\\"\\u0378'"}, +{"text":"\u0000-\udb40\udc01\ud835\udfce0x\u000b\u2003\u0663\u001c","repr":"'\\x00-\\U000e0001\ud835\udfce0x\\x0b\\u2003\u0663\\x1c'","split":["\u0000-\udb40\udc01\ud835\udfce0x","\u0663"],"strip":"\u0000-\udb40\udc01\ud835\udfce0x\u000b\u2003\u0663","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x00-\\U000e0001\ud835\udfce0x\\x0b\\u2003\u0663\\x1c'"}, +{"text":"\ud835\udfce\\\uff11\ufeff:+\u001f","repr":"'\ud835\udfce\\\\\uff11\\ufeff:+\\x1f'","split":["\ud835\udfce\\\uff11\ufeff:+"],"strip":"\ud835\udfce\\\uff11\ufeff:+","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud835\udfce\\\\\uff11\\ufeff:+\\x1f'"}, +{"text":"0x-\u200b\u0085\ud83d\ude00:+aX7\uff11","repr":"'0x-\\u200b\\x85\ud83d\ude00:+aX7\uff11'","split":["0x-\u200b","\ud83d\ude00:+aX7\uff11"],"strip":"0x-\u200b\u0085\ud83d\ude00:+aX7\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '0x-\\u200b\\x85\ud83d\ude00:+aX7\uff11'"}, +{"text":"\u00e9\t-","repr":"'\u00e9\\t-'","split":["\u00e9","-"],"strip":"\u00e9\t-","uuid":null,"int_error":"invalid literal for int() with base 10: '\u00e9\\t-'"}, +{"text":"\\\ufeff","repr":"'\\\\\\ufeff'","split":["\\\ufeff"],"strip":"\\\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '\\\\\\ufeff'"}, +{"text":"\uff11}","repr":"'\uff11}'","split":["\uff11}"],"strip":"\uff11}","uuid":null,"int_error":"invalid literal for int() with base 10: '\uff11}'"}, +{"text":"\n1 a","repr":"'\\n1 a'","split":["1","a"],"strip":"1 a","uuid":null,"int_error":"invalid literal for int() with base 10: '\\n1 a'"}, +{"text":"\\:\ud83d\ude00\u3000\u0000:\na\u00ad\uff11\ud835\udfce","repr":"'\\\\:\ud83d\ude00\\u3000\\x00:\\na\\xad\uff11\ud835\udfce'","split":["\\:\ud83d\ude00","\u0000:","a\u00ad\uff11\ud835\udfce"],"strip":"\\:\ud83d\ude00\u3000\u0000:\na\u00ad\uff11\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: '\\\\:\ud83d\ude00\\u3000\\x00:\\na\\xad\uff11\ud835\udfce'"}, +{"text":"a\u0085\tx\u200b=","repr":"'a\\x85\\tx\\u200b='","split":["a","x\u200b="],"strip":"a\u0085\tx\u200b=","uuid":null,"int_error":"invalid literal for int() with base 10: 'a\\x85\\tx\\u200b='"}, +{"text":"\u200ba{","repr":"'\\u200ba{'","split":["\u200ba{"],"strip":"\u200ba{","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u200ba{'"}, +{"text":"}\u001f\udb40\udc01","repr":"'}\\x1f\\U000e0001'","split":["}","\udb40\udc01"],"strip":"}\u001f\udb40\udc01","uuid":null,"int_error":"invalid literal for int() with base 10: '}\\x1f\\U000e0001'"}, +{"text":"a7\u00a0\"\u0000\t\u0378\u0378\u0000\\\u001f","repr":"'a7\\xa0\"\\x00\\t\\u0378\\u0378\\x00\\\\\\x1f'","split":["a7","\"\u0000","\u0378\u0378\u0000\\"],"strip":"a7\u00a0\"\u0000\t\u0378\u0378\u0000\\","uuid":null,"int_error":"invalid literal for int() with base 10: 'a7\\xa0\"\\x00\\t\\u0378\\u0378\\x00\\\\\\x1f'"}, +{"text":"\u00ad+-}X1","repr":"'\\xad+-}X1'","split":["\u00ad+-}X1"],"strip":"\u00ad+-}X1","uuid":null,"int_error":"invalid literal for int() with base 10: '\\xad+-}X1'"}, +{"text":"\u001f7\u00ad\"\ufeff","repr":"'\\x1f7\\xad\"\\ufeff'","split":["7\u00ad\"\ufeff"],"strip":"7\u00ad\"\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f7\\xad\"\\ufeff'"}, +{"text":"\ud83d\ude00X1\"\u001f\ud83d\ude00\ufeff\n,'\u0663","repr":"'\ud83d\ude00X1\"\\x1f\ud83d\ude00\\ufeff\\n,\\'\u0663'","split":["\ud83d\ude00X1\"","\ud83d\ude00\ufeff",",'\u0663"],"strip":"\ud83d\ude00X1\"\u001f\ud83d\ude00\ufeff\n,'\u0663","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud83d\ude00X1\"\\x1f\ud83d\ude00\\ufeff\\n,\\'\u0663'"}, +{"text":":uuid:","repr":"':uuid:'","split":[":uuid:"],"strip":":uuid:","uuid":null,"int_error":"invalid literal for int() with base 10: ':uuid:'"}, +{"text":"\u2003","repr":"'\\u2003'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2003'"}, +{"text":"_\u000b0x\u2003auuid:'\u001c","repr":"\"_\\x0b0x\\u2003auuid:'\\x1c\"","split":["_","0x","auuid:'"],"strip":"_\u000b0x\u2003auuid:'","uuid":null,"int_error":"invalid literal for int() with base 10: \"_\\x0b0x\\u2003auuid:'\\x1c\""}, +{"text":"a\u30000=\u007f\u001c+_","repr":"'a\\u30000=\\x7f\\x1c+_'","split":["a","0=\u007f","+_"],"strip":"a\u30000=\u007f\u001c+_","uuid":null,"int_error":"invalid literal for int() with base 10: 'a\\u30000=\\x7f\\x1c+_'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"\"","repr":"'\"'","split":["\""],"strip":"\"","uuid":null,"int_error":"invalid literal for int() with base 10: '\"'"}, +{"text":"\u001f\u00001\u2003\u0000\n\u001f\uff119==\u00a0","repr":"'\\x1f\\x001\\u2003\\x00\\n\\x1f\uff119==\\xa0'","split":["\u00001","\u0000","\uff119=="],"strip":"\u00001\u2003\u0000\n\u001f\uff119==","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f\\x001\\u2003\\x00\\n\\x1f\uff119==\\xa0'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"\u0000\u00ad\u300009 F\u0378\u001c\ud835\udfce\u2003","repr":"'\\x00\\xad\\u300009 F\\u0378\\x1c\ud835\udfce\\u2003'","split":["\u0000\u00ad","09","F\u0378","\ud835\udfce"],"strip":"\u0000\u00ad\u300009 F\u0378\u001c\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x00\\xad\\u300009 F\\u0378\\x1c\ud835\udfce\\u2003'"}, +{"text":"\u2003","repr":"'\\u2003'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2003'"}, +{"text":"}F\\\u2003,\u001f\n7\t\"","repr":"'}F\\\\\\u2003,\\x1f\\n7\\t\"'","split":["}F\\",",","7","\""],"strip":"}F\\\u2003,\u001f\n7\t\"","uuid":null,"int_error":"invalid literal for int() with base 10: '}F\\\\\\u2003,\\x1f\\n7\\t\"'"}, +{"text":"F\u0085{\u2003'\u007f\ufeff\u001f","repr":"\"F\\x85{\\u2003'\\x7f\\ufeff\\x1f\"","split":["F","{","'\u007f\ufeff"],"strip":"F\u0085{\u2003'\u007f\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: \"F\\x85{\\u2003'\\x7f\\ufeff\\x1f\""}, +{"text":"\u2028\u00ad_F\u3000X","repr":"'\\u2028\\xad_F\\u3000X'","split":["\u00ad_F","X"],"strip":"\u00ad_F\u3000X","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2028\\xad_F\\u3000X'"}, +{"text":"=_\ud835\udfce","repr":"'=_\ud835\udfce'","split":["=_\ud835\udfce"],"strip":"=_\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: '=_\ud835\udfce'"}, +{"text":",\n\u000b9","repr":"',\\n\\x0b9'","split":[",","9"],"strip":",\n\u000b9","uuid":null,"int_error":"invalid literal for int() with base 10: ',\\n\\x0b9'"}, +{"text":":\u00ad\ud835\udfce'","repr":"\":\\xad\ud835\udfce'\"","split":[":\u00ad\ud835\udfce'"],"strip":":\u00ad\ud835\udfce'","uuid":null,"int_error":"invalid literal for int() with base 10: \":\\xad\ud835\udfce'\""}, +{"text":"\u0085","repr":"'\\x85'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85'"}, +{"text":"a\u00e90","repr":"'a\u00e90'","split":["a\u00e90"],"strip":"a\u00e90","uuid":null,"int_error":"invalid literal for int() with base 10: 'a\u00e90'"}, +{"text":"10x}\u0663a,\ufeffX=","repr":"'10x}\u0663a,\\ufeffX='","split":["10x}\u0663a,\ufeffX="],"strip":"10x}\u0663a,\ufeffX=","uuid":null,"int_error":"invalid literal for int() with base 10: '10x}\u0663a,\\ufeffX='"}, +{"text":"0+","repr":"'0+'","split":["0+"],"strip":"0+","uuid":null,"int_error":"invalid literal for int() with base 10: '0+'"}, +{"text":" \u2003+\u200bXurn:\u0663}","repr":"' \\u2003+\\u200bXurn:\u0663}'","split":["+\u200bXurn:\u0663}"],"strip":"+\u200bXurn:\u0663}","uuid":null,"int_error":"invalid literal for int() with base 10: ' \\u2003+\\u200bXurn:\u0663}'"}, +{"text":"=\uff11","repr":"'=\uff11'","split":["=\uff11"],"strip":"=\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '=\uff11'"}, +{"text":"\u001c\u06630-+x\udb40\udc01=uuid:","repr":"'\\x1c\u06630-+x\\U000e0001=uuid:'","split":["\u06630-+x\udb40\udc01=uuid:"],"strip":"\u06630-+x\udb40\udc01=uuid:","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1c\u06630-+x\\U000e0001=uuid:'"}, +{"text":"\u0663\turn:',\u007f","repr":"\"\u0663\\turn:',\\x7f\"","split":["\u0663","urn:',\u007f"],"strip":"\u0663\turn:',\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: \"\u0663\\turn:',\\x7f\""}, +{"text":"F\u000b\\\u000b\u2003\udb40\udc01","repr":"'F\\x0b\\\\\\x0b\\u2003\\U000e0001'","split":["F","\\","\udb40\udc01"],"strip":"F\u000b\\\u000b\u2003\udb40\udc01","uuid":null,"int_error":"invalid literal for int() with base 10: 'F\\x0b\\\\\\x0b\\u2003\\U000e0001'"}, +{"text":"\u0378X\u001fuuid:\u2028+\u0000\ud83d\ude00\u2003 \u0085'","repr":"\"\\u0378X\\x1fuuid:\\u2028+\\x00\ud83d\ude00\\u2003 \\x85'\"","split":["\u0378X","uuid:","+\u0000\ud83d\ude00","'"],"strip":"\u0378X\u001fuuid:\u2028+\u0000\ud83d\ude00\u2003 \u0085'","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\u0378X\\x1fuuid:\\u2028+\\x00\ud83d\ude00\\u2003 \\x85'\""}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"\u007f{,1a\u0085\uff11a","repr":"'\\x7f{,1a\\x85\uff11a'","split":["\u007f{,1a","\uff11a"],"strip":"\u007f{,1a\u0085\uff11a","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f{,1a\\x85\uff11a'"}, +{"text":",\uff11\ud83d\ude00uuid:a\u0000\u0000\\","repr":"',\uff11\ud83d\ude00uuid:a\\x00\\x00\\\\'","split":[",\uff11\ud83d\ude00uuid:a\u0000\u0000\\"],"strip":",\uff11\ud83d\ude00uuid:a\u0000\u0000\\","uuid":null,"int_error":"invalid literal for int() with base 10: ',\uff11\ud83d\ude00uuid:a\\x00\\x00\\\\'"}, +{"text":"\u0378a\u00ad","repr":"'\\u0378a\\xad'","split":["\u0378a\u00ad"],"strip":"\u0378a\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u0378a\\xad'"}, +{"text":"'","repr":"\"'\"","split":["'"],"strip":"'","uuid":null,"int_error":"invalid literal for int() with base 10: \"'\""}, +{"text":"\u3000\u200b_x0\u0663\u20039F","repr":"'\\u3000\\u200b_x0\u0663\\u20039F'","split":["\u200b_x0\u0663","9F"],"strip":"\u200b_x0\u0663\u20039F","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\u200b_x0\u0663\\u20039F'"}, +{"text":"1,\ufeff\u00a000x0x","repr":"'1,\\ufeff\\xa000x0x'","split":["1,\ufeff","00x0x"],"strip":"1,\ufeff\u00a000x0x","uuid":null,"int_error":"invalid literal for int() with base 10: '1,\\ufeff\\xa000x0x'"}, +{"text":"\u007f\u3000\u00e9_x-a\u007f","repr":"'\\x7f\\u3000\u00e9_x-a\\x7f'","split":["\u007f","\u00e9_x-a\u007f"],"strip":"\u007f\u3000\u00e9_x-a\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f\\u3000\u00e9_x-a\\x7f'"}, +{"text":"\ufeff\u0085\u000bF\u001f\u001f\u3000\ufeff","repr":"'\\ufeff\\x85\\x0bF\\x1f\\x1f\\u3000\\ufeff'","split":["\ufeff","F","\ufeff"],"strip":"\ufeff\u0085\u000bF\u001f\u001f\u3000\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff\\x85\\x0bF\\x1f\\x1f\\u3000\\ufeff'"}, +{"text":"+-\u0000\"\u007f\u0000\u0378\uff11\u3000\u001f","repr":"'+-\\x00\"\\x7f\\x00\\u0378\uff11\\u3000\\x1f'","split":["+-\u0000\"\u007f\u0000\u0378\uff11"],"strip":"+-\u0000\"\u007f\u0000\u0378\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '+-\\x00\"\\x7f\\x00\\u0378\uff11\\u3000\\x1f'"}, +{"text":"\ud83d\ude00\n\ud83d\ude00x\u0000\ufeff\\","repr":"'\ud83d\ude00\\n\ud83d\ude00x\\x00\\ufeff\\\\'","split":["\ud83d\ude00","\ud83d\ude00x\u0000\ufeff\\"],"strip":"\ud83d\ude00\n\ud83d\ude00x\u0000\ufeff\\","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud83d\ude00\\n\ud83d\ude00x\\x00\\ufeff\\\\'"}, +{"text":"-\u3000\u0085:F\u00ad","repr":"'-\\u3000\\x85:F\\xad'","split":["-",":F\u00ad"],"strip":"-\u3000\u0085:F\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '-\\u3000\\x85:F\\xad'"}, +{"text":"9\ufeff","repr":"'9\\ufeff'","split":["9\ufeff"],"strip":"9\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '9\\ufeff'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"}\u001cF0x","repr":"'}\\x1cF0x'","split":["}","F0x"],"strip":"}\u001cF0x","uuid":null,"int_error":"invalid literal for int() with base 10: '}\\x1cF0x'"}, +{"text":"\"X9'\u001f\\\u2003","repr":"'\"X9\\'\\x1f\\\\\\u2003'","split":["\"X9'","\\"],"strip":"\"X9'\u001f\\","uuid":null,"int_error":"invalid literal for int() with base 10: '\"X9\\'\\x1f\\\\\\u2003'"}, +{"text":"\u200b\u2003,","repr":"'\\u200b\\u2003,'","split":["\u200b",","],"strip":"\u200b\u2003,","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u200b\\u2003,'"}, +{"text":"}+","repr":"'}+'","split":["}+"],"strip":"}+","uuid":null,"int_error":"invalid literal for int() with base 10: '}+'"}, +{"text":"\u00a0\ud835\udfce0_\t11+\u00a0","repr":"'\\xa0\ud835\udfce0_\\t11+\\xa0'","split":["\ud835\udfce0_","11+"],"strip":"\ud835\udfce0_\t11+","uuid":null,"int_error":"invalid literal for int() with base 10: '\\xa0\ud835\udfce0_\\t11+\\xa0'"}, +{"text":"\\\ud83d\ude00a\ufeffx\uff11\u00e9''=urn:uuid:","repr":"\"\\\\\ud83d\ude00a\\ufeffx\uff11\u00e9''=urn:uuid:\"","split":["\\\ud83d\ude00a\ufeffx\uff11\u00e9''=urn:uuid:"],"strip":"\\\ud83d\ude00a\ufeffx\uff11\u00e9''=urn:uuid:","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\\\\ud83d\ude00a\\ufeffx\uff11\u00e9''=urn:uuid:\""}, +{"text":"\u007f0x\u200b_","repr":"'\\x7f0x\\u200b_'","split":["\u007f0x\u200b_"],"strip":"\u007f0x\u200b_","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f0x\\u200b_'"}, +{"text":"\ud83d\ude00\ud835\udfce\u00e90x\u00ad Fa\u0378","repr":"'\ud83d\ude00\ud835\udfce\u00e90x\\xad Fa\\u0378'","split":["\ud83d\ude00\ud835\udfce\u00e90x\u00ad","Fa\u0378"],"strip":"\ud83d\ude00\ud835\udfce\u00e90x\u00ad Fa\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud83d\ude00\ud835\udfce\u00e90x\\xad Fa\\u0378'"}, +{"text":"+\u00ad\u3000\u200b\udb40\udc01\"","repr":"'+\\xad\\u3000\\u200b\\U000e0001\"'","split":["+\u00ad","\u200b\udb40\udc01\""],"strip":"+\u00ad\u3000\u200b\udb40\udc01\"","uuid":null,"int_error":"invalid literal for int() with base 10: '+\\xad\\u3000\\u200b\\U000e0001\"'"}, +{"text":"\u2003-x\\\u06630x\u00e9{{","repr":"'\\u2003-x\\\\\u06630x\u00e9{{'","split":["-x\\\u06630x\u00e9{{"],"strip":"-x\\\u06630x\u00e9{{","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2003-x\\\\\u06630x\u00e9{{'"}, +{"text":":urn:\u00ad\"\u00ad\u0378\"\uff117-","repr":"':urn:\\xad\"\\xad\\u0378\"\uff117-'","split":[":urn:\u00ad\"\u00ad\u0378\"\uff117-"],"strip":":urn:\u00ad\"\u00ad\u0378\"\uff117-","uuid":null,"int_error":"invalid literal for int() with base 10: ':urn:\\xad\"\\xad\\u0378\"\uff117-'"}, +{"text":"\u001f\u0000\u2003+\u001c}\ufeff=\u0085\uff11","repr":"'\\x1f\\x00\\u2003+\\x1c}\\ufeff=\\x85\uff11'","split":["\u0000","+","}\ufeff=","\uff11"],"strip":"\u0000\u2003+\u001c}\ufeff=\u0085\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f\\x00\\u2003+\\x1c}\\ufeff=\\x85\uff11'"}, +{"text":"\u200b\u000b 1","repr":"'\\u200b\\x0b 1'","split":["\u200b","1"],"strip":"\u200b\u000b 1","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u200b\\x0b 1'"}, +{"text":"+\u2028\u000b\u00e9\u00ad+\\\u007f","repr":"'+\\u2028\\x0b\u00e9\\xad+\\\\\\x7f'","split":["+","\u00e9\u00ad+\\\u007f"],"strip":"+\u2028\u000b\u00e9\u00ad+\\\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '+\\u2028\\x0b\u00e9\\xad+\\\\\\x7f'"}, +{"text":"\\{\u00ad_9,{\uff117\u001f\ud835\udfce,","repr":"'\\\\{\\xad_9,{\uff117\\x1f\ud835\udfce,'","split":["\\{\u00ad_9,{\uff117","\ud835\udfce,"],"strip":"\\{\u00ad_9,{\uff117\u001f\ud835\udfce,","uuid":null,"int_error":"invalid literal for int() with base 10: '\\\\{\\xad_9,{\uff117\\x1f\ud835\udfce,'"}, +{"text":"-F\u00e9\u001c\u03787\u0000\u06630__","repr":"'-F\u00e9\\x1c\\u03787\\x00\u06630__'","split":["-F\u00e9","\u03787\u0000\u06630__"],"strip":"-F\u00e9\u001c\u03787\u0000\u06630__","uuid":null,"int_error":"invalid literal for int() with base 10: '-F\u00e9\\x1c\\u03787\\x00\u06630__'"}, +{"text":"+\ud83d\ude00}0x\u3000}\u2028, _}\u2028","repr":"'+\ud83d\ude00}0x\\u3000}\\u2028, _}\\u2028'","split":["+\ud83d\ude00}0x","}",",","_}"],"strip":"+\ud83d\ude00}0x\u3000}\u2028, _}","uuid":null,"int_error":"invalid literal for int() with base 10: '+\ud83d\ude00}0x\\u3000}\\u2028, _}\\u2028'"}, +{"text":"9\u001f\u0663\\F","repr":"'9\\x1f\u0663\\\\F'","split":["9","\u0663\\F"],"strip":"9\u001f\u0663\\F","uuid":null,"int_error":"invalid literal for int() with base 10: '9\\x1f\u0663\\\\F'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"=\u2028\u0000_=\ud835\udfce0\n}\ufeff'-","repr":"\"=\\u2028\\x00_=\ud835\udfce0\\n}\\ufeff'-\"","split":["=","\u0000_=\ud835\udfce0","}\ufeff'-"],"strip":"=\u2028\u0000_=\ud835\udfce0\n}\ufeff'-","uuid":null,"int_error":"invalid literal for int() with base 10: \"=\\u2028\\x00_=\ud835\udfce0\\n}\\ufeff'-\""}, +{"text":"\u03787}1","repr":"'\\u03787}1'","split":["\u03787}1"],"strip":"\u03787}1","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u03787}1'"}, +{"text":"90x\u2028\u001c\u0085\u000b\u0000","repr":"'90x\\u2028\\x1c\\x85\\x0b\\x00'","split":["90x","\u0000"],"strip":"90x\u2028\u001c\u0085\u000b\u0000","uuid":null,"int_error":"invalid literal for int() with base 10: '90x\\u2028\\x1c\\x85\\x0b\\x00'"}, +{"text":"a:_\u001c","repr":"'a:_\\x1c'","split":["a:_"],"strip":"a:_","uuid":null,"int_error":"invalid literal for int() with base 10: 'a:_\\x1c'"}, +{"text":"urn:9\u001c\u007furn:,\u0378_\uff11","repr":"'urn:9\\x1c\\x7furn:,\\u0378_\uff11'","split":["urn:9","\u007furn:,\u0378_\uff11"],"strip":"urn:9\u001c\u007furn:,\u0378_\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:9\\x1c\\x7furn:,\\u0378_\uff11'"}, +{"text":"}\u0000\u0000urn:{9","repr":"'}\\x00\\x00urn:{9'","split":["}\u0000\u0000urn:{9"],"strip":"}\u0000\u0000urn:{9","uuid":null,"int_error":"invalid literal for int() with base 10: '}\\x00\\x00urn:{9'"}, +{"text":"\u0378_\u007f=\u007f\u00a0\u000b","repr":"'\\u0378_\\x7f=\\x7f\\xa0\\x0b'","split":["\u0378_\u007f=\u007f"],"strip":"\u0378_\u007f=\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u0378_\\x7f=\\x7f\\xa0\\x0b'"}, +{"text":"9\ufeff7uuid:7-=\u2028\ud835\udfce\u30009","repr":"'9\\ufeff7uuid:7-=\\u2028\ud835\udfce\\u30009'","split":["9\ufeff7uuid:7-=","\ud835\udfce","9"],"strip":"9\ufeff7uuid:7-=\u2028\ud835\udfce\u30009","uuid":null,"int_error":"invalid literal for int() with base 10: '9\\ufeff7uuid:7-=\\u2028\ud835\udfce\\u30009'"}, +{"text":"\u00e9+9\\\t7","repr":"'\u00e9+9\\\\\\t7'","split":["\u00e9+9\\","7"],"strip":"\u00e9+9\\\t7","uuid":null,"int_error":"invalid literal for int() with base 10: '\u00e9+9\\\\\\t7'"}, +{"text":"\u007f\ufeff\n\u0378X\u001fF","repr":"'\\x7f\\ufeff\\n\\u0378X\\x1fF'","split":["\u007f\ufeff","\u0378X","F"],"strip":"\u007f\ufeff\n\u0378X\u001fF","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f\\ufeff\\n\\u0378X\\x1fF'"}, +{"text":"\n7","repr":"'\\n7'","split":["7"],"strip":"7","uuid":null,"int":"7"}, +{"text":"_'+,\u200b\u0378uuid:x","repr":"\"_'+,\\u200b\\u0378uuid:x\"","split":["_'+,\u200b\u0378uuid:x"],"strip":"_'+,\u200b\u0378uuid:x","uuid":null,"int_error":"invalid literal for int() with base 10: \"_'+,\\u200b\\u0378uuid:x\""}, +{"text":":urn:\u200b0x\t9-\u0085","repr":"':urn:\\u200b0x\\t9-\\x85'","split":[":urn:\u200b0x","9-"],"strip":":urn:\u200b0x\t9-","uuid":null,"int_error":"invalid literal for int() with base 10: ':urn:\\u200b0x\\t9-\\x85'"}, +{"text":"X\u00a0\udb40\udc01\u0378","repr":"'X\\xa0\\U000e0001\\u0378'","split":["X","\udb40\udc01\u0378"],"strip":"X\u00a0\udb40\udc01\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: 'X\\xa0\\U000e0001\\u0378'"}, +{"text":",uuid:7X\tuuid:\u00a0\u000b\u2003 {","repr":"',uuid:7X\\tuuid:\\xa0\\x0b\\u2003 {'","split":[",uuid:7X","uuid:","{"],"strip":",uuid:7X\tuuid:\u00a0\u000b\u2003 {","uuid":null,"int_error":"invalid literal for int() with base 10: ',uuid:7X\\tuuid:\\xa0\\x0b\\u2003 {'"}, +{"text":"\u00e90\u001c","repr":"'\u00e90\\x1c'","split":["\u00e90"],"strip":"\u00e90","uuid":null,"int_error":"invalid literal for int() with base 10: '\u00e90\\x1c'"}, +{"text":"\u00e9x \u00a0\u001f\u200b","repr":"'\u00e9x \\xa0\\x1f\\u200b'","split":["\u00e9x","\u200b"],"strip":"\u00e9x \u00a0\u001f\u200b","uuid":null,"int_error":"invalid literal for int() with base 10: '\u00e9x \\xa0\\x1f\\u200b'"}, +{"text":"={\udb40\udc01=7-\ud835\udfce0x 0x\u000b","repr":"'={\\U000e0001=7-\ud835\udfce0x 0x\\x0b'","split":["={\udb40\udc01=7-\ud835\udfce0x","0x"],"strip":"={\udb40\udc01=7-\ud835\udfce0x 0x","uuid":null,"int_error":"invalid literal for int() with base 10: '={\\U000e0001=7-\ud835\udfce0x 0x\\x0b'"}, +{"text":"\n\udb40\udc01xx0x}\ud835\udfce{\ud835\udfce1","repr":"'\\n\\U000e0001xx0x}\ud835\udfce{\ud835\udfce1'","split":["\udb40\udc01xx0x}\ud835\udfce{\ud835\udfce1"],"strip":"\udb40\udc01xx0x}\ud835\udfce{\ud835\udfce1","uuid":null,"int_error":"invalid literal for int() with base 10: '\\n\\U000e0001xx0x}\ud835\udfce{\ud835\udfce1'"}, +{"text":"urn:=","repr":"'urn:='","split":["urn:="],"strip":"urn:=","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:='"}, +{"text":"\ud835\udfce\\\u007f\u00ad\u0378\u0000\uff11\u2028F0\u2003\u007f","repr":"'\ud835\udfce\\\\\\x7f\\xad\\u0378\\x00\uff11\\u2028F0\\u2003\\x7f'","split":["\ud835\udfce\\\u007f\u00ad\u0378\u0000\uff11","F0","\u007f"],"strip":"\ud835\udfce\\\u007f\u00ad\u0378\u0000\uff11\u2028F0\u2003\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud835\udfce\\\\\\x7f\\xad\\u0378\\x00\uff11\\u2028F0\\u2003\\x7f'"}, +{"text":"\u00a0","repr":"'\\xa0'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\xa0'"}, +{"text":"\u0000","repr":"'\\x00'","split":["\u0000"],"strip":"\u0000","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x00'"}, +{"text":"\u3000\\\u30007\u00e9\u000b\u2003x9}F\u00a0","repr":"'\\u3000\\\\\\u30007\u00e9\\x0b\\u2003x9}F\\xa0'","split":["\\","7\u00e9","x9}F"],"strip":"\\\u30007\u00e9\u000b\u2003x9}F","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\\\\\u30007\u00e9\\x0b\\u2003x9}F\\xa0'"}, +{"text":"\u0000\u0085\u3000","repr":"'\\x00\\x85\\u3000'","split":["\u0000"],"strip":"\u0000","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x00\\x85\\u3000'"}, +{"text":"7\udb40\udc0117\u3000\udb40\udc01\u0663\u00a0\u001c","repr":"'7\\U000e000117\\u3000\\U000e0001\u0663\\xa0\\x1c'","split":["7\udb40\udc0117","\udb40\udc01\u0663"],"strip":"7\udb40\udc0117\u3000\udb40\udc01\u0663","uuid":null,"int_error":"invalid literal for int() with base 10: '7\\U000e000117\\u3000\\U000e0001\u0663\\xa0\\x1c'"}, +{"text":" \\\t\t{F\n","repr":"' \\\\\\t\\t{F\\n'","split":["\\","{F"],"strip":"\\\t\t{F","uuid":null,"int_error":"invalid literal for int() with base 10: ' \\\\\\t\\t{F\\n'"}, +{"text":"0xF\u0085urn:0","repr":"'0xF\\x85urn:0'","split":["0xF","urn:0"],"strip":"0xF\u0085urn:0","uuid":null,"int_error":"invalid literal for int() with base 10: '0xF\\x85urn:0'"}, +{"text":"\uff11","repr":"'\uff11'","split":["\uff11"],"strip":"\uff11","uuid":null,"int":"1"}, +{"text":"\u00a0\u200b71\uff111\u007f9'\u0000\u001f,","repr":"\"\\xa0\\u200b71\uff111\\x7f9'\\x00\\x1f,\"","split":["\u200b71\uff111\u007f9'\u0000",","],"strip":"\u200b71\uff111\u007f9'\u0000\u001f,","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\xa0\\u200b71\uff111\\x7f9'\\x00\\x1f,\""}, +{"text":"_\u0378\u2003\u007f","repr":"'_\\u0378\\u2003\\x7f'","split":["_\u0378","\u007f"],"strip":"_\u0378\u2003\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '_\\u0378\\u2003\\x7f'"}, +{"text":"\u200b\u200b\uff11'\\\ufeff=\u3000'","repr":"\"\\u200b\\u200b\uff11'\\\\\\ufeff=\\u3000'\"","split":["\u200b\u200b\uff11'\\\ufeff=","'"],"strip":"\u200b\u200b\uff11'\\\ufeff=\u3000'","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\u200b\\u200b\uff11'\\\\\\ufeff=\\u3000'\""}, +{"text":"-\t\\-={\u2028\t0x+\n}","repr":"'-\\t\\\\-={\\u2028\\t0x+\\n}'","split":["-","\\-={","0x+","}"],"strip":"-\t\\-={\u2028\t0x+\n}","uuid":null,"int_error":"invalid literal for int() with base 10: '-\\t\\\\-={\\u2028\\t0x+\\n}'"}, +{"text":"\ud83d\ude00\u000b9'\uff11F\u001fuuid:\u000b{\ufeff7","repr":"\"\ud83d\ude00\\x0b9'\uff11F\\x1fuuid:\\x0b{\\ufeff7\"","split":["\ud83d\ude00","9'\uff11F","uuid:","{\ufeff7"],"strip":"\ud83d\ude00\u000b9'\uff11F\u001fuuid:\u000b{\ufeff7","uuid":null,"int_error":"invalid literal for int() with base 10: \"\ud83d\ude00\\x0b9'\uff11F\\x1fuuid:\\x0b{\\ufeff7\""}, +{"text":"\u007f0","repr":"'\\x7f0'","split":["\u007f0"],"strip":"\u007f0","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f0'"}, +{"text":"urn:1\ufeff\u0085","repr":"'urn:1\\ufeff\\x85'","split":["urn:1\ufeff"],"strip":"urn:1\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:1\\ufeff\\x85'"}, +{"text":"0x\u001f}uuid:,a\ud83d\ude00\u00a0","repr":"'0x\\x1f}uuid:,a\ud83d\ude00\\xa0'","split":["0x","}uuid:,a\ud83d\ude00"],"strip":"0x\u001f}uuid:,a\ud83d\ude00","uuid":null,"int_error":"invalid literal for int() with base 10: '0x\\x1f}uuid:,a\ud83d\ude00\\xa0'"}, +{"text":"\u03789\udb40\udc01{\ud83d\ude00=0x-\u007f\u00a00","repr":"'\\u03789\\U000e0001{\ud83d\ude00=0x-\\x7f\\xa00'","split":["\u03789\udb40\udc01{\ud83d\ude00=0x-\u007f","0"],"strip":"\u03789\udb40\udc01{\ud83d\ude00=0x-\u007f\u00a00","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u03789\\U000e0001{\ud83d\ude00=0x-\\x7f\\xa00'"}, +{"text":"\u2028","repr":"'\\u2028'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2028'"}, +{"text":"\ud835\udfceuuid:'\udb40\udc01\\","repr":"\"\ud835\udfceuuid:'\\U000e0001\\\\\"","split":["\ud835\udfceuuid:'\udb40\udc01\\"],"strip":"\ud835\udfceuuid:'\udb40\udc01\\","uuid":null,"int_error":"invalid literal for int() with base 10: \"\ud835\udfceuuid:'\\U000e0001\\\\\""}, +{"text":"\ufeff\u0663\u3000\uff11\u200b","repr":"'\\ufeff\u0663\\u3000\uff11\\u200b'","split":["\ufeff\u0663","\uff11\u200b"],"strip":"\ufeff\u0663\u3000\uff11\u200b","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff\u0663\\u3000\uff11\\u200b'"}, +{"text":"\udb40\udc011","repr":"'\\U000e00011'","split":["\udb40\udc011"],"strip":"\udb40\udc011","uuid":null,"int_error":"invalid literal for int() with base 10: '\\U000e00011'"}, +{"text":"\ud835\udfce,\\\ufeff\uff11\u00e9\ud83d\ude00\u2028\ufeff","repr":"'\ud835\udfce,\\\\\\ufeff\uff11\u00e9\ud83d\ude00\\u2028\\ufeff'","split":["\ud835\udfce,\\\ufeff\uff11\u00e9\ud83d\ude00","\ufeff"],"strip":"\ud835\udfce,\\\ufeff\uff11\u00e9\ud83d\ude00\u2028\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud835\udfce,\\\\\\ufeff\uff11\u00e9\ud83d\ude00\\u2028\\ufeff'"}, +{"text":"\u001fuuid:\"\udb40\udc01","repr":"'\\x1fuuid:\"\\U000e0001'","split":["uuid:\"\udb40\udc01"],"strip":"uuid:\"\udb40\udc01","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1fuuid:\"\\U000e0001'"}, +{"text":"\u2028","repr":"'\\u2028'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2028'"}, +{"text":"X+\u0000\ufeff\ud83d\ude00\uff11=urn:\udb40\udc01:","repr":"'X+\\x00\\ufeff\ud83d\ude00\uff11=urn:\\U000e0001:'","split":["X+\u0000\ufeff\ud83d\ude00\uff11=urn:\udb40\udc01:"],"strip":"X+\u0000\ufeff\ud83d\ude00\uff11=urn:\udb40\udc01:","uuid":null,"int_error":"invalid literal for int() with base 10: 'X+\\x00\\ufeff\ud83d\ude00\uff11=urn:\\U000e0001:'"}, +{"text":"\u0085\u0663\u0663\udb40\udc01\u00a0\ufeff+Fa\u00ad","repr":"'\\x85\u0663\u0663\\U000e0001\\xa0\\ufeff+Fa\\xad'","split":["\u0663\u0663\udb40\udc01","\ufeff+Fa\u00ad"],"strip":"\u0663\u0663\udb40\udc01\u00a0\ufeff+Fa\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85\u0663\u0663\\U000e0001\\xa0\\ufeff+Fa\\xad'"}, +{"text":"\n\u0000\u000b0x","repr":"'\\n\\x00\\x0b0x'","split":["\u0000","0x"],"strip":"\u0000\u000b0x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\n\\x00\\x0b0x'"}, +{"text":"\u0000\\0x,9\u0000=:","repr":"'\\x00\\\\0x,9\\x00=:'","split":["\u0000\\0x,9\u0000=:"],"strip":"\u0000\\0x,9\u0000=:","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x00\\\\0x,9\\x00=:'"}, +{"text":"0x\n\u0085X1\u00e9urn: ","repr":"'0x\\n\\x85X1\u00e9urn: '","split":["0x","X1\u00e9urn:"],"strip":"0x\n\u0085X1\u00e9urn:","uuid":null,"int_error":"invalid literal for int() with base 10: '0x\\n\\x85X1\u00e9urn: '"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"\u0085F \"\u0085\u200b\ufeff,","repr":"'\\x85F \"\\x85\\u200b\\ufeff,'","split":["F","\"","\u200b\ufeff,"],"strip":"F \"\u0085\u200b\ufeff,","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85F \"\\x85\\u200b\\ufeff,'"}, +{"text":"\u000b\u20030x{\u0663x","repr":"'\\x0b\\u20030x{\u0663x'","split":["0x{\u0663x"],"strip":"0x{\u0663x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x0b\\u20030x{\u0663x'"}, +{"text":"'\ud835\udfce:1urn:\"","repr":"'\\'\ud835\udfce:1urn:\"'","split":["'\ud835\udfce:1urn:\""],"strip":"'\ud835\udfce:1urn:\"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\'\ud835\udfce:1urn:\"'"}, +{"text":"\uff11\u001f{xuuid:urn:\n","repr":"'\uff11\\x1f{xuuid:urn:\\n'","split":["\uff11","{xuuid:urn:"],"strip":"\uff11\u001f{xuuid:urn:","uuid":null,"int_error":"invalid literal for int() with base 10: '\uff11\\x1f{xuuid:urn:\\n'"}, +{"text":"_\n{-\t\u0663","repr":"'_\\n{-\\t\u0663'","split":["_","{-","\u0663"],"strip":"_\n{-\t\u0663","uuid":null,"int_error":"invalid literal for int() with base 10: '_\\n{-\\t\u0663'"}, +{"text":"\u2028\u000b' 0=","repr":"\"\\u2028\\x0b' 0=\"","split":["'","0="],"strip":"' 0=","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\u2028\\x0b' 0=\""}, +{"text":"\uff11,=0xuuid:'","repr":"\"\uff11,=0xuuid:'\"","split":["\uff11,=0xuuid:'"],"strip":"\uff11,=0xuuid:'","uuid":null,"int_error":"invalid literal for int() with base 10: \"\uff11,=0xuuid:'\""}, +{"text":"\u3000\u00ad, \n","repr":"'\\u3000\\xad, \\n'","split":["\u00ad,"],"strip":"\u00ad,","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\xad, \\n'"}, +{"text":"\u0085\u00ad\n\u2028=\u2003_\u00e9a\u007f_","repr":"'\\x85\\xad\\n\\u2028=\\u2003_\u00e9a\\x7f_'","split":["\u00ad","=","_\u00e9a\u007f_"],"strip":"\u00ad\n\u2028=\u2003_\u00e9a\u007f_","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85\\xad\\n\\u2028=\\u2003_\u00e9a\\x7f_'"}, +{"text":"}\u200b\u00ad-\uff11","repr":"'}\\u200b\\xad-\uff11'","split":["}\u200b\u00ad-\uff11"],"strip":"}\u200b\u00ad-\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '}\\u200b\\xad-\uff11'"}, +{"text":"\u0378\u00a0+\\\na","repr":"'\\u0378\\xa0+\\\\\\na'","split":["\u0378","+\\","a"],"strip":"\u0378\u00a0+\\\na","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u0378\\xa0+\\\\\\na'"}, +{"text":"91x_\u0378\u0085\udb40\udc01\udb40\udc01a\u00e9\u3000","repr":"'91x_\\u0378\\x85\\U000e0001\\U000e0001a\u00e9\\u3000'","split":["91x_\u0378","\udb40\udc01\udb40\udc01a\u00e9"],"strip":"91x_\u0378\u0085\udb40\udc01\udb40\udc01a\u00e9","uuid":null,"int_error":"invalid literal for int() with base 10: '91x_\\u0378\\x85\\U000e0001\\U000e0001a\u00e9\\u3000'"}, +{"text":"+\u200b++\u007f\u3000\\0xX}","repr":"'+\\u200b++\\x7f\\u3000\\\\0xX}'","split":["+\u200b++\u007f","\\0xX}"],"strip":"+\u200b++\u007f\u3000\\0xX}","uuid":null,"int_error":"invalid literal for int() with base 10: '+\\u200b++\\x7f\\u3000\\\\0xX}'"}, +{"text":"\u001fa\"\u00ad'","repr":"'\\x1fa\"\\xad\\''","split":["a\"\u00ad'"],"strip":"a\"\u00ad'","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1fa\"\\xad\\''"}, +{"text":"\u0000\ufeff\u00009\u0085","repr":"'\\x00\\ufeff\\x009\\x85'","split":["\u0000\ufeff\u00009"],"strip":"\u0000\ufeff\u00009","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x00\\ufeff\\x009\\x85'"}, +{"text":"urn:\ud835\udfce\n=:\u0378","repr":"'urn:\ud835\udfce\\n=:\\u0378'","split":["urn:\ud835\udfce","=:\u0378"],"strip":"urn:\ud835\udfce\n=:\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:\ud835\udfce\\n=:\\u0378'"}, +{"text":" :Fuuid:\u001f=,","repr":"' :Fuuid:\\x1f=,'","split":[":Fuuid:","=,"],"strip":":Fuuid:\u001f=,","uuid":null,"int_error":"invalid literal for int() with base 10: ' :Fuuid:\\x1f=,'"}, +{"text":"\u001f01\u2028\u0663\u0085","repr":"'\\x1f01\\u2028\u0663\\x85'","split":["01","\u0663"],"strip":"01\u2028\u0663","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f01\\u2028\u0663\\x85'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"X9\n\ud835\udfce","repr":"'X9\\n\ud835\udfce'","split":["X9","\ud835\udfce"],"strip":"X9\n\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: 'X9\\n\ud835\udfce'"}, +{"text":"\u001f\u00ad_\n\u007f\u3000''","repr":"\"\\x1f\\xad_\\n\\x7f\\u3000''\"","split":["\u00ad_","\u007f","''"],"strip":"\u00ad_\n\u007f\u3000''","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\x1f\\xad_\\n\\x7f\\u3000''\""}, +{"text":"=xx\ufeff\t\u007f=","repr":"'=xx\\ufeff\\t\\x7f='","split":["=xx\ufeff","\u007f="],"strip":"=xx\ufeff\t\u007f=","uuid":null,"int_error":"invalid literal for int() with base 10: '=xx\\ufeff\\t\\x7f='"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"uuid:\u0378}0x=","repr":"'uuid:\\u0378}0x='","split":["uuid:\u0378}0x="],"strip":"uuid:\u0378}0x=","uuid":null,"int_error":"invalid literal for int() with base 10: 'uuid:\\u0378}0x='"}, +{"text":"X\ud83d\ude00\u0378xF\n{\ud83d\ude00\u0085","repr":"'X\ud83d\ude00\\u0378xF\\n{\ud83d\ude00\\x85'","split":["X\ud83d\ude00\u0378xF","{\ud83d\ude00"],"strip":"X\ud83d\ude00\u0378xF\n{\ud83d\ude00","uuid":null,"int_error":"invalid literal for int() with base 10: 'X\ud83d\ude00\\u0378xF\\n{\ud83d\ude00\\x85'"}, +{"text":"0x:,\u2028\u007f\u007f\u000b\u0663=7X","repr":"'0x:,\\u2028\\x7f\\x7f\\x0b\u0663=7X'","split":["0x:,","\u007f\u007f","\u0663=7X"],"strip":"0x:,\u2028\u007f\u007f\u000b\u0663=7X","uuid":null,"int_error":"invalid literal for int() with base 10: '0x:,\\u2028\\x7f\\x7f\\x0b\u0663=7X'"}, +{"text":"++","repr":"'++'","split":["++"],"strip":"++","uuid":null,"int_error":"invalid literal for int() with base 10: '++'"}, +{"text":"\u3000\udb40\udc01}\ud835\udfce_\ufeff\\-\u200buuid:","repr":"'\\u3000\\U000e0001}\ud835\udfce_\\ufeff\\\\-\\u200buuid:'","split":["\udb40\udc01}\ud835\udfce_\ufeff\\-\u200buuid:"],"strip":"\udb40\udc01}\ud835\udfce_\ufeff\\-\u200buuid:","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\U000e0001}\ud835\udfce_\\ufeff\\\\-\\u200buuid:'"}, +{"text":"\u001f\u0378\"x","repr":"'\\x1f\\u0378\"x'","split":["\u0378\"x"],"strip":"\u0378\"x","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f\\u0378\"x'"}, +{"text":"'-\u00a0\u001f\u0378urn:9\ud83d\ude00\ud835\udfce","repr":"\"'-\\xa0\\x1f\\u0378urn:9\ud83d\ude00\ud835\udfce\"","split":["'-","\u0378urn:9\ud83d\ude00\ud835\udfce"],"strip":"'-\u00a0\u001f\u0378urn:9\ud83d\ude00\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: \"'-\\xa0\\x1f\\u0378urn:9\ud83d\ude00\ud835\udfce\""}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"\u0085}+\u200b\u0663\u001f+\u00e9\u2003urn:\u00a0","repr":"'\\x85}+\\u200b\u0663\\x1f+\u00e9\\u2003urn:\\xa0'","split":["}+\u200b\u0663","+\u00e9","urn:"],"strip":"}+\u200b\u0663\u001f+\u00e9\u2003urn:","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85}+\\u200b\u0663\\x1f+\u00e9\\u2003urn:\\xa0'"}, +{"text":"\u0085\u2028","repr":"'\\x85\\u2028'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x85\\u2028'"}, +{"text":"\u2003\ud83d\ude00,\u001c\u000b1uuid:'\u3000","repr":"\"\\u2003\ud83d\ude00,\\x1c\\x0b1uuid:'\\u3000\"","split":["\ud83d\ude00,","1uuid:'"],"strip":"\ud83d\ude00,\u001c\u000b1uuid:'","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\u2003\ud83d\ude00,\\x1c\\x0b1uuid:'\\u3000\""}, +{"text":"\u00ad","repr":"'\\xad'","split":["\u00ad"],"strip":"\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '\\xad'"}, +{"text":"\u001f9\u200b\u000b\u200b\u2003urn:\t","repr":"'\\x1f9\\u200b\\x0b\\u200b\\u2003urn:\\t'","split":["9\u200b","\u200b","urn:"],"strip":"9\u200b\u000b\u200b\u2003urn:","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f9\\u200b\\x0b\\u200b\\u2003urn:\\t'"}, +{"text":"\n\u0378- \u0000\u001f\ud83d\ude00\ud83d\ude00+X,","repr":"'\\n\\u0378- \\x00\\x1f\ud83d\ude00\ud83d\ude00+X,'","split":["\u0378-","\u0000","\ud83d\ude00\ud83d\ude00+X,"],"strip":"\u0378- \u0000\u001f\ud83d\ude00\ud83d\ude00+X,","uuid":null,"int_error":"invalid literal for int() with base 10: '\\n\\u0378- \\x00\\x1f\ud83d\ude00\ud83d\ude00+X,'"}, +{"text":"\"\ud835\udfce,\u007f","repr":"'\"\ud835\udfce,\\x7f'","split":["\"\ud835\udfce,\u007f"],"strip":"\"\ud835\udfce,\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '\"\ud835\udfce,\\x7f'"}, +{"text":"\u00e9'\u00a0X\\:a\u2003urn:\u2028","repr":"\"\u00e9'\\xa0X\\\\:a\\u2003urn:\\u2028\"","split":["\u00e9'","X\\:a","urn:"],"strip":"\u00e9'\u00a0X\\:a\u2003urn:","uuid":null,"int_error":"invalid literal for int() with base 10: \"\u00e9'\\xa0X\\\\:a\\u2003urn:\\u2028\""}, +{"text":":","repr":"':'","split":[":"],"strip":":","uuid":null,"int_error":"invalid literal for int() with base 10: ':'"}, +{"text":"F\u0000:=\u200b\u001f_\u00e9\"x","repr":"'F\\x00:=\\u200b\\x1f_\u00e9\"x'","split":["F\u0000:=\u200b","_\u00e9\"x"],"strip":"F\u0000:=\u200b\u001f_\u00e9\"x","uuid":null,"int_error":"invalid literal for int() with base 10: 'F\\x00:=\\u200b\\x1f_\u00e9\"x'"}, +{"text":"7\u00adurn:\uff11","repr":"'7\\xadurn:\uff11'","split":["7\u00adurn:\uff11"],"strip":"7\u00adurn:\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '7\\xadurn:\uff11'"}, +{"text":"a{,0+\"\u20280x:{\n","repr":"'a{,0+\"\\u20280x:{\\n'","split":["a{,0+\"","0x:{"],"strip":"a{,0+\"\u20280x:{","uuid":null,"int_error":"invalid literal for int() with base 10: 'a{,0+\"\\u20280x:{\\n'"}, +{"text":"\u00e9\ud83d\ude00\u00e9\udb40\udc01\udb40\udc01\u0663\"\udb40\udc01\u3000-","repr":"'\u00e9\ud83d\ude00\u00e9\\U000e0001\\U000e0001\u0663\"\\U000e0001\\u3000-'","split":["\u00e9\ud83d\ude00\u00e9\udb40\udc01\udb40\udc01\u0663\"\udb40\udc01","-"],"strip":"\u00e9\ud83d\ude00\u00e9\udb40\udc01\udb40\udc01\u0663\"\udb40\udc01\u3000-","uuid":null,"int_error":"invalid literal for int() with base 10: '\u00e9\ud83d\ude00\u00e9\\U000e0001\\U000e0001\u0663\"\\U000e0001\\u3000-'"}, +{"text":"\u2028x7 uuid:09","repr":"'\\u2028x7 uuid:09'","split":["x7","uuid:09"],"strip":"x7 uuid:09","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2028x7 uuid:09'"}, +{"text":"97\u0000\u00e9\n\u007f \u007f","repr":"'97\\x00\u00e9\\n\\x7f \\x7f'","split":["97\u0000\u00e9","\u007f","\u007f"],"strip":"97\u0000\u00e9\n\u007f \u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '97\\x00\u00e9\\n\\x7f \\x7f'"}, +{"text":"\ufeff\u2003\"x\ufeff\u200b0\u00ad\ufeffa\u007f","repr":"'\\ufeff\\u2003\"x\\ufeff\\u200b0\\xad\\ufeffa\\x7f'","split":["\ufeff","\"x\ufeff\u200b0\u00ad\ufeffa\u007f"],"strip":"\ufeff\u2003\"x\ufeff\u200b0\u00ad\ufeffa\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff\\u2003\"x\\ufeff\\u200b0\\xad\\ufeffa\\x7f'"}, +{"text":"0xF","repr":"'0xF'","split":["0xF"],"strip":"0xF","uuid":null,"int_error":"invalid literal for int() with base 10: '0xF'"}, +{"text":"\u001c\uff11-\uff11=uuid:\"\u0000\u0085\u001f+","repr":"'\\x1c\uff11-\uff11=uuid:\"\\x00\\x85\\x1f+'","split":["\uff11-\uff11=uuid:\"\u0000","+"],"strip":"\uff11-\uff11=uuid:\"\u0000\u0085\u001f+","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1c\uff11-\uff11=uuid:\"\\x00\\x85\\x1f+'"}, +{"text":"0x","repr":"'0x'","split":["0x"],"strip":"0x","uuid":null,"int_error":"invalid literal for int() with base 10: '0x'"}, +{"text":"\u200b+\u06630x\ud83d\ude00\u00e9}","repr":"'\\u200b+\u06630x\ud83d\ude00\u00e9}'","split":["\u200b+\u06630x\ud83d\ude00\u00e9}"],"strip":"\u200b+\u06630x\ud83d\ude00\u00e9}","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u200b+\u06630x\ud83d\ude00\u00e9}'"}, +{"text":"","repr":"''","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: ''"}, +{"text":"7\"{","repr":"'7\"{'","split":["7\"{"],"strip":"7\"{","uuid":null,"int_error":"invalid literal for int() with base 10: '7\"{'"}, +{"text":"=1\u001c\u0000","repr":"'=1\\x1c\\x00'","split":["=1","\u0000"],"strip":"=1\u001c\u0000","uuid":null,"int_error":"invalid literal for int() with base 10: '=1\\x1c\\x00'"}, +{"text":"\ufeff\u000b","repr":"'\\ufeff\\x0b'","split":["\ufeff"],"strip":"\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff\\x0b'"}, +{"text":"770x","repr":"'770x'","split":["770x"],"strip":"770x","uuid":null,"int_error":"invalid literal for int() with base 10: '770x'"}, +{"text":"+\u00ad\u00ad\u03781\u00e9\u0000","repr":"'+\\xad\\xad\\u03781\u00e9\\x00'","split":["+\u00ad\u00ad\u03781\u00e9\u0000"],"strip":"+\u00ad\u00ad\u03781\u00e9\u0000","uuid":null,"int_error":"invalid literal for int() with base 10: '+\\xad\\xad\\u03781\u00e9\\x00'"}, +{"text":"9:\u00e9\u000b1\uff11+\uff11\ud835\udfce\n","repr":"'9:\u00e9\\x0b1\uff11+\uff11\ud835\udfce\\n'","split":["9:\u00e9","1\uff11+\uff11\ud835\udfce"],"strip":"9:\u00e9\u000b1\uff11+\uff11\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: '9:\u00e9\\x0b1\uff11+\uff11\ud835\udfce\\n'"}, +{"text":"\u200b-\u000b{\u2003\u0000\u30000+:","repr":"'\\u200b-\\x0b{\\u2003\\x00\\u30000+:'","split":["\u200b-","{","\u0000","0+:"],"strip":"\u200b-\u000b{\u2003\u0000\u30000+:","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u200b-\\x0b{\\u2003\\x00\\u30000+:'"}, +{"text":"x\u00a0\t\u0000\u200bx\ufeff","repr":"'x\\xa0\\t\\x00\\u200bx\\ufeff'","split":["x","\u0000\u200bx\ufeff"],"strip":"x\u00a0\t\u0000\u200bx\ufeff","uuid":null,"int_error":"invalid literal for int() with base 10: 'x\\xa0\\t\\x00\\u200bx\\ufeff'"}, +{"text":"\\\u00e9\\a\t\u00a0\udb40\udc01,urn:auuid:}","repr":"'\\\\\u00e9\\\\a\\t\\xa0\\U000e0001,urn:auuid:}'","split":["\\\u00e9\\a","\udb40\udc01,urn:auuid:}"],"strip":"\\\u00e9\\a\t\u00a0\udb40\udc01,urn:auuid:}","uuid":null,"int_error":"invalid literal for int() with base 10: '\\\\\u00e9\\\\a\\t\\xa0\\U000e0001,urn:auuid:}'"}, +{"text":"\\\uff11\uff11a7F\tX\u0378","repr":"'\\\\\uff11\uff11a7F\\tX\\u0378'","split":["\\\uff11\uff11a7F","X\u0378"],"strip":"\\\uff11\uff11a7F\tX\u0378","uuid":null,"int_error":"invalid literal for int() with base 10: '\\\\\uff11\uff11a7F\\tX\\u0378'"}, +{"text":"xF0","repr":"'xF0'","split":["xF0"],"strip":"xF0","uuid":null,"int_error":"invalid literal for int() with base 10: 'xF0'"}, +{"text":"\u3000uuid:\u2003\uff11\n{uuid:0x+","repr":"'\\u3000uuid:\\u2003\uff11\\n{uuid:0x+'","split":["uuid:","\uff11","{uuid:0x+"],"strip":"uuid:\u2003\uff11\n{uuid:0x+","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000uuid:\\u2003\uff11\\n{uuid:0x+'"}, +{"text":"\u0663\uff11\u000b\u00009urn:\u0085\u00ad","repr":"'\u0663\uff11\\x0b\\x009urn:\\x85\\xad'","split":["\u0663\uff11","\u00009urn:","\u00ad"],"strip":"\u0663\uff11\u000b\u00009urn:\u0085\u00ad","uuid":null,"int_error":"invalid literal for int() with base 10: '\u0663\uff11\\x0b\\x009urn:\\x85\\xad'"}, +{"text":"7+","repr":"'7+'","split":["7+"],"strip":"7+","uuid":null,"int_error":"invalid literal for int() with base 10: '7+'"}, +{"text":"\ud83d\ude00\"\u007f\u001c\u001c0x\u0000\\\u00ad \uff11","repr":"'\ud83d\ude00\"\\x7f\\x1c\\x1c0x\\x00\\\\\\xad \uff11'","split":["\ud83d\ude00\"\u007f","0x\u0000\\\u00ad","\uff11"],"strip":"\ud83d\ude00\"\u007f\u001c\u001c0x\u0000\\\u00ad \uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '\ud83d\ude00\"\\x7f\\x1c\\x1c0x\\x00\\\\\\xad \uff11'"}, +{"text":"\u0378++\u200b+\ufeff}\u0085\"\udb40\udc01\uff11X","repr":"'\\u0378++\\u200b+\\ufeff}\\x85\"\\U000e0001\uff11X'","split":["\u0378++\u200b+\ufeff}","\"\udb40\udc01\uff11X"],"strip":"\u0378++\u200b+\ufeff}\u0085\"\udb40\udc01\uff11X","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u0378++\\u200b+\\ufeff}\\x85\"\\U000e0001\uff11X'"}, +{"text":"\u0663x\u3000\ud83d\ude00\u0000\u00ad{\u0378urn:{F9","repr":"'\u0663x\\u3000\ud83d\ude00\\x00\\xad{\\u0378urn:{F9'","split":["\u0663x","\ud83d\ude00\u0000\u00ad{\u0378urn:{F9"],"strip":"\u0663x\u3000\ud83d\ude00\u0000\u00ad{\u0378urn:{F9","uuid":null,"int_error":"invalid literal for int() with base 10: '\u0663x\\u3000\ud83d\ude00\\x00\\xad{\\u0378urn:{F9'"}, +{"text":"\u2003\udb40\udc01:\u0378'X","repr":"\"\\u2003\\U000e0001:\\u0378'X\"","split":["\udb40\udc01:\u0378'X"],"strip":"\udb40\udc01:\u0378'X","uuid":null,"int_error":"invalid literal for int() with base 10: \"\\u2003\\U000e0001:\\u0378'X\""}, +{"text":"\u007f_\u00e9","repr":"'\\x7f_\u00e9'","split":["\u007f_\u00e9"],"strip":"\u007f_\u00e9","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f_\u00e9'"}, +{"text":"\u00007\ufeff\ud835\udfce,\n\u00e9\u2028\u2003-\ud835\udfce\u000b","repr":"'\\x007\\ufeff\ud835\udfce,\\n\u00e9\\u2028\\u2003-\ud835\udfce\\x0b'","split":["\u00007\ufeff\ud835\udfce,","\u00e9","-\ud835\udfce"],"strip":"\u00007\ufeff\ud835\udfce,\n\u00e9\u2028\u2003-\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x007\\ufeff\ud835\udfce,\\n\u00e9\\u2028\\u2003-\ud835\udfce\\x0b'"}, +{"text":"\u2003_\u001f0x7","repr":"'\\u2003_\\x1f0x7'","split":["_","0x7"],"strip":"_\u001f0x7","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u2003_\\x1f0x7'"}, +{"text":"\u3000+\u00ad\ud83d\ude00\u0663\u00e9","repr":"'\\u3000+\\xad\ud83d\ude00\u0663\u00e9'","split":["+\u00ad\ud83d\ude00\u0663\u00e9"],"strip":"+\u00ad\ud83d\ude00\u0663\u00e9","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000+\\xad\ud83d\ude00\u0663\u00e9'"}, +{"text":"0xX\u000b{\u3000}x+X}","repr":"'0xX\\x0b{\\u3000}x+X}'","split":["0xX","{","}x+X}"],"strip":"0xX\u000b{\u3000}x+X}","uuid":null,"int_error":"invalid literal for int() with base 10: '0xX\\x0b{\\u3000}x+X}'"}, +{"text":"\u3000\u0000","repr":"'\\u3000\\x00'","split":["\u0000"],"strip":"\u0000","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\x00'"}, +{"text":"\u001f\t\u007furn:+\udb40\udc01\u001f\ufeff\uff117","repr":"'\\x1f\\t\\x7furn:+\\U000e0001\\x1f\\ufeff\uff117'","split":["\u007furn:+\udb40\udc01","\ufeff\uff117"],"strip":"\u007furn:+\udb40\udc01\u001f\ufeff\uff117","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f\\t\\x7furn:+\\U000e0001\\x1f\\ufeff\uff117'"}, +{"text":"-\ufeff\u00e9\u0085\n\u3000uuid:\u0663'9_urn:","repr":"\"-\\ufeff\u00e9\\x85\\n\\u3000uuid:\u0663'9_urn:\"","split":["-\ufeff\u00e9","uuid:\u0663'9_urn:"],"strip":"-\ufeff\u00e9\u0085\n\u3000uuid:\u0663'9_urn:","uuid":null,"int_error":"invalid literal for int() with base 10: \"-\\ufeff\u00e9\\x85\\n\\u3000uuid:\u0663'9_urn:\""}, +{"text":"0-\ufeff\u001c-_\"\udb40\udc01\u00e9\t,0x","repr":"'0-\\ufeff\\x1c-_\"\\U000e0001\u00e9\\t,0x'","split":["0-\ufeff","-_\"\udb40\udc01\u00e9",",0x"],"strip":"0-\ufeff\u001c-_\"\udb40\udc01\u00e9\t,0x","uuid":null,"int_error":"invalid literal for int() with base 10: '0-\\ufeff\\x1c-_\"\\U000e0001\u00e9\\t,0x'"}, +{"text":"a\u200b\u00ad\u000b\u2003urn:a","repr":"'a\\u200b\\xad\\x0b\\u2003urn:a'","split":["a\u200b\u00ad","urn:a"],"strip":"a\u200b\u00ad\u000b\u2003urn:a","uuid":null,"int_error":"invalid literal for int() with base 10: 'a\\u200b\\xad\\x0b\\u2003urn:a'"}, +{"text":"\u001c=\u007f","repr":"'\\x1c=\\x7f'","split":["=\u007f"],"strip":"=\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1c=\\x7f'"}, +{"text":"\n\u00a0","repr":"'\\n\\xa0'","split":[],"strip":"","uuid":null,"int_error":"invalid literal for int() with base 10: '\\n\\xa0'"}, +{"text":"F\u2003\udb40\udc01,\u2003\t\n\u2028\"","repr":"'F\\u2003\\U000e0001,\\u2003\\t\\n\\u2028\"'","split":["F","\udb40\udc01,","\""],"strip":"F\u2003\udb40\udc01,\u2003\t\n\u2028\"","uuid":null,"int_error":"invalid literal for int() with base 10: 'F\\u2003\\U000e0001,\\u2003\\t\\n\\u2028\"'"}, +{"text":"\u0663\\\uff11\u001f","repr":"'\u0663\\\\\uff11\\x1f'","split":["\u0663\\\uff11"],"strip":"\u0663\\\uff11","uuid":null,"int_error":"invalid literal for int() with base 10: '\u0663\\\\\uff11\\x1f'"}, +{"text":"\u3000\u00007_\u00a00x70\udb40\udc01","repr":"'\\u3000\\x007_\\xa00x70\\U000e0001'","split":["\u00007_","0x70\udb40\udc01"],"strip":"\u00007_\u00a00x70\udb40\udc01","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u3000\\x007_\\xa00x70\\U000e0001'"}, +{"text":"}","repr":"'}'","split":["}"],"strip":"}","uuid":null,"int_error":"invalid literal for int() with base 10: '}'"}, +{"text":"\u0378_x,: 0x9","repr":"'\\u0378_x,: 0x9'","split":["\u0378_x,:","0x9"],"strip":"\u0378_x,: 0x9","uuid":null,"int_error":"invalid literal for int() with base 10: '\\u0378_x,: 0x9'"}, +{"text":"12345678-1234-5678-1234-5678112345678","repr":"'12345678-1234-5678-1234-5678112345678'","split":["12345678-1234-5678-1234-5678112345678"],"strip":"12345678-1234-5678-1234-5678112345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-5678112345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-56712345678","repr":"'urn:uuid:12345678-1234-5678-1234-56712345678'","split":["urn:uuid:12345678-1234-5678-1234-56712345678"],"strip":"urn:uuid:12345678-1234-5678-1234-56712345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-56712345678'"}, +{"text":"{1234568-1234-5678-1234-567812345678}","repr":"'{1234568-1234-5678-1234-567812345678}'","split":["{1234568-1234-5678-1234-567812345678}"],"strip":"{1234568-1234-5678-1234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{1234568-1234-5678-1234-567812345678}'"}, +{"text":"12345678-12384-5678-1 34-57812345678","repr":"'12345678-12384-5678-1 34-57812345678'","split":["12345678-12384-5678-1","34-57812345678"],"strip":"12345678-12384-5678-1 34-57812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-12384-5678-1 34-57812345678'"}, +{"text":"{12345678-12x4-567\u00a08-1234-56781234568}","repr":"'{12345678-12x4-567\\xa08-1234-56781234568}'","split":["{12345678-12x4-567","8-1234-56781234568}"],"strip":"{12345678-12x4-567\u00a08-1234-56781234568}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-12x4-567\\xa08-1234-56781234568}'"}, +{"text":"{12345678-1234-5678\u001f-1234-5678123455678_","repr":"'{12345678-1234-5678\\x1f-1234-5678123455678_'","split":["{12345678-1234-5678","-1234-5678123455678_"],"strip":"{12345678-1234-5678\u001f-1234-5678123455678_","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678\\x1f-1234-5678123455678_'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678uuid:1234-5678-1234-567812345678","repr":"'12345678uuid:1234-5678-1234-567812345678'","split":["12345678uuid:1234-5678-1234-567812345678"],"strip":"12345678uuid:1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678uuid:1234-5678-1234-567812345678'"}, +{"text":"12345678\u001f-1234-5678-124-56812345678","repr":"'12345678\\x1f-1234-5678-124-56812345678'","split":["12345678","-1234-5678-124-56812345678"],"strip":"12345678\u001f-1234-5678-124-56812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678\\x1f-1234-5678-124-56812345678'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-56781234578","repr":"'urn:uuid:12345678-1234-5678-1234-56781234578'","split":["urn:uuid:12345678-1234-5678-1234-56781234578"],"strip":"urn:uuid:12345678-1234-5678-1234-56781234578","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-56781234578'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"urn:uuid:12345678-124-5678-1234-567812345678","repr":"'urn:uuid:12345678-124-5678-1234-567812345678'","split":["urn:uuid:12345678-124-5678-1234-567812345678"],"strip":"urn:uuid:12345678-124-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-124-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-2349-5678112345678","repr":"'12345678-1234-5678-2349-5678112345678'","split":["12345678-1234-5678-2349-5678112345678"],"strip":"12345678-1234-5678-2349-5678112345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-2349-5678112345678'"}, +{"text":"1234567812345678123456781345678","repr":"'1234567812345678123456781345678'","split":["1234567812345678123456781345678"],"strip":"1234567812345678123456781345678","uuid":null,"int":"1234567812345678123456781345678"}, +{"text":"7rn:uuid:12345678-12345678-1234-56781234B678","repr":"'7rn:uuid:12345678-12345678-1234-56781234B678'","split":["7rn:uuid:12345678-12345678-1234-56781234B678"],"strip":"7rn:uuid:12345678-12345678-1234-56781234B678","uuid":null,"int_error":"invalid literal for int() with base 10: '7rn:uuid:12345678-12345678-1234-56781234B678'"}, +{"text":"1234578123456781F34567812345678","repr":"'1234578123456781F34567812345678'","split":["1234578123456781F34567812345678"],"strip":"1234578123456781F34567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234578123456781F34567812345678'"}, +{"text":"1234567812345678123456781245678","repr":"'1234567812345678123456781245678'","split":["1234567812345678123456781245678"],"strip":"1234567812345678123456781245678","uuid":null,"int":"1234567812345678123456781245678"}, +{"text":"12345678-124-5678-1234-56781\u00ad2345678","repr":"'12345678-124-5678-1234-56781\\xad2345678'","split":["12345678-124-5678-1234-56781\u00ad2345678"],"strip":"12345678-124-5678-1234-56781\u00ad2345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-124-5678-1234-56781\\xad2345678'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1234-56781245678","repr":"'12345678-1234-5678-1234-56781245678'","split":["12345678-1234-5678-1234-56781245678"],"strip":"12345678-1234-5678-1234-56781245678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-56781245678'"}, +{"text":"1234567812456781\ud835\udfce34567812345678","repr":"'1234567812456781\ud835\udfce34567812345678'","split":["1234567812456781\ud835\udfce34567812345678"],"strip":"1234567812456781\ud835\udfce34567812345678","uuid":null,"int":"1234567812456781034567812345678"}, +{"text":"{12345678-1234-5\u2028678-1234-56781234}78}","repr":"'{12345678-1234-5\\u2028678-1234-56781234}78}'","split":["{12345678-1234-5","678-1234-56781234}78}"],"strip":"{12345678-1234-5\u2028678-1234-56781234}78}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5\\u2028678-1234-56781234}78}'"}, +{"text":"urn:uuid:12345698-1234-5678-1234-567812345678","repr":"'urn:uuid:12345698-1234-5678-1234-567812345678'","split":["urn:uuid:12345698-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345698-1234-5678-1234-567812345678","uuid":"12345698-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345698-1234-5678-1234-567812345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-56812345678","repr":"'urn:uuid:12345678-1234-5678-1234-56812345678'","split":["urn:uuid:12345678-1234-5678-1234-56812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-56812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-56812345678'"}, +{"text":"urn:uu3d:2345678-1234-5678B-1234-567812345678","repr":"'urn:uu3d:2345678-1234-5678B-1234-567812345678'","split":["urn:uu3d:2345678-1234-5678B-1234-567812345678"],"strip":"urn:uu3d:2345678-1234-5678B-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uu3d:2345678-1234-5678B-1234-567812345678'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1534-56782345678","repr":"'12345678-1234-5678-1534-56782345678'","split":["12345678-1234-5678-1534-56782345678"],"strip":"12345678-1234-5678-1534-56782345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1534-56782345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"12345678123456781234567812a45678","repr":"'12345678123456781234567812a45678'","split":["12345678123456781234567812a45678"],"strip":"12345678123456781234567812a45678","uuid":"12345678-1234-5678-1234-567812a45678","int_error":"invalid literal for int() with base 10: '12345678123456781234567812a45678'"}, +{"text":"123456781234567823456781234\u0378567\udb40\udc018","repr":"'123456781234567823456781234\\u0378567\\U000e00018'","split":["123456781234567823456781234\u0378567\udb40\udc018"],"strip":"123456781234567823456781234\u0378567\udb40\udc018","uuid":null,"int_error":"invalid literal for int() with base 10: '123456781234567823456781234\\u0378567\\U000e00018'"}, +{"text":"{1245678-1234-5678-1234-567812345678}","repr":"'{1245678-1234-5678-1234-567812345678}'","split":["{1245678-1234-5678-1234-567812345678}"],"strip":"{1245678-1234-5678-1234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{1245678-1234-5678-1234-567812345678}'"}, +{"text":"urn\\uuid:12345678-1234-5678\u20281234-567812=45678","repr":"'urn\\\\uuid:12345678-1234-5678\\u20281234-567812=45678'","split":["urn\\uuid:12345678-1234-5678","1234-567812=45678"],"strip":"urn\\uuid:12345678-1234-5678\u20281234-567812=45678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn\\\\uuid:12345678-1234-5678\\u20281234-567812=45678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"123456778-1234-5678-1234-567812345678","repr":"'123456778-1234-5678-1234-567812345678'","split":["123456778-1234-5678-1234-567812345678"],"strip":"123456778-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '123456778-1234-5678-1234-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"12345678\u00a01234-5678-1234-567812-345678","repr":"'12345678\\xa01234-5678-1234-567812-345678'","split":["12345678","1234-5678-1234-567812-345678"],"strip":"12345678\u00a01234-5678-1234-567812-345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678\\xa01234-5678-1234-567812-345678'"}, +{"text":"1234567812345678123456781234568","repr":"'1234567812345678123456781234568'","split":["1234567812345678123456781234568"],"strip":"1234567812345678123456781234568","uuid":null,"int":"1234567812345678123456781234568"}, +{"text":"123456781234567812567812345678","repr":"'123456781234567812567812345678'","split":["123456781234567812567812345678"],"strip":"123456781234567812567812345678","uuid":null,"int":"123456781234567812567812345678"}, +{"text":"x123456781234678123456781234678","repr":"'x123456781234678123456781234678'","split":["x123456781234678123456781234678"],"strip":"x123456781234678123456781234678","uuid":null,"int_error":"invalid literal for int() with base 10: 'x123456781234678123456781234678'"}, +{"text":"12345678-1234\u00a0567\ufeff8-123c4-567812345678","repr":"'12345678-1234\\xa0567\\ufeff8-123c4-567812345678'","split":["12345678-1234","567\ufeff8-123c4-567812345678"],"strip":"12345678-1234\u00a0567\ufeff8-123c4-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234\\xa0567\\ufeff8-123c4-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"123456\u066378\u00851234-568-1234-567812345678","repr":"'123456\u066378\\x851234-568-1234-567812345678'","split":["123456\u066378","1234-568-1234-567812345678"],"strip":"123456\u066378\u00851234-568-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '123456\u066378\\x851234-568-1234-567812345678'"}, +{"text":"urn:uuid\udb40\udc0113456788-1234-5678-1234-567812345678","repr":"'urn:uuid\\U000e000113456788-1234-5678-1234-567812345678'","split":["urn:uuid\udb40\udc0113456788-1234-5678-1234-567812345678"],"strip":"urn:uuid\udb40\udc0113456788-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid\\U000e000113456788-1234-5678-1234-567812345678'"}, +{"text":"123456778-1234-5678-1234-567812345678","repr":"'123456778-1234-5678-1234-567812345678'","split":["123456778-1234-5678-1234-567812345678"],"strip":"123456778-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '123456778-1234-5678-1234-567812345678'"}, +{"text":"12345678-123467-1234-567812345678","repr":"'12345678-123467-1234-567812345678'","split":["12345678-123467-1234-567812345678"],"strip":"12345678-123467-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-123467-1234-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"12345678-12 34-5678-1234-5678\ufeff2345678","repr":"'12345678-12 34-5678-1234-5678\\ufeff2345678'","split":["12345678-12","34-5678-1234-5678\ufeff2345678"],"strip":"12345678-12 34-5678-1234-5678\ufeff2345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-12 34-5678-1234-5678\\ufeff2345678'"}, +{"text":"1234568-12\ufeff34-5678-1234-5\ufeff67812345678","repr":"'1234568-12\\ufeff34-5678-1234-5\\ufeff67812345678'","split":["1234568-12\ufeff34-5678-1234-5\ufeff67812345678"],"strip":"1234568-12\ufeff34-5678-1234-5\ufeff67812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234568-12\\ufeff34-5678-1234-5\\ufeff67812345678'"}, +{"text":"123456781234567812345678123458","repr":"'123456781234567812345678123458'","split":["123456781234567812345678123458"],"strip":"123456781234567812345678123458","uuid":null,"int":"123456781234567812345678123458"}, +{"text":"{12345678-1234-5678-1234-56781236 45678}","repr":"'{12345678-1234-5678-1234-56781236 45678}'","split":["{12345678-1234-5678-1234-56781236","45678}"],"strip":"{12345678-1234-5678-1234-56781236 45678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-56781236 45678}'"}, +{"text":"{12345678-1234-5678-1234-56781234567'}","repr":"\"{12345678-1234-5678-1234-56781234567'}\"","split":["{12345678-1234-5678-1234-56781234567'}"],"strip":"{12345678-1234-5678-1234-56781234567'}","uuid":null,"int_error":"invalid literal for int() with base 10: \"{12345678-1234-5678-1234-56781234567'}\""}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"urn:uuid:1\u000045678-1234-5678-123uuid:-567812345678","repr":"'urn:uuid:1\\x0045678-1234-5678-123uuid:-567812345678'","split":["urn:uuid:1\u000045678-1234-5678-123uuid:-567812345678"],"strip":"urn:uuid:1\u000045678-1234-5678-123uuid:-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:1\\x0045678-1234-5678-123uuid:-567812345678'"}, +{"text":"1234567812345678123467812345678","repr":"'1234567812345678123467812345678'","split":["1234567812345678123467812345678"],"strip":"1234567812345678123467812345678","uuid":null,"int":"1234567812345678123467812345678"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"312345678-1234-5678-1234-567812345678}","repr":"'312345678-1234-5678-1234-567812345678}'","split":["312345678-1234-5678-1234-567812345678}"],"strip":"312345678-1234-5678-1234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '312345678-1234-5678-1234-567812345678}'"}, +{"text":"123456781234567812345678\u00a0234568","repr":"'123456781234567812345678\\xa0234568'","split":["123456781234567812345678","234568"],"strip":"123456781234567812345678\u00a0234568","uuid":null,"int_error":"invalid literal for int() with base 10: '123456781234567812345678\\xa0234568'"}, +{"text":"1235678-1234-5678-1234-567812345678","repr":"'1235678-1234-5678-1234-567812345678'","split":["1235678-1234-5678-1234-567812345678"],"strip":"1235678-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1235678-1234-5678-1234-567812345678'"}, +{"text":"12347678-1234-5678-2349-567812345678","repr":"'12347678-1234-5678-2349-567812345678'","split":["12347678-1234-5678-2349-567812345678"],"strip":"12347678-1234-5678-2349-567812345678","uuid":"12347678-1234-5678-2349-567812345678","int_error":"invalid literal for int() with base 10: '12347678-1234-5678-2349-567812345678'"}, +{"text":"12345678-1234-5678-1f34-567812345678","repr":"'12345678-1234-5678-1f34-567812345678'","split":["12345678-1234-5678-1f34-567812345678"],"strip":"12345678-1234-5678-1f34-567812345678","uuid":"12345678-1234-5678-1f34-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1f34-567812345678'"}, +{"text":"123456781234567812345678123456718","repr":"'123456781234567812345678123456718'","split":["123456781234567812345678123456718"],"strip":"123456781234567812345678123456718","uuid":null,"int":"123456781234567812345678123456718"}, +{"text":"{12345678-1uuid:34,-5678-1234-567812345678}","repr":"'{12345678-1uuid:34,-5678-1234-567812345678}'","split":["{12345678-1uuid:34,-5678-1234-567812345678}"],"strip":"{12345678-1uuid:34,-5678-1234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1uuid:34,-5678-1234-567812345678}'"}, +{"text":"{12345678-12,4-567 -1234-5678123456478}","repr":"'{12345678-12,4-567 -1234-5678123456478}'","split":["{12345678-12,4-567","-1234-5678123456478}"],"strip":"{12345678-12,4-567 -1234-5678123456478}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-12,4-567 -1234-5678123456478}'"}, +{"text":"urn:uuid:123f5678-1234-5678-1234-5678123{5678","repr":"'urn:uuid:123f5678-1234-5678-1234-5678123{5678'","split":["urn:uuid:123f5678-1234-5678-1234-5678123{5678"],"strip":"urn:uuid:123f5678-1234-5678-1234-5678123{5678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:123f5678-1234-5678-1234-5678123{5678'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"123456781234567812456781234567\u00e98","repr":"'123456781234567812456781234567\u00e98'","split":["123456781234567812456781234567\u00e98"],"strip":"123456781234567812456781234567\u00e98","uuid":null,"int_error":"invalid literal for int() with base 10: '123456781234567812456781234567\u00e98'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"12-345678-1234-5678d1234-567812345678","repr":"'12-345678-1234-5678d1234-567812345678'","split":["12-345678-1234-5678d1234-567812345678"],"strip":"12-345678-1234-5678d1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12-345678-1234-5678d1234-567812345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"12345678-1234-5678-\ud83d\ude00234-567812345678","repr":"'12345678-1234-5678-\ud83d\ude00234-567812345678'","split":["12345678-1234-5678-\ud83d\ude00234-567812345678"],"strip":"12345678-1234-5678-\ud83d\ude00234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-\ud83d\ude00234-567812345678'"}, +{"text":"1234568-1234-5678-1234567812345678","repr":"'1234568-1234-5678-1234567812345678'","split":["1234568-1234-5678-1234567812345678"],"strip":"1234568-1234-5678-1234567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234568-1234-5678-1234567812345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1234-\uff11567812345678","repr":"'12345678-1234-5678-1234-\uff11567812345678'","split":["12345678-1234-5678-1234-\uff11567812345678"],"strip":"12345678-1234-5678-1234-\uff11567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-\uff11567812345678'"}, +{"text":"1234B678-1234-5678-1234-5627812345678","repr":"'1234B678-1234-5678-1234-5627812345678'","split":["1234B678-1234-5678-1234-5627812345678"],"strip":"1234B678-1234-5678-1234-5627812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234B678-1234-5678-1234-5627812345678'"}, +{"text":"urn:uuid:12345678-12-4-\uff11678-1234-567812345678","repr":"'urn:uuid:12345678-12-4-\uff11678-1234-567812345678'","split":["urn:uuid:12345678-12-4-\uff11678-1234-567812345678"],"strip":"urn:uuid:12345678-12-4-\uff11678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-12-4-\uff11678-1234-567812345678'"}, +{"text":"12345678-1234-678-123\u007f4-567812345678","repr":"'12345678-1234-678-123\\x7f4-567812345678'","split":["12345678-1234-678-123\u007f4-567812345678"],"strip":"12345678-1234-678-123\u007f4-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-678-123\\x7f4-567812345678'"}, +{"text":"{12345678-1234-5678-1\u001c234-567812345\u2003678}","repr":"'{12345678-1234-5678-1\\x1c234-567812345\\u2003678}'","split":["{12345678-1234-5678-1","234-567812345","678}"],"strip":"{12345678-1234-5678-1\u001c234-567812345\u2003678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1\\x1c234-567812345\\u2003678}'"}, +{"text":"urn:uuid:1234567\\8-1234-56738-1234-5678123456\"8","repr":"'urn:uuid:1234567\\\\8-1234-56738-1234-5678123456\"8'","split":["urn:uuid:1234567\\8-1234-56738-1234-5678123456\"8"],"strip":"urn:uuid:1234567\\8-1234-56738-1234-5678123456\"8","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:1234567\\\\8-1234-56738-1234-5678123456\"8'"}, +{"text":"12345678-1234-5678-123}-567812345678","repr":"'12345678-1234-5678-123}-567812345678'","split":["12345678-1234-5678-123}-567812345678"],"strip":"12345678-1234-5678-123}-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-123}-567812345678'"}, +{"text":"12345678-12\u00a034-5678-1234-567812345678","repr":"'12345678-12\\xa034-5678-1234-567812345678'","split":["12345678-12","34-5678-1234-567812345678"],"strip":"12345678-12\u00a034-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-12\\xa034-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1234-567812345\u0085678","repr":"'12345678-1234-5678-1234-567812345\\x85678'","split":["12345678-1234-5678-1234-567812345","678"],"strip":"12345678-1234-5678-1234-567812345\u0085678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345\\x85678'"}, +{"text":"urn:uuid:12345678-12\u001f4-5678-1234-567812345678","repr":"'urn:uuid:12345678-12\\x1f4-5678-1234-567812345678'","split":["urn:uuid:12345678-12","4-5678-1234-567812345678"],"strip":"urn:uuid:12345678-12\u001f4-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-12\\x1f4-5678-1234-567812345678'"}, +{"text":"{12345678-1234-5678-\u3000123-56781-345678}","repr":"'{12345678-1234-5678-\\u3000123-56781-345678}'","split":["{12345678-1234-5678-","123-56781-345678}"],"strip":"{12345678-1234-5678-\u3000123-56781-345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-\\u3000123-56781-345678}'"}, +{"text":"{12345678-1234-5678-1b234-567812345678}","repr":"'{12345678-1234-5678-1b234-567812345678}'","split":["{12345678-1234-5678-1b234-567812345678}"],"strip":"{12345678-1234-5678-1b234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1b234-567812345678}'"}, +{"text":"urn:uuid:2345678-1234-5678-1234-567\t12345678","repr":"'urn:uuid:2345678-1234-5678-1234-567\\t12345678'","split":["urn:uuid:2345678-1234-5678-1234-567","12345678"],"strip":"urn:uuid:2345678-1234-5678-1234-567\t12345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:2345678-1234-5678-1234-567\\t12345678'"}, +{"text":"urn:uuid:12345678-\u00a0234-5678-1234-567812345678","repr":"'urn:uuid:12345678-\\xa0234-5678-1234-567812345678'","split":["urn:uuid:12345678-","234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-\u00a0234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-\\xa0234-5678-1234-567812345678'"}, +{"text":"12345678-134-5678-1234-5678123456\u000b7","repr":"'12345678-134-5678-1234-5678123456\\x0b7'","split":["12345678-134-5678-1234-5678123456","7"],"strip":"12345678-134-5678-1234-5678123456\u000b7","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-134-5678-1234-5678123456\\x0b7'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"{1245678-1234-5678-1234-567812345678}","repr":"'{1245678-1234-5678-1234-567812345678}'","split":["{1245678-1234-5678-1234-567812345678}"],"strip":"{1245678-1234-5678-1234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{1245678-1234-5678-1234-567812345678}'"}, +{"text":"123456781234-5678-1234-5678123\uff115678","repr":"'123456781234-5678-1234-5678123\uff115678'","split":["123456781234-5678-1234-5678123\uff115678"],"strip":"123456781234-5678-1234-5678123\uff115678","uuid":"12345678-1234-5678-1234-567812315678","int_error":"invalid literal for int() with base 10: '123456781234-5678-1234-5678123\uff115678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"12345678\u0000-1234-5678-1234-567812345678","repr":"'12345678\\x00-1234-5678-1234-567812345678'","split":["12345678\u0000-1234-5678-1234-567812345678"],"strip":"12345678\u0000-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678\\x00-1234-5678-1234-567812345678'"}, +{"text":"{1345678-1234-5678-1234-56782345678-","repr":"'{1345678-1234-5678-1234-56782345678-'","split":["{1345678-1234-5678-1234-56782345678-"],"strip":"{1345678-1234-5678-1234-56782345678-","uuid":null,"int_error":"invalid literal for int() with base 10: '{1345678-1234-5678-1234-56782345678-'"}, +{"text":"12345678-1234-5678-1234-56781245678","repr":"'12345678-1234-5678-1234-56781245678'","split":["12345678-1234-5678-1234-56781245678"],"strip":"12345678-1234-5678-1234-56781245678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-56781245678'"}, +{"text":"12345678-1234-567-1234-56812345678","repr":"'12345678-1234-567-1234-56812345678'","split":["12345678-1234-567-1234-56812345678"],"strip":"12345678-1234-567-1234-56812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-567-1234-56812345678'"}, +{"text":"12345678_-1234-5678-1234-56781\uff11345678","repr":"'12345678_-1234-5678-1234-56781\uff11345678'","split":["12345678_-1234-5678-1234-56781\uff11345678"],"strip":"12345678_-1234-5678-1234-56781\uff11345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678_-1234-5678-1234-56781\uff11345678'"}, +{"text":"1214578-1234-5678-12a4-567812345678","repr":"'1214578-1234-5678-12a4-567812345678'","split":["1214578-1234-5678-12a4-567812345678"],"strip":"1214578-1234-5678-12a4-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1214578-1234-5678-12a4-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"urnuuid:12345678-1234-567A8-1234-567812345678","repr":"'urnuuid:12345678-1234-567A8-1234-567812345678'","split":["urnuuid:12345678-1234-567A8-1234-567812345678"],"strip":"urnuuid:12345678-1234-567A8-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urnuuid:12345678-1234-567A8-1234-567812345678'"}, +{"text":"1234 5678e123456781234567812345678","repr":"'1234 5678e123456781234567812345678'","split":["1234","5678e123456781234567812345678"],"strip":"1234 5678e123456781234567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234 5678e123456781234567812345678'"}, +{"text":"1\u0663345678-1234-b678-1734-567812345678","repr":"'1\u0663345678-1234-b678-1734-567812345678'","split":["1\u0663345678-1234-b678-1734-567812345678"],"strip":"1\u0663345678-1234-b678-1734-567812345678","uuid":"13345678-1234-b678-1734-567812345678","int_error":"invalid literal for int() with base 10: '1\u0663345678-1234-b678-1734-567812345678'"}, +{"text":"123456781234567812346781345678","repr":"'123456781234567812346781345678'","split":["123456781234567812346781345678"],"strip":"123456781234567812346781345678","uuid":null,"int":"123456781234567812346781345678"}, +{"text":"{12345678-1234-56768-1234-56781234E56d78}","repr":"'{12345678-1234-56768-1234-56781234E56d78}'","split":["{12345678-1234-56768-1234-56781234E56d78}"],"strip":"{12345678-1234-56768-1234-56781234E56d78}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-56768-1234-56781234E56d78}'"}, +{"text":"{12345678-1234-5678-1234-567812f45678}","repr":"'{12345678-1234-5678-1234-567812f45678}'","split":["{12345678-1234-5678-1234-567812f45678}"],"strip":"{12345678-1234-5678-1234-567812f45678}","uuid":"12345678-1234-5678-1234-567812f45678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812f45678}'"}, +{"text":"urn}:uuid:C2345678-1234-5678-1234-5670812345678","repr":"'urn}:uuid:C2345678-1234-5678-1234-5670812345678'","split":["urn}:uuid:C2345678-1234-5678-1234-5670812345678"],"strip":"urn}:uuid:C2345678-1234-5678-1234-5670812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn}:uuid:C2345678-1234-5678-1234-5670812345678'"}, +{"text":"{123\u007f5678-123-5678-1_234-567812345678}","repr":"'{123\\x7f5678-123-5678-1_234-567812345678}'","split":["{123\u007f5678-123-5678-1_234-567812345678}"],"strip":"{123\u007f5678-123-5678-1_234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{123\\x7f5678-123-5678-1_234-567812345678}'"}, +{"text":"{12345678-234-5678-124-567812345678}","repr":"'{12345678-234-5678-124-567812345678}'","split":["{12345678-234-5678-124-567812345678}"],"strip":"{12345678-234-5678-124-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-234-5678-124-567812345678}'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"1234678-1234-5678-1234-567812345678","repr":"'1234678-1234-5678-1234-567812345678'","split":["1234678-1234-5678-1234-567812345678"],"strip":"1234678-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234678-1234-5678-1234-567812345678'"}, +{"text":"1234567812X4567812345678162345678","repr":"'1234567812X4567812345678162345678'","split":["1234567812X4567812345678162345678"],"strip":"1234567812X4567812345678162345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234567812X4567812345678162345678'"}, +{"text":"1234578-1234-5678-123{4-\u000067812345678","repr":"'1234578-1234-5678-123{4-\\x0067812345678'","split":["1234578-1234-5678-123{4-\u000067812345678"],"strip":"1234578-1234-5678-123{4-\u000067812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234578-1234-5678-123{4-\\x0067812345678'"}, +{"text":"13\u00ad5678123456781234567812345678","repr":"'13\\xad5678123456781234567812345678'","split":["13\u00ad5678123456781234567812345678"],"strip":"13\u00ad5678123456781234567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '13\\xad5678123456781234567812345678'"}, +{"text":"{12345678-1234-5678-1234-56782345678}","repr":"'{12345678-1234-5678-1234-56782345678}'","split":["{12345678-1234-5678-1234-56782345678}"],"strip":"{12345678-1234-5678-1234-56782345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-56782345678}'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"urn:uuid:123456789-1234-e678-1234-567812345678","repr":"'urn:uuid:123456789-1234-e678-1234-567812345678'","split":["urn:uuid:123456789-1234-e678-1234-567812345678"],"strip":"urn:uuid:123456789-1234-e678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:123456789-1234-e678-1234-567812345678'"}, +{"text":"urn:uu3id:1234e678-123D-5678-1234-567812345678","repr":"'urn:uu3id:1234e678-123D-5678-1234-567812345678'","split":["urn:uu3id:1234e678-123D-5678-1234-567812345678"],"strip":"urn:uu3id:1234e678-123D-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uu3id:1234e678-123D-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-123-567812345678","repr":"'12345678-1234-5678-123-567812345678'","split":["12345678-1234-5678-123-567812345678"],"strip":"12345678-1234-5678-123-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-123-567812345678'"}, +{"text":"12345678-B234-5678-1234-567812345678","repr":"'12345678-B234-5678-1234-567812345678'","split":["12345678-B234-5678-1234-567812345678"],"strip":"12345678-B234-5678-1234-567812345678","uuid":"12345678-b234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-B234-5678-1234-567812345678'"}, +{"text":"{123456c78-1234-5678-1234-567812345678}","repr":"'{123456c78-1234-5678-1234-567812345678}'","split":["{123456c78-1234-5678-1234-567812345678}"],"strip":"{123456c78-1234-5678-1234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{123456c78-1234-5678-1234-567812345678}'"}, +{"text":"12345678-134-5678-1234-5'7812345678","repr":"\"12345678-134-5678-1234-5'7812345678\"","split":["12345678-134-5678-1234-5'7812345678"],"strip":"12345678-134-5678-1234-5'7812345678","uuid":null,"int_error":"invalid literal for int() with base 10: \"12345678-134-5678-1234-5'7812345678\""}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"urn:uuid:12345678-1234-678-1234-06781245678","repr":"'urn:uuid:12345678-1234-678-1234-06781245678'","split":["urn:uuid:12345678-1234-678-1234-06781245678"],"strip":"urn:uuid:12345678-1234-678-1234-06781245678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-678-1234-06781245678'"}, +{"text":"12345678-1204-5678-1234-567812345678","repr":"'12345678-1204-5678-1234-567812345678'","split":["12345678-1204-5678-1234-567812345678"],"strip":"12345678-1204-5678-1234-567812345678","uuid":"12345678-1204-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1204-5678-1234-567812345678'"}, +{"text":"c2345678-1234-5678-1234-567812345678","repr":"'c2345678-1234-5678-1234-567812345678'","split":["c2345678-1234-5678-1234-567812345678"],"strip":"c2345678-1234-5678-1234-567812345678","uuid":"c2345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'c2345678-1234-5678-1234-567812345678'"}, +{"text":"{12345678-1234,-5678-1234-567812345678}","repr":"'{12345678-1234,-5678-1234-567812345678}'","split":["{12345678-1234,-5678-1234-567812345678}"],"strip":"{12345678-1234,-5678-1234-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234,-5678-1234-567812345678}'"}, +{"text":"2345678-1234-5678-1234-5681234578","repr":"'2345678-1234-5678-1234-5681234578'","split":["2345678-1234-5678-1234-5681234578"],"strip":"2345678-1234-5678-1234-5681234578","uuid":null,"int_error":"invalid literal for int() with base 10: '2345678-1234-5678-1234-5681234578'"}, +{"text":"1234567871234-5678-1234-567812345678","repr":"'1234567871234-5678-1234-567812345678'","split":["1234567871234-5678-1234-567812345678"],"strip":"1234567871234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234567871234-5678-1234-567812345678'"}, +{"text":"{12345678-1234-5668-1234-567812345678","repr":"'{12345678-1234-5668-1234-567812345678'","split":["{12345678-1234-5668-1234-567812345678"],"strip":"{12345678-1234-5668-1234-567812345678","uuid":"12345678-1234-5668-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5668-1234-567812345678'"}, +{"text":"12845678-1234-5678-1234-5\u06637812345678","repr":"'12845678-1234-5678-1234-5\u06637812345678'","split":["12845678-1234-5678-1234-5\u06637812345678"],"strip":"12845678-1234-5678-1234-5\u06637812345678","uuid":"12845678-1234-5678-1234-537812345678","int_error":"invalid literal for int() with base 10: '12845678-1234-5678-1234-5\u06637812345678'"}, +{"text":"1245678-1234-5678-123456781234567","repr":"'1245678-1234-5678-123456781234567'","split":["1245678-1234-5678-123456781234567"],"strip":"1245678-1234-5678-123456781234567","uuid":null,"int_error":"invalid literal for int() with base 10: '1245678-1234-5678-123456781234567'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"{12345678-124-5678-1234-5678123456{8}","repr":"'{12345678-124-5678-1234-5678123456{8}'","split":["{12345678-124-5678-1234-5678123456{8}"],"strip":"{12345678-124-5678-1234-5678123456{8}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-124-5678-1234-5678123456{8}'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"12345678-12\u200b4-5678-1234-567812345678","repr":"'12345678-12\\u200b4-5678-1234-567812345678'","split":["12345678-12\u200b4-5678-1234-567812345678"],"strip":"12345678-12\u200b4-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-12\\u200b4-5678-1234-567812345678'"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"12345678-1\u202834-5678-1234-567812345678","repr":"'12345678-1\\u202834-5678-1234-567812345678'","split":["12345678-1","34-5678-1234-567812345678"],"strip":"12345678-1\u202834-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1\\u202834-5678-1234-567812345678'"}, +{"text":"12345678a234567812345678B12345678","repr":"'12345678a234567812345678B12345678'","split":["12345678a234567812345678B12345678"],"strip":"12345678a234567812345678B12345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678a234567812345678B12345678'"}, +{"text":"1234578-1234-5678-91234-56X812345678","repr":"'1234578-1234-5678-91234-56X812345678'","split":["1234578-1234-5678-91234-56X812345678"],"strip":"1234578-1234-5678-91234-56X812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234578-1234-5678-91234-56X812345678'"}, +{"text":"urn:uuid:12\u001cC45678-1234-5678-1234-57812345678","repr":"'urn:uuid:12\\x1cC45678-1234-5678-1234-57812345678'","split":["urn:uuid:12","C45678-1234-5678-1234-57812345678"],"strip":"urn:uuid:12\u001cC45678-1234-5678-1234-57812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12\\x1cC45678-1234-5678-1234-57812345678'"}, +{"text":"urn:uuid:12345,678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345,678-1234-5678-1234-567812345678'","split":["urn:uuid:12345,678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345,678-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345,678-1234-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1234-56781345678}","repr":"'12345678-1234-5678-1234-56781345678}'","split":["12345678-1234-5678-1234-56781345678}"],"strip":"12345678-1234-5678-1234-56781345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-56781345678}'"}, +{"text":"{123456\u300078-1234-5678-123D-x67812345678}","repr":"'{123456\\u300078-1234-5678-123D-x67812345678}'","split":["{123456","78-1234-5678-123D-x67812345678}"],"strip":"{123456\u300078-1234-5678-123D-x67812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{123456\\u300078-1234-5678-123D-x67812345678}'"}, +{"text":"12345678-1234-508-1234-567812345678","repr":"'12345678-1234-508-1234-567812345678'","split":["12345678-1234-508-1234-567812345678"],"strip":"12345678-1234-508-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-508-1234-567812345678'"}, +{"text":"1\u2003a2345678-1234-5678-1234-567812345678","repr":"'1\\u2003a2345678-1234-5678-1234-567812345678'","split":["1","a2345678-1234-5678-1234-567812345678"],"strip":"1\u2003a2345678-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1\\u2003a2345678-1234-5678-1234-567812345678'"}, +{"text":"1234567812345c67812345678123245678","repr":"'1234567812345c67812345678123245678'","split":["1234567812345c67812345678123245678"],"strip":"1234567812345c67812345678123245678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234567812345c67812345678123245678'"}, +{"text":"urn:uuid:12345678-1234-567\n-1234-567812345678","repr":"'urn:uuid:12345678-1234-567\\n-1234-567812345678'","split":["urn:uuid:12345678-1234-567","-1234-567812345678"],"strip":"urn:uuid:12345678-1234-567\n-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-567\\n-1234-567812345678'"}, +{"text":"1834E78123456781234567812345678","repr":"'1834E78123456781234567812345678'","split":["1834E78123456781234567812345678"],"strip":"1834E78123456781234567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1834E78123456781234567812345678'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"1234567-1234-\u20035678-123-567812345678","repr":"'1234567-1234-\\u20035678-123-567812345678'","split":["1234567-1234-","5678-123-567812345678"],"strip":"1234567-1234-\u20035678-123-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1234567-1234-\\u20035678-123-567812345678'"}, +{"text":"123456781\"23456d8123456781235678","repr":"'123456781\"23456d8123456781235678'","split":["123456781\"23456d8123456781235678"],"strip":"123456781\"23456d8123456781235678","uuid":null,"int_error":"invalid literal for int() with base 10: '123456781\"23456d8123456781235678'"}, +{"text":"12345678-1234-=678-1234-5673812345678","repr":"'12345678-1234-=678-1234-5673812345678'","split":["12345678-1234-=678-1234-5673812345678"],"strip":"12345678-1234-=678-1234-5673812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-=678-1234-5673812345678'"}, +{"text":"123568123\u300056781234567812345678","repr":"'123568123\\u300056781234567812345678'","split":["123568123","56781234567812345678"],"strip":"123568123\u300056781234567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '123568123\\u300056781234567812345678'"}, +{"text":"12345678-12314-56785-1234-567812345678","repr":"'12345678-12314-56785-1234-567812345678'","split":["12345678-12314-56785-1234-567812345678"],"strip":"12345678-12314-56785-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-12314-56785-1234-567812345678'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"12345678-1234-5678-1234567812345678","repr":"'12345678-1234-5678-1234567812345678'","split":["12345678-1234-5678-1234567812345678"],"strip":"12345678-1234-5678-1234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234567812345678'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678123456781234567812345678","repr":"'12345678123456781234567812345678'","split":["12345678123456781234567812345678"],"strip":"12345678123456781234567812345678","uuid":"12345678-1234-5678-1234-567812345678","int":"12345678123456781234567812345678"}, +{"text":"{12345678-1234-5678-1234-567812345678}","repr":"'{12345678-1234-5678-1234-567812345678}'","split":["{12345678-1234-5678-1234-567812345678}"],"strip":"{12345678-1234-5678-1234-567812345678}","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-567812345678}'"}, +{"text":"1234567812345678123567812345678","repr":"'1234567812345678123567812345678'","split":["1234567812345678123567812345678"],"strip":"1234567812345678123567812345678","uuid":null,"int":"1234567812345678123567812345678"}, +{"text":"{12345678-1234-5674-1234-567812345a78}","repr":"'{12345678-1234-5674-1234-567812345a78}'","split":["{12345678-1234-5674-1234-567812345a78}"],"strip":"{12345678-1234-5674-1234-567812345a78}","uuid":"12345678-1234-5674-1234-567812345a78","int_error":"invalid literal for int() with base 10: '{12345678-1234-5674-1234-567812345a78}'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"1234567812346781234567812345678","repr":"'1234567812346781234567812345678'","split":["1234567812346781234567812345678"],"strip":"1234567812346781234567812345678","uuid":null,"int":"1234567812346781234567812345678"}, +{"text":"1245678-1234-5678-1234-567812345678","repr":"'1245678-1234-5678-1234-567812345678'","split":["1245678-1234-5678-1234-567812345678"],"strip":"1245678-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '1245678-1234-5678-1234-567812345678'"}, +{"text":"urn:uuid:1234578-1234-5678-1234-567812345678","repr":"'urn:uuid:1234578-1234-5678-1234-567812345678'","split":["urn:uuid:1234578-1234-5678-1234-567812345678"],"strip":"urn:uuid:1234578-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:1234578-1234-5678-1234-567812345678'"}, +{"text":"urn:uuid\uff11:123459B78-1234-5678-1234-567812345678","repr":"'urn:uuid\uff11:123459B78-1234-5678-1234-567812345678'","split":["urn:uuid\uff11:123459B78-1234-5678-1234-567812345678"],"strip":"urn:uuid\uff11:123459B78-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid\uff11:123459B78-1234-5678-1234-567812345678'"}, +{"text":"12345678-\ud83d\ude00\\234-5678-1234-567812345678","repr":"'12345678-\ud83d\ude00\\\\234-5678-1234-567812345678'","split":["12345678-\ud83d\ude00\\234-5678-1234-567812345678"],"strip":"12345678-\ud83d\ude00\\234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-\ud83d\ude00\\\\234-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1234-56781234567","repr":"'12345678-1234-5678-1234-56781234567'","split":["12345678-1234-5678-1234-56781234567"],"strip":"12345678-1234-5678-1234-56781234567","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-56781234567'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"12345678-12\\4-5678-1234-5675812345678","repr":"'12345678-12\\\\4-5678-1234-5675812345678'","split":["12345678-12\\4-5678-1234-5675812345678"],"strip":"12345678-12\\4-5678-1234-5675812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-12\\\\4-5678-1234-5675812345678'"}, +{"text":"12345678-1234-5678-1234-56782345D78","repr":"'12345678-1234-5678-1234-56782345D78'","split":["12345678-1234-5678-1234-56782345D78"],"strip":"12345678-1234-5678-1234-56782345D78","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-56782345D78'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"urn:uuid:12345678-1234-56781234-567812345678","repr":"'urn:uuid:12345678-1234-56781234-567812345678'","split":["urn:uuid:12345678-1234-56781234-567812345678"],"strip":"urn:uuid:12345678-1234-56781234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-56781234-567812345678'"}, +{"text":"urn:uuid:123{45678-1234-5678-234-'567812345678","repr":"\"urn:uuid:123{45678-1234-5678-234-'567812345678\"","split":["urn:uuid:123{45678-1234-5678-234-'567812345678"],"strip":"urn:uuid:123{45678-1234-5678-234-'567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: \"urn:uuid:123{45678-1234-5678-234-'567812345678\""}, +{"text":"12\u2028\\45678-1234-5678-1234-567812\u2028345678","repr":"'12\\u2028\\\\45678-1234-5678-1234-567812\\u2028345678'","split":["12","\\45678-1234-5678-1234-567812","345678"],"strip":"12\u2028\\45678-1234-5678-1234-567812\u2028345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12\\u2028\\\\45678-1234-5678-1234-567812\\u2028345678'"}, +{"text":"12345\\678123406781234567812345678","repr":"'12345\\\\678123406781234567812345678'","split":["12345\\678123406781234567812345678"],"strip":"12345\\678123406781234567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345\\\\678123406781234567812345678'"}, +{"text":"{123456c78-1234-568-1234-567812345678","repr":"'{123456c78-1234-568-1234-567812345678'","split":["{123456c78-1234-568-1234-567812345678"],"strip":"{123456c78-1234-568-1234-567812345678","uuid":"123456c7-8123-4568-1234-567812345678","int_error":"invalid literal for int() with base 10: '{123456c78-1234-568-1234-567812345678'"}, +{"text":"urn:uui:12345678-1234-5678-1234a56781234678","repr":"'urn:uui:12345678-1234-5678-1234a56781234678'","split":["urn:uui:12345678-1234-5678-1234a56781234678"],"strip":"urn:uui:12345678-1234-5678-1234a56781234678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uui:12345678-1234-5678-1234a56781234678'"}, +{"text":"12345678-1234-5678-1234-56781234678","repr":"'12345678-1234-5678-1234-56781234678'","split":["12345678-1234-5678-1234-56781234678"],"strip":"12345678-1234-5678-1234-56781234678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-56781234678'"}, +{"text":"urn:uuid:12345678-234-5678-234-5678123\u20035678","repr":"'urn:uuid:12345678-234-5678-234-5678123\\u20035678'","split":["urn:uuid:12345678-234-5678-234-5678123","5678"],"strip":"urn:uuid:12345678-234-5678-234-5678123\u20035678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-234-5678-234-5678123\\u20035678'"}, +{"text":"12345678-1234-568-1234-567812345678","repr":"'12345678-1234-568-1234-567812345678'","split":["12345678-1234-568-1234-567812345678"],"strip":"12345678-1234-568-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-568-1234-567812345678'"}, +{"text":"urn:uui\ud83d\ude00d:1234578-1234-5678-1234-567812345678","repr":"'urn:uui\ud83d\ude00d:1234578-1234-5678-1234-567812345678'","split":["urn:uui\ud83d\ude00d:1234578-1234-5678-1234-567812345678"],"strip":"urn:uui\ud83d\ude00d:1234578-1234-5678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uui\ud83d\ude00d:1234578-1234-5678-1234-567812345678'"}, +{"text":"12345678-1234-5678-1234-567812345678","repr":"'12345678-1234-5678-1234-567812345678'","split":["12345678-1234-5678-1234-567812345678"],"strip":"12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: '12345678-1234-5678-1234-567812345678'"}, +{"text":"12345x78-1234-5678-1234-56782345678","repr":"'12345x78-1234-5678-1234-56782345678'","split":["12345x78-1234-5678-1234-56782345678"],"strip":"12345x78-1234-5678-1234-56782345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345x78-1234-5678-1234-56782345678'"}, +{"text":"{12345678-1234-5678-1234-56=8:1234\ud83d\ude005678}","repr":"'{12345678-1234-5678-1234-56=8:1234\ud83d\ude005678}'","split":["{12345678-1234-5678-1234-56=8:1234\ud83d\ude005678}"],"strip":"{12345678-1234-5678-1234-56=8:1234\ud83d\ude005678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-56=8:1234\ud83d\ude005678}'"}, +{"text":"12345678-1234-5678-123\u00854-57812345678","repr":"'12345678-1234-5678-123\\x854-57812345678'","split":["12345678-1234-5678-123","4-57812345678"],"strip":"12345678-1234-5678-123\u00854-57812345678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-1234-5678-123\\x854-57812345678'"}, +{"text":"12345678-123\u000b4-5678-1234-56781245678","repr":"'12345678-123\\x0b4-5678-1234-56781245678'","split":["12345678-123","4-5678-1234-56781245678"],"strip":"12345678-123\u000b4-5678-1234-56781245678","uuid":null,"int_error":"invalid literal for int() with base 10: '12345678-123\\x0b4-5678-1234-56781245678'"}, +{"text":"urn:uuid:12345678-1234-56478-1934-56712345678","repr":"'urn:uuid:12345678-1234-56478-1934-56712345678'","split":["urn:uuid:12345678-1234-56478-1934-56712345678"],"strip":"urn:uuid:12345678-1234-56478-1934-56712345678","uuid":"12345678-1234-5647-8193-456712345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-56478-1934-56712345678'"}, +{"text":"urn:uuid:1c234678-1234-\ud83d\ude005678-1234-567812345678","repr":"'urn:uuid:1c234678-1234-\ud83d\ude005678-1234-567812345678'","split":["urn:uuid:1c234678-1234-\ud83d\ude005678-1234-567812345678"],"strip":"urn:uuid:1c234678-1234-\ud83d\ude005678-1234-567812345678","uuid":null,"int_error":"invalid literal for int() with base 10: 'urn:uuid:1c234678-1234-\ud83d\ude005678-1234-567812345678'"}, +{"text":"{12345678-1234-5678-1234-5678+12345678}","repr":"'{12345678-1234-5678-1234-5678+12345678}'","split":["{12345678-1234-5678-1234-5678+12345678}"],"strip":"{12345678-1234-5678-1234-5678+12345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5678-1234-5678+12345678}'"}, +{"text":"urn:uuid:12345678-1234-5678-1234-567812345678","repr":"'urn:uuid:12345678-1234-5678-1234-567812345678'","split":["urn:uuid:12345678-1234-5678-1234-567812345678"],"strip":"urn:uuid:12345678-1234-5678-1234-567812345678","uuid":"12345678-1234-5678-1234-567812345678","int_error":"invalid literal for int() with base 10: 'urn:uuid:12345678-1234-5678-1234-567812345678'"}, +{"text":"{12345678-1234-5D678-1234\u00e9-567812345678}","repr":"'{12345678-1234-5D678-1234\u00e9-567812345678}'","split":["{12345678-1234-5D678-1234\u00e9-567812345678}"],"strip":"{12345678-1234-5D678-1234\u00e9-567812345678}","uuid":null,"int_error":"invalid literal for int() with base 10: '{12345678-1234-5D678-1234\u00e9-567812345678}'"}, +{"text":"0x9\u00ad650722769","repr":"'0x9\\xad650722769'","split":["0x9\u00ad650722769"],"strip":"0x9\u00ad650722769","uuid":null,"int_error":"invalid literal for int() with base 10: '0x9\\xad650722769'"}, +{"text":"46013807112","repr":"'46013807112'","split":["46013807112"],"strip":"46013807112","uuid":null,"int":"46013807112"}, +{"text":"32\u000b92610527","repr":"'32\\x0b92610527'","split":["32","92610527"],"strip":"32\u000b92610527","uuid":null,"int_error":"invalid literal for int() with base 10: '32\\x0b92610527'"}, +{"text":"1a9\u00ad2557146414","repr":"'1a9\\xad2557146414'","split":["1a9\u00ad2557146414"],"strip":"1a9\u00ad2557146414","uuid":null,"int_error":"invalid literal for int() with base 10: '1a9\\xad2557146414'"}, +{"text":"896743'31684\u2003X","repr":"\"896743'31684\\u2003X\"","split":["896743'31684","X"],"strip":"896743'31684\u2003X","uuid":null,"int_error":"invalid literal for int() with base 10: \"896743'31684\\u2003X\""}, +{"text":"-8\udb40\udc016259_259\u001f3","repr":"'-8\\U000e00016259_259\\x1f3'","split":["-8\udb40\udc016259_259","3"],"strip":"-8\udb40\udc016259_259\u001f3","uuid":null,"int_error":"invalid literal for int() with base 10: '-8\\U000e00016259_259\\x1f3'"}, +{"text":"-66997\u00a0921\u00008\ud83d\ude0072","repr":"'-66997\\xa0921\\x008\ud83d\ude0072'","split":["-66997","921\u00008\ud83d\ude0072"],"strip":"-66997\u00a0921\u00008\ud83d\ude0072","uuid":null,"int_error":"invalid literal for int() with base 10: '-66997\\xa0921\\x008\ud83d\ude0072'"}, +{"text":"19183994030","repr":"'19183994030'","split":["19183994030"],"strip":"19183994030","uuid":null,"int":"19183994030"}, +{"text":"-838508502324","repr":"'-838508502324'","split":["-838508502324"],"strip":"-838508502324","uuid":null,"int":"-838508502324"}, +{"text":"11070909980","repr":"'11070909980'","split":["11070909980"],"strip":"11070909980","uuid":null,"int":"11070909980"}, +{"text":"77871628769","repr":"'77871628769'","split":["77871628769"],"strip":"77871628769","uuid":null,"int":"77871628769"}, +{"text":"388350446'50","repr":"\"388350446'50\"","split":["388350446'50"],"strip":"388350446'50","uuid":null,"int_error":"invalid literal for int() with base 10: \"388350446'50\""}, +{"text":"4931441646\n63","repr":"'4931441646\\n63'","split":["4931441646","63"],"strip":"4931441646\n63","uuid":null,"int_error":"invalid literal for int() with base 10: '4931441646\\n63'"}, +{"text":"66541504\u00a058","repr":"'66541504\\xa058'","split":["66541504","58"],"strip":"66541504\u00a058","uuid":null,"int_error":"invalid literal for int() with base 10: '66541504\\xa058'"}, +{"text":"67729uuid:2642","repr":"'67729uuid:2642'","split":["67729uuid:2642"],"strip":"67729uuid:2642","uuid":null,"int_error":"invalid literal for int() with base 10: '67729uuid:2642'"}, +{"text":"-19257646598","repr":"'-19257646598'","split":["-19257646598"],"strip":"-19257646598","uuid":null,"int":"-19257646598"}, +{"text":"-45174965334urn:","repr":"'-45174965334urn:'","split":["-45174965334urn:"],"strip":"-45174965334urn:","uuid":null,"int_error":"invalid literal for int() with base 10: '-45174965334urn:'"}, +{"text":"265264768798","repr":"'265264768798'","split":["265264768798"],"strip":"265264768798","uuid":null,"int":"265264768798"}, +{"text":"2\uff11995720\u037870","repr":"'2\uff11995720\\u037870'","split":["2\uff11995720\u037870"],"strip":"2\uff11995720\u037870","uuid":null,"int_error":"invalid literal for int() with base 10: '2\uff11995720\\u037870'"}, +{"text":"829741\u0663972045","repr":"'829741\u0663972045'","split":["829741\u0663972045"],"strip":"829741\u0663972045","uuid":null,"int":"8297413972045"}, +{"text":"337785023071","repr":"'337785023071'","split":["337785023071"],"strip":"337785023071","uuid":null,"int":"337785023071"}, +{"text":"93609769032\u00a0","repr":"'93609769032\\xa0'","split":["93609769032"],"strip":"93609769032","uuid":null,"int":"93609769032"}, +{"text":"-12513667488","repr":"'-12513667488'","split":["-12513667488"],"strip":"-12513667488","uuid":null,"int":"-12513667488"}, +{"text":"311003738848","repr":"'311003738848'","split":["311003738848"],"strip":"311003738848","uuid":null,"int":"311003738848"}, +{"text":"58\u0085722765078","repr":"'58\\x85722765078'","split":["58","722765078"],"strip":"58\u0085722765078","uuid":null,"int_error":"invalid literal for int() with base 10: '58\\x85722765078'"}, +{"text":"\u007f9\\6205283276","repr":"'\\x7f9\\\\6205283276'","split":["\u007f9\\6205283276"],"strip":"\u007f9\\6205283276","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x7f9\\\\6205283276'"}, +{"text":"2\u007f8\u3000846249137","repr":"'2\\x7f8\\u3000846249137'","split":["2\u007f8","846249137"],"strip":"2\u007f8\u3000846249137","uuid":null,"int_error":"invalid literal for int() with base 10: '2\\x7f8\\u3000846249137'"}, +{"text":"-4\"28452002774","repr":"'-4\"28452002774'","split":["-4\"28452002774"],"strip":"-4\"28452002774","uuid":null,"int_error":"invalid literal for int() with base 10: '-4\"28452002774'"}, +{"text":"-7}57081050375","repr":"'-7}57081050375'","split":["-7}57081050375"],"strip":"-7}57081050375","uuid":null,"int_error":"invalid literal for int() with base 10: '-7}57081050375'"}, +{"text":"8888304769721+","repr":"'8888304769721+'","split":["8888304769721+"],"strip":"8888304769721+","uuid":null,"int_error":"invalid literal for int() with base 10: '8888304769721+'"}, +{"text":"501610631217","repr":"'501610631217'","split":["501610631217"],"strip":"501610631217","uuid":null,"int":"501610631217"}, +{"text":"7760914931","repr":"'7760914931'","split":["7760914931"],"strip":"7760914931","uuid":null,"int":"7760914931"}, +{"text":"-530\u00ad5950967","repr":"'-530\\xad5950967'","split":["-530\u00ad5950967"],"strip":"-530\u00ad5950967","uuid":null,"int_error":"invalid literal for int() with base 10: '-530\\xad5950967'"}, +{"text":"22503057420","repr":"'22503057420'","split":["22503057420"],"strip":"22503057420","uuid":null,"int":"22503057420"}, +{"text":"-674916167x04","repr":"'-674916167x04'","split":["-674916167x04"],"strip":"-674916167x04","uuid":null,"int_error":"invalid literal for int() with base 10: '-674916167x04'"}, +{"text":"-141 3161uuid:261","repr":"'-141 3161uuid:261'","split":["-141","3161uuid:261"],"strip":"-141 3161uuid:261","uuid":null,"int_error":"invalid literal for int() with base 10: '-141 3161uuid:261'"}, +{"text":"-413319852305","repr":"'-413319852305'","split":["-413319852305"],"strip":"-413319852305","uuid":null,"int":"-413319852305"}, +{"text":"-684517309300","repr":"'-684517309300'","split":["-684517309300"],"strip":"-684517309300","uuid":null,"int":"-684517309300"}, +{"text":"256026534354","repr":"'256026534354'","split":["256026534354"],"strip":"256026534354","uuid":null,"int":"256026534354"}, +{"text":"-99701250982","repr":"'-99701250982'","split":["-99701250982"],"strip":"-99701250982","uuid":null,"int":"-99701250982"}, +{"text":"-761948360465","repr":"'-761948360465'","split":["-761948360465"],"strip":"-761948360465","uuid":null,"int":"-761948360465"}, +{"text":"-879044390077","repr":"'-879044390077'","split":["-879044390077"],"strip":"-879044390077","uuid":null,"int":"-879044390077"}, +{"text":"-\u00003582581822uuid:","repr":"'-\\x003582581822uuid:'","split":["-\u00003582581822uuid:"],"strip":"-\u00003582581822uuid:","uuid":null,"int_error":"invalid literal for int() with base 10: '-\\x003582581822uuid:'"}, +{"text":"-\uff114758978624","repr":"'-\uff114758978624'","split":["-\uff114758978624"],"strip":"-\uff114758978624","uuid":null,"int":"-14758978624"}, +{"text":"-336782347001","repr":"'-336782347001'","split":["-336782347001"],"strip":"-336782347001","uuid":null,"int":"-336782347001"}, +{"text":"-742702785001","repr":"'-742702785001'","split":["-742702785001"],"strip":"-742702785001","uuid":null,"int":"-742702785001"}, +{"text":"-579043\udb40\udc011\n9369","repr":"'-579043\\U000e00011\\n9369'","split":["-579043\udb40\udc011","9369"],"strip":"-579043\udb40\udc011\n9369","uuid":null,"int_error":"invalid literal for int() with base 10: '-579043\\U000e00011\\n9369'"}, +{"text":"149215335646","repr":"'149215335646'","split":["149215335646"],"strip":"149215335646","uuid":null,"int":"149215335646"}, +{"text":"\n804871\ud83d\ude00994849","repr":"'\\n804871\ud83d\ude00994849'","split":["804871\ud83d\ude00994849"],"strip":"804871\ud83d\ude00994849","uuid":null,"int_error":"invalid literal for int() with base 10: '\\n804871\ud83d\ude00994849'"}, +{"text":"-639174390393","repr":"'-639174390393'","split":["-639174390393"],"strip":"-639174390393","uuid":null,"int":"-639174390393"}, +{"text":"-994666487267","repr":"'-994666487267'","split":["-994666487267"],"strip":"-994666487267","uuid":null,"int":"-994666487267"}, +{"text":"309248238212","repr":"'309248238212'","split":["309248238212"],"strip":"309248238212","uuid":null,"int":"309248238212"}, +{"text":"21396329102","repr":"'21396329102'","split":["21396329102"],"strip":"21396329102","uuid":null,"int":"21396329102"}, +{"text":"-889223071220","repr":"'-889223071220'","split":["-889223071220"],"strip":"-889223071220","uuid":null,"int":"-889223071220"}, +{"text":"-8119133770959","repr":"'-8119133770959'","split":["-8119133770959"],"strip":"-8119133770959","uuid":null,"int":"-8119133770959"}, +{"text":"3:0192094036","repr":"'3:0192094036'","split":["3:0192094036"],"strip":"3:0192094036","uuid":null,"int_error":"invalid literal for int() with base 10: '3:0192094036'"}, +{"text":"60,05111687","repr":"'60,05111687'","split":["60,05111687"],"strip":"60,05111687","uuid":null,"int_error":"invalid literal for int() with base 10: '60,05111687'"}, +{"text":"-84131943205","repr":"'-84131943205'","split":["-84131943205"],"strip":"-84131943205","uuid":null,"int":"-84131943205"}, +{"text":"61-9328\ud835\udfce5888","repr":"'61-9328\ud835\udfce5888'","split":["61-9328\ud835\udfce5888"],"strip":"61-9328\ud835\udfce5888","uuid":null,"int_error":"invalid literal for int() with base 10: '61-9328\ud835\udfce5888'"}, +{"text":"233_73944120","repr":"'233_73944120'","split":["233_73944120"],"strip":"233_73944120","uuid":null,"int":"23373944120"}, +{"text":"22 060729665\ud835\udfce\u3000","repr":"'22 060729665\ud835\udfce\\u3000'","split":["22","060729665\ud835\udfce"],"strip":"22 060729665\ud835\udfce","uuid":null,"int_error":"invalid literal for int() with base 10: '22 060729665\ud835\udfce\\u3000'"}, +{"text":"-716904258302","repr":"'-716904258302'","split":["-716904258302"],"strip":"-716904258302","uuid":null,"int":"-716904258302"}, +{"text":"-28\u000079528668","repr":"'-28\\x0079528668'","split":["-28\u000079528668"],"strip":"-28\u000079528668","uuid":null,"int_error":"invalid literal for int() with base 10: '-28\\x0079528668'"}, +{"text":"76893\u00a0280537","repr":"'76893\\xa0280537'","split":["76893","280537"],"strip":"76893\u00a0280537","uuid":null,"int_error":"invalid literal for int() with base 10: '76893\\xa0280537'"}, +{"text":"919229745054","repr":"'919229745054'","split":["919229745054"],"strip":"919229745054","uuid":null,"int":"919229745054"}, +{"text":"962913985{813","repr":"'962913985{813'","split":["962913985{813"],"strip":"962913985{813","uuid":null,"int_error":"invalid literal for int() with base 10: '962913985{813'"}, +{"text":"804951522645","repr":"'804951522645'","split":["804951522645"],"strip":"804951522645","uuid":null,"int":"804951522645"}, +{"text":"5\n,72749725","repr":"'5\\n,72749725'","split":["5",",72749725"],"strip":"5\n,72749725","uuid":null,"int_error":"invalid literal for int() with base 10: '5\\n,72749725'"}, +{"text":"-278875215825","repr":"'-278875215825'","split":["-278875215825"],"strip":"-278875215825","uuid":null,"int":"-278875215825"}, +{"text":"73=6623496\ufeff1\u007f","repr":"'73=6623496\\ufeff1\\x7f'","split":["73=6623496\ufeff1\u007f"],"strip":"73=6623496\ufeff1\u007f","uuid":null,"int_error":"invalid literal for int() with base 10: '73=6623496\\ufeff1\\x7f'"}, +{"text":"-54366433473","repr":"'-54366433473'","split":["-54366433473"],"strip":"-54366433473","uuid":null,"int":"-54366433473"}, +{"text":"-171859479060","repr":"'-171859479060'","split":["-171859479060"],"strip":"-171859479060","uuid":null,"int":"-171859479060"}, +{"text":"-20937540480","repr":"'-20937540480'","split":["-20937540480"],"strip":"-20937540480","uuid":null,"int":"-20937540480"}, +{"text":"\ufeff6494\u06635565667","repr":"'\\ufeff6494\u06635565667'","split":["\ufeff6494\u06635565667"],"strip":"\ufeff6494\u06635565667","uuid":null,"int_error":"invalid literal for int() with base 10: '\\ufeff6494\u06635565667'"}, +{"text":"237887762762","repr":"'237887762762'","split":["237887762762"],"strip":"237887762762","uuid":null,"int":"237887762762"}, +{"text":"4698577991862","repr":"'4698577991862'","split":["4698577991862"],"strip":"4698577991862","uuid":null,"int":"4698577991862"}, +{"text":"973214\u008528106","repr":"'973214\\x8528106'","split":["973214","28106"],"strip":"973214\u008528106","uuid":null,"int_error":"invalid literal for int() with base 10: '973214\\x8528106'"}, +{"text":"75{902351664","repr":"'75{902351664'","split":["75{902351664"],"strip":"75{902351664","uuid":null,"int_error":"invalid literal for int() with base 10: '75{902351664'"}, +{"text":"394129792377","repr":"'394129792377'","split":["394129792377"],"strip":"394129792377","uuid":null,"int":"394129792377"}, +{"text":"859233865321","repr":"'859233865321'","split":["859233865321"],"strip":"859233865321","uuid":null,"int":"859233865321"}, +{"text":"-98270495909","repr":"'-98270495909'","split":["-98270495909"],"strip":"-98270495909","uuid":null,"int":"-98270495909"}, +{"text":"367191268911","repr":"'367191268911'","split":["367191268911"],"strip":"367191268911","uuid":null,"int":"367191268911"}, +{"text":"01253803 2","repr":"'01253803 2'","split":["01253803","2"],"strip":"01253803 2","uuid":null,"int_error":"invalid literal for int() with base 10: '01253803 2'"}, +{"text":"-9:3629517,7","repr":"'-9:3629517,7'","split":["-9:3629517,7"],"strip":"-9:3629517,7","uuid":null,"int_error":"invalid literal for int() with base 10: '-9:3629517,7'"}, +{"text":"-292679502194","repr":"'-292679502194'","split":["-292679502194"],"strip":"-292679502194","uuid":null,"int":"-292679502194"}, +{"text":"-28\u00ad636\u3000929984","repr":"'-28\\xad636\\u3000929984'","split":["-28\u00ad636","929984"],"strip":"-28\u00ad636\u3000929984","uuid":null,"int_error":"invalid literal for int() with base 10: '-28\\xad636\\u3000929984'"}, +{"text":"265635844293","repr":"'265635844293'","split":["265635844293"],"strip":"265635844293","uuid":null,"int":"265635844293"}, +{"text":"-479084439094","repr":"'-479084439094'","split":["-479084439094"],"strip":"-479084439094","uuid":null,"int":"-479084439094"}, +{"text":"6004446285","repr":"'6004446285'","split":["6004446285"],"strip":"6004446285","uuid":null,"int":"6004446285"}, +{"text":"536\u000b429207=793","repr":"'536\\x0b429207=793'","split":["536","429207=793"],"strip":"536\u000b429207=793","uuid":null,"int_error":"invalid literal for int() with base 10: '536\\x0b429207=793'"}, +{"text":"-191677754742","repr":"'-191677754742'","split":["-191677754742"],"strip":"-191677754742","uuid":null,"int":"-191677754742"}, +{"text":"-6{985899844","repr":"'-6{985899844'","split":["-6{985899844"],"strip":"-6{985899844","uuid":null,"int_error":"invalid literal for int() with base 10: '-6{985899844'"}, +{"text":"-18126393020","repr":"'-18126393020'","split":["-18126393020"],"strip":"-18126393020","uuid":null,"int":"-18126393020"}, +{"text":"3662300155","repr":"'3662300155'","split":["3662300155"],"strip":"3662300155","uuid":null,"int":"3662300155"}, +{"text":"-44542647427","repr":"'-44542647427'","split":["-44542647427"],"strip":"-44542647427","uuid":null,"int":"-44542647427"}, +{"text":"64702080689","repr":"'64702080689'","split":["64702080689"],"strip":"64702080689","uuid":null,"int":"64702080689"}, +{"text":"-\ud83d\ude009054046675","repr":"'-\ud83d\ude009054046675'","split":["-\ud83d\ude009054046675"],"strip":"-\ud83d\ude009054046675","uuid":null,"int_error":"invalid literal for int() with base 10: '-\ud83d\ude009054046675'"}, +{"text":"4002a10076815","repr":"'4002a10076815'","split":["4002a10076815"],"strip":"4002a10076815","uuid":null,"int_error":"invalid literal for int() with base 10: '4002a10076815'"}, +{"text":"-65680450772","repr":"'-65680450772'","split":["-65680450772"],"strip":"-65680450772","uuid":null,"int":"-65680450772"}, +{"text":"-357153504400","repr":"'-357153504400'","split":["-357153504400"],"strip":"-357153504400","uuid":null,"int":"-357153504400"}, +{"text":"93736956714","repr":"'93736956714'","split":["93736956714"],"strip":"93736956714","uuid":null,"int":"93736956714"}, +{"text":"60\u001f1548369'61","repr":"\"60\\x1f1548369'61\"","split":["60","1548369'61"],"strip":"60\u001f1548369'61","uuid":null,"int_error":"invalid literal for int() with base 10: \"60\\x1f1548369'61\""}, +{"text":"-48576099635","repr":"'-48576099635'","split":["-48576099635"],"strip":"-48576099635","uuid":null,"int":"-48576099635"}, +{"text":"-61130950168","repr":"'-61130950168'","split":["-61130950168"],"strip":"-61130950168","uuid":null,"int":"-61130950168"}, +{"text":"-3\ud83d\ude00306a2530167","repr":"'-3\ud83d\ude00306a2530167'","split":["-3\ud83d\ude00306a2530167"],"strip":"-3\ud83d\ude00306a2530167","uuid":null,"int_error":"invalid literal for int() with base 10: '-3\ud83d\ude00306a2530167'"}, +{"text":"51584385212\n","repr":"'51584385212\\n'","split":["51584385212"],"strip":"51584385212","uuid":null,"int":"51584385212"}, +{"text":"727442921843","repr":"'727442921843'","split":["727442921843"],"strip":"727442921843","uuid":null,"int":"727442921843"}, +{"text":"-972888118726","repr":"'-972888118726'","split":["-972888118726"],"strip":"-972888118726","uuid":null,"int":"-972888118726"}, +{"text":"95\u037835\u001f3064516","repr":"'95\\u037835\\x1f3064516'","split":["95\u037835","3064516"],"strip":"95\u037835\u001f3064516","uuid":null,"int_error":"invalid literal for int() with base 10: '95\\u037835\\x1f3064516'"}, +{"text":"9470900740x18","repr":"'9470900740x18'","split":["9470900740x18"],"strip":"9470900740x18","uuid":null,"int_error":"invalid literal for int() with base 10: '9470900740x18'"}, +{"text":"704244478357","repr":"'704244478357'","split":["704244478357"],"strip":"704244478357","uuid":null,"int":"704244478357"}, +{"text":"5082425\u001c0503","repr":"'5082425\\x1c0503'","split":["5082425","0503"],"strip":"5082425\u001c0503","uuid":null,"int_error":"invalid literal for int() with base 10: '5082425\\x1c0503'"}, +{"text":"12133045756","repr":"'12133045756'","split":["12133045756"],"strip":"12133045756","uuid":null,"int":"12133045756"}, +{"text":"965257810854","repr":"'965257810854'","split":["965257810854"],"strip":"965257810854","uuid":null,"int":"965257810854"}, +{"text":"-51159660235","repr":"'-51159660235'","split":["-51159660235"],"strip":"-51159660235","uuid":null,"int":"-51159660235"}, +{"text":"-1919844\n8877\u20037\t","repr":"'-1919844\\n8877\\u20037\\t'","split":["-1919844","8877","7"],"strip":"-1919844\n8877\u20037","uuid":null,"int_error":"invalid literal for int() with base 10: '-1919844\\n8877\\u20037\\t'"}, +{"text":"21227534358","repr":"'21227534358'","split":["21227534358"],"strip":"21227534358","uuid":null,"int":"21227534358"}, +{"text":"-505713\u007f16454","repr":"'-505713\\x7f16454'","split":["-505713\u007f16454"],"strip":"-505713\u007f16454","uuid":null,"int_error":"invalid literal for int() with base 10: '-505713\\x7f16454'"}, +{"text":"298982975","repr":"'298982975'","split":["298982975"],"strip":"298982975","uuid":null,"int":"298982975"}, +{"text":"33119:63\ufeff300","repr":"'33119:63\\ufeff300'","split":["33119:63\ufeff300"],"strip":"33119:63\ufeff300","uuid":null,"int_error":"invalid literal for int() with base 10: '33119:63\\ufeff300'"}, +{"text":"\u001f2\ufeff081526678","repr":"'\\x1f2\\ufeff081526678'","split":["2\ufeff081526678"],"strip":"2\ufeff081526678","uuid":null,"int_error":"invalid literal for int() with base 10: '\\x1f2\\ufeff081526678'"}, +{"text":"18350x95981963","repr":"'18350x95981963'","split":["18350x95981963"],"strip":"18350x95981963","uuid":null,"int_error":"invalid literal for int() with base 10: '18350x95981963'"}, +{"text":"-961677106128","repr":"'-961677106128'","split":["-961677106128"],"strip":"-961677106128","uuid":null,"int":"-961677106128"}, +{"text":"-507042-7257\udb40\udc010","repr":"'-507042-7257\\U000e00010'","split":["-507042-7257\udb40\udc010"],"strip":"-507042-7257\udb40\udc010","uuid":null,"int_error":"invalid literal for int() with base 10: '-507042-7257\\U000e00010'"}, +{"text":"720584196749","repr":"'720584196749'","split":["720584196749"],"strip":"720584196749","uuid":null,"int":"720584196749"}, +{"text":"694355,4\u03787618urn:","repr":"'694355,4\\u03787618urn:'","split":["694355,4\u03787618urn:"],"strip":"694355,4\u03787618urn:","uuid":null,"int_error":"invalid literal for int() with base 10: '694355,4\\u03787618urn:'"}, +{"text":"3925610813","repr":"'3925610813'","split":["3925610813"],"strip":"3925610813","uuid":null,"int":"3925610813"}, +{"text":"-5\u00e9834827235","repr":"'-5\u00e9834827235'","split":["-5\u00e9834827235"],"strip":"-5\u00e9834827235","uuid":null,"int_error":"invalid literal for int() with base 10: '-5\u00e9834827235'"}, +{"text":"8624572016","repr":"'8624572016'","split":["8624572016"],"strip":"8624572016","uuid":null,"int":"8624572016"}, +{"text":"-35385868754","repr":"'-35385868754'","split":["-35385868754"],"strip":"-35385868754","uuid":null,"int":"-35385868754"}, +{"text":"-849205320x76","repr":"'-849205320x76'","split":["-849205320x76"],"strip":"-849205320x76","uuid":null,"int_error":"invalid literal for int() with base 10: '-849205320x76'"}, +{"text":"162159\u300030","repr":"'162159\\u300030'","split":["162159","30"],"strip":"162159\u300030","uuid":null,"int_error":"invalid literal for int() with base 10: '162159\\u300030'"}, +{"text":"-970960a3,291","repr":"'-970960a3,291'","split":["-970960a3,291"],"strip":"-970960a3,291","uuid":null,"int_error":"invalid literal for int() with base 10: '-970960a3,291'"}, +{"text":"-21452716796","repr":"'-21452716796'","split":["-21452716796"],"strip":"-21452716796","uuid":null,"int":"-21452716796"}, +{"text":"352145050F543","repr":"'352145050F543'","split":["352145050F543"],"strip":"352145050F543","uuid":null,"int_error":"invalid literal for int() with base 10: '352145050F543'"}, +{"text":"372200030527","repr":"'372200030527'","split":["372200030527"],"strip":"372200030527","uuid":null,"int":"372200030527"}, +{"text":"-\u2003\udb40\udc0145980474084","repr":"'-\\u2003\\U000e000145980474084'","split":["-","\udb40\udc0145980474084"],"strip":"-\u2003\udb40\udc0145980474084","uuid":null,"int_error":"invalid literal for int() with base 10: '-\\u2003\\U000e000145980474084'"}, +{"text":"8\"79879861","repr":"'8\"79879861'","split":["8\"79879861"],"strip":"8\"79879861","uuid":null,"int_error":"invalid literal for int() with base 10: '8\"79879861'"}, +{"text":"-77982739\"55","repr":"'-77982739\"55'","split":["-77982739\"55"],"strip":"-77982739\"55","uuid":null,"int_error":"invalid literal for int() with base 10: '-77982739\"55'"}, +{"text":"7426281774","repr":"'7426281774'","split":["7426281774"],"strip":"7426281774","uuid":null,"int":"7426281774"}, +{"text":"\u2028164507150645","repr":"'\\u2028164507150645'","split":["164507150645"],"strip":"164507150645","uuid":null,"int":"164507150645"}, +{"text":"-47462861502","repr":"'-47462861502'","split":["-47462861502"],"strip":"-47462861502","uuid":null,"int":"-47462861502"}, +{"text":"-521954399042","repr":"'-521954399042'","split":["-521954399042"],"strip":"-521954399042","uuid":null,"int":"-521954399042"}, +{"text":"-941420302204","repr":"'-941420302204'","split":["-941420302204"],"strip":"-941420302204","uuid":null,"int":"-941420302204"}, +{"text":"-8571506896","repr":"'-8571506896'","split":["-8571506896"],"strip":"-8571506896","uuid":null,"int":"-8571506896"}, +{"text":"66772}42667","repr":"'66772}42667'","split":["66772}42667"],"strip":"66772}42667","uuid":null,"int_error":"invalid literal for int() with base 10: '66772}42667'"}, +{"text":"722218285673","repr":"'722218285673'","split":["722218285673"],"strip":"722218285673","uuid":null,"int":"722218285673"}, +{"text":"-493302351164","repr":"'-493302351164'","split":["-493302351164"],"strip":"-493302351164","uuid":null,"int":"-493302351164"}, +{"text":"\"-68\u0378782778303","repr":"'\"-68\\u0378782778303'","split":["\"-68\u0378782778303"],"strip":"\"-68\u0378782778303","uuid":null,"int_error":"invalid literal for int() with base 10: '\"-68\\u0378782778303'"}, +{"text":"90954464\u00a082","repr":"'90954464\\xa082'","split":["90954464","82"],"strip":"90954464\u00a082","uuid":null,"int_error":"invalid literal for int() with base 10: '90954464\\xa082'"} +], +"paths": [ +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":".","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"./a","str":"a","parts":["a"],"absolute":false,"name":"a","parents":["."]}, +{"flavor":"posix","text":"a/./b/","str":"a/b","parts":["a","b"],"absolute":false,"name":"b","parents":["a","."]}, +{"flavor":"posix","text":"a//b","str":"a/b","parts":["a","b"],"absolute":false,"name":"b","parents":["a","."]}, +{"flavor":"posix","text":"a/../b","str":"a/../b","parts":["a","..","b"],"absolute":false,"name":"b","parents":["a/..","a","."]}, +{"flavor":"posix","text":"/a","str":"/a","parts":["/","a"],"absolute":true,"name":"a","parents":["/"]}, +{"flavor":"posix","text":"//a","str":"//a","parts":["//","a"],"absolute":true,"name":"a","parents":["//"]}, +{"flavor":"posix","text":"///a","str":"/a","parts":["/","a"],"absolute":true,"name":"a","parents":["/"]}, +{"flavor":"posix","text":"a\\b","str":"a\\b","parts":["a\\b"],"absolute":false,"name":"a\\b","parents":["."]}, +{"flavor":"posix","text":"c:x","str":"c:x","parts":["c:x"],"absolute":false,"name":"c:x","parents":["."]}, +{"flavor":"posix","text":"C:/a/b/","str":"C:/a/b","parts":["C:","a","b"],"absolute":false,"name":"b","parents":["C:/a","C:","."]}, +{"flavor":"posix","text":"c:","str":"c:","parts":["c:"],"absolute":false,"name":"c:","parents":["."]}, +{"flavor":"posix","text":"./c:x","str":"c:x","parts":["c:x"],"absolute":false,"name":"c:x","parents":["."]}, +{"flavor":"posix","text":"//server/share","str":"//server/share","parts":["//","server","share"],"absolute":true,"name":"share","parents":["//server","//"]}, +{"flavor":"posix","text":"//server/share/x","str":"//server/share/x","parts":["//","server","share","x"],"absolute":true,"name":"x","parents":["//server/share","//server","//"]}, +{"flavor":"posix","text":"\\\\?\\C:\\x","str":"\\\\?\\C:\\x","parts":["\\\\?\\C:\\x"],"absolute":false,"name":"\\\\?\\C:\\x","parents":["."]}, +{"flavor":"posix","text":"//?/UNC/server/share","str":"//?/UNC/server/share","parts":["//","?","UNC","server","share"],"absolute":true,"name":"share","parents":["//?/UNC/server","//?/UNC","//?","//"]}, +{"flavor":"posix","text":"//?/UNC/server","str":"//?/UNC/server","parts":["//","?","UNC","server"],"absolute":true,"name":"server","parents":["//?/UNC","//?","//"]}, +{"flavor":"posix","text":"\\\\.\\device\\x","str":"\\\\.\\device\\x","parts":["\\\\.\\device\\x"],"absolute":false,"name":"\\\\.\\device\\x","parents":["."]}, +{"flavor":"posix","text":"\\a","str":"\\a","parts":["\\a"],"absolute":false,"name":"\\a","parents":["."]}, +{"flavor":"posix","text":"\u00e9:x","str":"\u00e9:x","parts":["\u00e9:x"],"absolute":false,"name":"\u00e9:x","parents":["."]}, +{"flavor":"posix","text":"\\\\\\x","str":"\\\\\\x","parts":["\\\\\\x"],"absolute":false,"name":"\\\\\\x","parents":["."]}, +{"flavor":"posix","text":"//?/","str":"//?","parts":["//","?"],"absolute":true,"name":"?","parents":["//"]}, +{"flavor":"posix","text":"x:y:z","str":"x:y:z","parts":["x:y:z"],"absolute":false,"name":"x:y:z","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"uncb..x,y/\\/","str":"uncb..x,y/\\","parts":["uncb..x,y","\\"],"absolute":false,"name":"\\","parents":["uncb..x,y","."]}, +{"flavor":"posix","text":"\u00e9/","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":";\u0000?\u00e9","str":";\u0000?\u00e9","parts":[";\u0000?\u00e9"],"absolute":false,"name":";\u0000?\u00e9","parents":["."]}, +{"flavor":"posix","text":"x,y//.?\\..\u0000","str":"x,y/.?\\..\u0000","parts":["x,y",".?\\..\u0000"],"absolute":false,"name":".?\\..\u0000","parents":["x,y","."]}, +{"flavor":"posix","text":"x,y\u0000;..","str":"x,y\u0000;..","parts":["x,y\u0000;.."],"absolute":false,"name":"x,y\u0000;..","parents":["."]}, +{"flavor":"posix","text":"\\\\..uncC","str":"\\\\..uncC","parts":["\\\\..uncC"],"absolute":false,"name":"\\\\..uncC","parents":["."]}, +{"flavor":"posix","text":"a\\\\uncb//","str":"a\\\\uncb","parts":["a\\\\uncb"],"absolute":false,"name":"a\\\\uncb","parents":["."]}, +{"flavor":"posix","text":":a\u0000","str":":a\u0000","parts":[":a\u0000"],"absolute":false,"name":":a\u0000","parents":["."]}, +{"flavor":"posix","text":";UNC\\\\bUNC","str":";UNC\\\\bUNC","parts":[";UNC\\\\bUNC"],"absolute":false,"name":";UNC\\\\bUNC","parents":["."]}, +{"flavor":"posix","text":"\u00e9c?;aCb\u0000","str":"\u00e9c?;aCb\u0000","parts":["\u00e9c?;aCb\u0000"],"absolute":false,"name":"\u00e9c?;aCb\u0000","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":":..","str":":..","parts":[":.."],"absolute":false,"name":":..","parents":["."]}, +{"flavor":"posix","text":":C\u0000.\u0000.","str":":C\u0000.\u0000.","parts":[":C\u0000.\u0000."],"absolute":false,"name":":C\u0000.\u0000.","parents":["."]}, +{"flavor":"posix","text":"C:...","str":"C:...","parts":["C:..."],"absolute":false,"name":"C:...","parents":["."]}, +{"flavor":"posix","text":"C\u00e9;\\\\Cunc\u00e9/","str":"C\u00e9;\\\\Cunc\u00e9","parts":["C\u00e9;\\\\Cunc\u00e9"],"absolute":false,"name":"C\u00e9;\\\\Cunc\u00e9","parents":["."]}, +{"flavor":"posix","text":"\\\\","str":"\\\\","parts":["\\\\"],"absolute":false,"name":"\\\\","parents":["."]}, +{"flavor":"posix","text":":C\u0000 \u00e9","str":":C\u0000 \u00e9","parts":[":C\u0000 \u00e9"],"absolute":false,"name":":C\u0000 \u00e9","parents":["."]}, +{"flavor":"posix","text":"UNC\\\\\\..cc","str":"UNC\\\\\\..cc","parts":["UNC\\\\\\..cc"],"absolute":false,"name":"UNC\\\\\\..cc","parents":["."]}, +{"flavor":"posix","text":" ////","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"posix","text":".?..","str":".?..","parts":[".?.."],"absolute":false,"name":".?..","parents":["."]}, +{"flavor":"posix","text":"?UNC//","str":"?UNC","parts":["?UNC"],"absolute":false,"name":"?UNC","parents":["."]}, +{"flavor":"posix","text":"..//\u00e9\u0000\u0000unc\u00e9","str":"../\u00e9\u0000\u0000unc\u00e9","parts":["..","\u00e9\u0000\u0000unc\u00e9"],"absolute":false,"name":"\u00e9\u0000\u0000unc\u00e9","parents":["..","."]}, +{"flavor":"posix","text":".bb\u0000 //\\\\a","str":".bb\u0000 /\\\\a","parts":[".bb\u0000 ","\\\\a"],"absolute":false,"name":"\\\\a","parents":[".bb\u0000 ","."]}, +{"flavor":"posix","text":"bbx,yx,yx,y/ ","str":"bbx,yx,yx,y/ ","parts":["bbx,yx,yx,y"," "],"absolute":false,"name":" ","parents":["bbx,yx,yx,y","."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\\?:","str":"\\?:","parts":["\\?:"],"absolute":false,"name":"\\?:","parents":["."]}, +{"flavor":"posix","text":"unc;\u00e9;..\\\\","str":"unc;\u00e9;..\\\\","parts":["unc;\u00e9;..\\\\"],"absolute":false,"name":"unc;\u00e9;..\\\\","parents":["."]}, +{"flavor":"posix","text":"/?\\\u00e9?:","str":"/?\\\u00e9?:","parts":["/","?\\\u00e9?:"],"absolute":true,"name":"?\\\u00e9?:","parents":["/"]}, +{"flavor":"posix","text":" ac..Cx,y","str":" ac..Cx,y","parts":[" ac..Cx,y"],"absolute":false,"name":" ac..Cx,y","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":" ?cx,yC","str":" ?cx,yC","parts":[" ?cx,yC"],"absolute":false,"name":" ?cx,yC","parents":["."]}, +{"flavor":"posix","text":"C: C\u00e9unc;unc","str":"C: C\u00e9unc;unc","parts":["C: C\u00e9unc;unc"],"absolute":false,"name":"C: C\u00e9unc;unc","parents":["."]}, +{"flavor":"posix","text":"\u0000uncUNC?","str":"\u0000uncUNC?","parts":["\u0000uncUNC?"],"absolute":false,"name":"\u0000uncUNC?","parents":["."]}, +{"flavor":"posix","text":"\\\\b/c","str":"\\\\b/c","parts":["\\\\b","c"],"absolute":false,"name":"c","parents":["\\\\b","."]}, +{"flavor":"posix","text":"\\\\ C","str":"\\\\ C","parts":["\\\\ C"],"absolute":false,"name":"\\\\ C","parents":["."]}, +{"flavor":"posix","text":"C\\;unc\\\\;","str":"C\\;unc\\\\;","parts":["C\\;unc\\\\;"],"absolute":false,"name":"C\\;unc\\\\;","parents":["."]}, +{"flavor":"posix","text":"?bunc\u0000c?unc","str":"?bunc\u0000c?unc","parts":["?bunc\u0000c?unc"],"absolute":false,"name":"?bunc\u0000c?unc","parents":["."]}, +{"flavor":"posix","text":"b?","str":"b?","parts":["b?"],"absolute":false,"name":"b?","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\\\\Cbx,y;c","str":"\\\\Cbx,y;c","parts":["\\\\Cbx,y;c"],"absolute":false,"name":"\\\\Cbx,y;c","parents":["."]}, +{"flavor":"posix","text":"\u0000Cunc ;x,y","str":"\u0000Cunc ;x,y","parts":["\u0000Cunc ;x,y"],"absolute":false,"name":"\u0000Cunc ;x,y","parents":["."]}, +{"flavor":"posix","text":"..c\u0000\\\\..\u0000","str":"..c\u0000\\\\..\u0000","parts":["..c\u0000\\\\..\u0000"],"absolute":false,"name":"..c\u0000\\\\..\u0000","parents":["."]}, +{"flavor":"posix","text":"\\\\a","str":"\\\\a","parts":["\\\\a"],"absolute":false,"name":"\\\\a","parents":["."]}, +{"flavor":"posix","text":"/UNCb // ","str":"/UNCb / ","parts":["/","UNCb "," "],"absolute":true,"name":" ","parents":["/UNCb ","/"]}, +{"flavor":"posix","text":":uncuncUNC\\\\\\","str":":uncuncUNC\\\\\\","parts":[":uncuncUNC\\\\\\"],"absolute":false,"name":":uncuncUNC\\\\\\","parents":["."]}, +{"flavor":"posix","text":"\\\\C..b\u0000a","str":"\\\\C..b\u0000a","parts":["\\\\C..b\u0000a"],"absolute":false,"name":"\\\\C..b\u0000a","parents":["."]}, +{"flavor":"posix","text":"\u0000\u00e9C//uncC:","str":"\u0000\u00e9C/uncC:","parts":["\u0000\u00e9C","uncC:"],"absolute":false,"name":"uncC:","parents":["\u0000\u00e9C","."]}, +{"flavor":"posix","text":";","str":";","parts":[";"],"absolute":false,"name":";","parents":["."]}, +{"flavor":"posix","text":"//x,y","str":"//x,y","parts":["//","x,y"],"absolute":true,"name":"x,y","parents":["//"]}, +{"flavor":"posix","text":"/unc","str":"/unc","parts":["/","unc"],"absolute":true,"name":"unc","parents":["/"]}, +{"flavor":"posix","text":".. c","str":".. c","parts":[".. c"],"absolute":false,"name":".. c","parents":["."]}, +{"flavor":"posix","text":"..\\\\a\u0000","str":"..\\\\a\u0000","parts":["..\\\\a\u0000"],"absolute":false,"name":"..\\\\a\u0000","parents":["."]}, +{"flavor":"posix","text":"/CCUNC:","str":"/CCUNC:","parts":["/","CCUNC:"],"absolute":true,"name":"CCUNC:","parents":["/"]}, +{"flavor":"posix","text":";C\u0000","str":";C\u0000","parts":[";C\u0000"],"absolute":false,"name":";C\u0000","parents":["."]}, +{"flavor":"posix","text":":\u0000:.\\\\uncunc;","str":":\u0000:.\\\\uncunc;","parts":[":\u0000:.\\\\uncunc;"],"absolute":false,"name":":\u0000:.\\\\uncunc;","parents":["."]}, +{"flavor":"posix","text":"uncUNC;.\u00e9\u00e9uncb","str":"uncUNC;.\u00e9\u00e9uncb","parts":["uncUNC;.\u00e9\u00e9uncb"],"absolute":false,"name":"uncUNC;.\u00e9\u00e9uncb","parents":["."]}, +{"flavor":"posix","text":"?C.. \\\\","str":"?C.. \\\\","parts":["?C.. \\\\"],"absolute":false,"name":"?C.. \\\\","parents":["."]}, +{"flavor":"posix","text":"\u0000\\\\cb:","str":"\u0000\\\\cb:","parts":["\u0000\\\\cb:"],"absolute":false,"name":"\u0000\\\\cb:","parents":["."]}, +{"flavor":"posix","text":"\u0000: /Cx,yx,y..","str":"\u0000: /Cx,yx,y..","parts":["\u0000: ","Cx,yx,y.."],"absolute":false,"name":"Cx,yx,y..","parents":["\u0000: ","."]}, +{"flavor":"posix","text":"\\x,y..;b.","str":"\\x,y..;b.","parts":["\\x,y..;b."],"absolute":false,"name":"\\x,y..;b.","parents":["."]}, +{"flavor":"posix","text":"\u00e9 b..:","str":"\u00e9 b..:","parts":["\u00e9 b..:"],"absolute":false,"name":"\u00e9 b..:","parents":["."]}, +{"flavor":"posix","text":"\u00e9","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"x,y","str":"x,y","parts":["x,y"],"absolute":false,"name":"x,y","parents":["."]}, +{"flavor":"posix","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"\\","parents":["."]}, +{"flavor":"posix","text":"?;bc ","str":"?;bc ","parts":["?;bc "],"absolute":false,"name":"?;bc ","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\u0000C... \\\\//","str":"\u0000C... \\\\","parts":["\u0000C... \\\\"],"absolute":false,"name":"\u0000C... \\\\","parents":["."]}, +{"flavor":"posix","text":"unc/\u00e9x,yC/c","str":"unc/\u00e9x,yC/c","parts":["unc","\u00e9x,yC","c"],"absolute":false,"name":"c","parents":["unc/\u00e9x,yC","unc","."]}, +{"flavor":"posix","text":"C..////C\\","str":"C../C\\","parts":["C..","C\\"],"absolute":false,"name":"C\\","parents":["C..","."]}, +{"flavor":"posix","text":"..x,yUNC\\.a::","str":"..x,yUNC\\.a::","parts":["..x,yUNC\\.a::"],"absolute":false,"name":"..x,yUNC\\.a::","parents":["."]}, +{"flavor":"posix","text":"UNCC..ax,y","str":"UNCC..ax,y","parts":["UNCC..ax,y"],"absolute":false,"name":"UNCC..ax,y","parents":["."]}, +{"flavor":"posix","text":"UNCunc\\\\:.. :","str":"UNCunc\\\\:.. :","parts":["UNCunc\\\\:.. :"],"absolute":false,"name":"UNCunc\\\\:.. :","parents":["."]}, +{"flavor":"posix","text":"/? a","str":"/? a","parts":["/","? a"],"absolute":true,"name":"? a","parents":["/"]}, +{"flavor":"posix","text":"/\\","str":"/\\","parts":["/","\\"],"absolute":true,"name":"\\","parents":["/"]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"uncC\\:ca. ","str":"uncC\\:ca. ","parts":["uncC\\:ca. "],"absolute":false,"name":"uncC\\:ca. ","parents":["."]}, +{"flavor":"posix","text":";b\\\\?b?:.","str":";b\\\\?b?:.","parts":[";b\\\\?b?:."],"absolute":false,"name":";b\\\\?b?:.","parents":["."]}, +{"flavor":"posix","text":"\\\\ a;c","str":"\\\\ a;c","parts":["\\\\ a;c"],"absolute":false,"name":"\\\\ a;c","parents":["."]}, +{"flavor":"posix","text":"b? ///","str":"b? ","parts":["b? "],"absolute":false,"name":"b? ","parents":["."]}, +{"flavor":"posix","text":"\u00e9","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"posix","text":" :\\\\\u00e9Cuncx,y\u0000","str":" :\\\\\u00e9Cuncx,y\u0000","parts":[" :\\\\\u00e9Cuncx,y\u0000"],"absolute":false,"name":" :\\\\\u00e9Cuncx,y\u0000","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\\\\.","str":"\\\\.","parts":["\\\\."],"absolute":false,"name":"\\\\.","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"\\","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"..","str":"..","parts":[".."],"absolute":false,"name":"..","parents":["."]}, +{"flavor":"posix","text":"//","str":"//","parts":["//"],"absolute":true,"name":"","parents":[]}, +{"flavor":"posix","text":"C\\\\","str":"C\\\\","parts":["C\\\\"],"absolute":false,"name":"C\\\\","parents":["."]}, +{"flavor":"posix","text":"uncb\u0000unc","str":"uncb\u0000unc","parts":["uncb\u0000unc"],"absolute":false,"name":"uncb\u0000unc","parents":["."]}, +{"flavor":"posix","text":"..c..a\u0000","str":"..c..a\u0000","parts":["..c..a\u0000"],"absolute":false,"name":"..c..a\u0000","parents":["."]}, +{"flavor":"posix","text":"\\\\\\\\x,yunc\\\\\u00e9?","str":"\\\\\\\\x,yunc\\\\\u00e9?","parts":["\\\\\\\\x,yunc\\\\\u00e9?"],"absolute":false,"name":"\\\\\\\\x,yunc\\\\\u00e9?","parents":["."]}, +{"flavor":"posix","text":"x,yax,y/UNC:.","str":"x,yax,y/UNC:.","parts":["x,yax,y","UNC:."],"absolute":false,"name":"UNC:.","parents":["x,yax,y","."]}, +{"flavor":"posix","text":"/","str":"/","parts":["/"],"absolute":true,"name":"","parents":[]}, +{"flavor":"posix","text":"a.:\\/","str":"a.:\\","parts":["a.:\\"],"absolute":false,"name":"a.:\\","parents":["."]}, +{"flavor":"posix","text":"Cc;\u0000","str":"Cc;\u0000","parts":["Cc;\u0000"],"absolute":false,"name":"Cc;\u0000","parents":["."]}, +{"flavor":"posix","text":"\\\\ac//\\\\","str":"\\\\ac/\\\\","parts":["\\\\ac","\\\\"],"absolute":false,"name":"\\\\","parents":["\\\\ac","."]}, +{"flavor":"posix","text":"\u0000b x,yUNC\\","str":"\u0000b x,yUNC\\","parts":["\u0000b x,yUNC\\"],"absolute":false,"name":"\u0000b x,yUNC\\","parents":["."]}, +{"flavor":"posix","text":":CcCx,y//\\\\","str":":CcCx,y/\\\\","parts":[":CcCx,y","\\\\"],"absolute":false,"name":"\\\\","parents":[":CcCx,y","."]}, +{"flavor":"posix","text":"?C\u0000;C/ ","str":"?C\u0000;C/ ","parts":["?C\u0000;C"," "],"absolute":false,"name":" ","parents":["?C\u0000;C","."]}, +{"flavor":"posix","text":"\\\\//;a\\\\UNC","str":"\\\\/;a\\\\UNC","parts":["\\\\",";a\\\\UNC"],"absolute":false,"name":";a\\\\UNC","parents":["\\\\","."]}, +{"flavor":"posix","text":";;.b","str":";;.b","parts":[";;.b"],"absolute":false,"name":";;.b","parents":["."]}, +{"flavor":"posix","text":"x,y\\\\bunc:;","str":"x,y\\\\bunc:;","parts":["x,y\\\\bunc:;"],"absolute":false,"name":"x,y\\\\bunc:;","parents":["."]}, +{"flavor":"posix","text":"\\\\C:\u00e9","str":"\\\\C:\u00e9","parts":["\\\\C:\u00e9"],"absolute":false,"name":"\\\\C:\u00e9","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"unc\\..c?unc","str":"unc\\..c?unc","parts":["unc\\..c?unc"],"absolute":false,"name":"unc\\..c?unc","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"unc","str":"unc","parts":["unc"],"absolute":false,"name":"unc","parents":["."]}, +{"flavor":"posix","text":". ?x,y","str":". ?x,y","parts":[". ?x,y"],"absolute":false,"name":". ?x,y","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\u0000\\\\","str":"\u0000\\\\","parts":["\u0000\\\\"],"absolute":false,"name":"\u0000\\\\","parents":["."]}, +{"flavor":"posix","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"\\","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"UNC/\u0000C","str":"UNC/\u0000C","parts":["UNC","\u0000C"],"absolute":false,"name":"\u0000C","parents":["UNC","."]}, +{"flavor":"posix","text":" uncunc","str":" uncunc","parts":[" uncunc"],"absolute":false,"name":" uncunc","parents":["."]}, +{"flavor":"posix","text":"\u0000c.?..bC..","str":"\u0000c.?..bC..","parts":["\u0000c.?..bC.."],"absolute":false,"name":"\u0000c.?..bC..","parents":["."]}, +{"flavor":"posix","text":".UNC//;UNCCc//","str":".UNC/;UNCCc","parts":[".UNC",";UNCCc"],"absolute":false,"name":";UNCCc","parents":[".UNC","."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":".x,y","str":".x,y","parts":[".x,y"],"absolute":false,"name":".x,y","parents":["."]}, +{"flavor":"posix","text":": :","str":": :","parts":[": :"],"absolute":false,"name":": :","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"unca:?ba\u0000unc","str":"unca:?ba\u0000unc","parts":["unca:?ba\u0000unc"],"absolute":false,"name":"unca:?ba\u0000unc","parents":["."]}, +{"flavor":"posix","text":":b/:","str":":b/:","parts":[":b",":"],"absolute":false,"name":":","parents":[":b","."]}, +{"flavor":"posix","text":"UNC\u00e9.\u00e9","str":"UNC\u00e9.\u00e9","parts":["UNC\u00e9.\u00e9"],"absolute":false,"name":"UNC\u00e9.\u00e9","parents":["."]}, +{"flavor":"posix","text":"c\\\\UNCunc\\\u00e9/x,y","str":"c\\\\UNCunc\\\u00e9/x,y","parts":["c\\\\UNCunc\\\u00e9","x,y"],"absolute":false,"name":"x,y","parents":["c\\\\UNCunc\\\u00e9","."]}, +{"flavor":"posix","text":"x,y\u00e9aUNC \\\\C","str":"x,y\u00e9aUNC \\\\C","parts":["x,y\u00e9aUNC \\\\C"],"absolute":false,"name":"x,y\u00e9aUNC \\\\C","parents":["."]}, +{"flavor":"posix","text":" ","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"posix","text":"..b.a","str":"..b.a","parts":["..b.a"],"absolute":false,"name":"..b.a","parents":["."]}, +{"flavor":"posix","text":"\\\\.C","str":"\\\\.C","parts":["\\\\.C"],"absolute":false,"name":"\\\\.C","parents":["."]}, +{"flavor":"posix","text":" // /","str":" / ","parts":[" "," "],"absolute":false,"name":" ","parents":[" ","."]}, +{"flavor":"posix","text":"\u0000? cCUNC;","str":"\u0000? cCUNC;","parts":["\u0000? cCUNC;"],"absolute":false,"name":"\u0000? cCUNC;","parents":["."]}, +{"flavor":"posix","text":"?\u00e9 \\\\","str":"?\u00e9 \\\\","parts":["?\u00e9 \\\\"],"absolute":false,"name":"?\u00e9 \\\\","parents":["."]}, +{"flavor":"posix","text":" ","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"posix","text":"cunc//","str":"cunc","parts":["cunc"],"absolute":false,"name":"cunc","parents":["."]}, +{"flavor":"posix","text":"\\\\\u00e9","str":"\\\\\u00e9","parts":["\\\\\u00e9"],"absolute":false,"name":"\\\\\u00e9","parents":["."]}, +{"flavor":"posix","text":"unc\u00e9\\\\","str":"unc\u00e9\\\\","parts":["unc\u00e9\\\\"],"absolute":false,"name":"unc\u00e9\\\\","parents":["."]}, +{"flavor":"posix","text":"\u0000","str":"\u0000","parts":["\u0000"],"absolute":false,"name":"\u0000","parents":["."]}, +{"flavor":"posix","text":"UNC;x,y","str":"UNC;x,y","parts":["UNC;x,y"],"absolute":false,"name":"UNC;x,y","parents":["."]}, +{"flavor":"posix","text":"UNC.c ","str":"UNC.c ","parts":["UNC.c "],"absolute":false,"name":"UNC.c ","parents":["."]}, +{"flavor":"posix","text":"a \u00e9..///","str":"a \u00e9..","parts":["a \u00e9.."],"absolute":false,"name":"a \u00e9..","parents":["."]}, +{"flavor":"posix","text":"x,y\u00e9x,y/ \\\u0000","str":"x,y\u00e9x,y/ \\\u0000","parts":["x,y\u00e9x,y"," \\\u0000"],"absolute":false,"name":" \\\u0000","parents":["x,y\u00e9x,y","."]}, +{"flavor":"posix","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"\\","parents":["."]}, +{"flavor":"posix","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"\\","parents":["."]}, +{"flavor":"posix","text":" b.","str":" b.","parts":[" b."],"absolute":false,"name":" b.","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":";..//Ca;","str":";../Ca;","parts":[";..","Ca;"],"absolute":false,"name":"Ca;","parents":[";..","."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"C\\/?UNC","str":"C\\/?UNC","parts":["C\\","?UNC"],"absolute":false,"name":"?UNC","parents":["C\\","."]}, +{"flavor":"posix","text":".c","str":".c","parts":[".c"],"absolute":false,"name":".c","parents":["."]}, +{"flavor":"posix","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"posix","text":"..;UNCx,y;UNC/","str":"..;UNCx,y;UNC","parts":["..;UNCx,y;UNC"],"absolute":false,"name":"..;UNCx,y;UNC","parents":["."]}, +{"flavor":"posix","text":":/\\;//x,y","str":":/\\;/x,y","parts":[":","\\;","x,y"],"absolute":false,"name":"x,y","parents":[":/\\;",":","."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"aC ///","str":"aC ","parts":["aC "],"absolute":false,"name":"aC ","parents":["."]}, +{"flavor":"posix","text":"/C","str":"/C","parts":["/","C"],"absolute":true,"name":"C","parents":["/"]}, +{"flavor":"posix","text":"x,yUNC??C?","str":"x,yUNC??C?","parts":["x,yUNC??C?"],"absolute":false,"name":"x,yUNC??C?","parents":["."]}, +{"flavor":"posix","text":"Cbb\u0000UNC","str":"Cbb\u0000UNC","parts":["Cbb\u0000UNC"],"absolute":false,"name":"Cbb\u0000UNC","parents":["."]}, +{"flavor":"posix","text":"a/","str":"a","parts":["a"],"absolute":false,"name":"a","parents":["."]}, +{"flavor":"posix","text":"cuncUNC\\\\...a","str":"cuncUNC\\\\...a","parts":["cuncUNC\\\\...a"],"absolute":false,"name":"cuncUNC\\\\...a","parents":["."]}, +{"flavor":"posix","text":"uncUNC.","str":"uncUNC.","parts":["uncUNC."],"absolute":false,"name":"uncUNC.","parents":["."]}, +{"flavor":"posix","text":"/\u00e9\\bCUNC;","str":"/\u00e9\\bCUNC;","parts":["/","\u00e9\\bCUNC;"],"absolute":true,"name":"\u00e9\\bCUNC;","parents":["/"]}, +{"flavor":"posix","text":";","str":";","parts":[";"],"absolute":false,"name":";","parents":["."]}, +{"flavor":"posix","text":"\u00e9 UNCaC:","str":"\u00e9 UNCaC:","parts":["\u00e9 UNCaC:"],"absolute":false,"name":"\u00e9 UNCaC:","parents":["."]}, +{"flavor":"posix","text":".?","str":".?","parts":[".?"],"absolute":false,"name":".?","parents":["."]}, +{"flavor":"posix","text":"..","str":"..","parts":[".."],"absolute":false,"name":"..","parents":["."]}, +{"flavor":"posix","text":"x,yx,y","str":"x,yx,y","parts":["x,yx,y"],"absolute":false,"name":"x,yx,y","parents":["."]}, +{"flavor":"posix","text":"?/a/;UNCx,y","str":"?/a/;UNCx,y","parts":["?","a",";UNCx,y"],"absolute":false,"name":";UNCx,y","parents":["?/a","?","."]}, +{"flavor":"posix","text":"\\Cb..","str":"\\Cb..","parts":["\\Cb.."],"absolute":false,"name":"\\Cb..","parents":["."]}, +{"flavor":"posix","text":":?/\u00e9\\\\\u0000b","str":":?/\u00e9\\\\\u0000b","parts":[":?","\u00e9\\\\\u0000b"],"absolute":false,"name":"\u00e9\\\\\u0000b","parents":[":?","."]}, +{"flavor":"posix","text":"/","str":"/","parts":["/"],"absolute":true,"name":"","parents":[]}, +{"flavor":"posix","text":"?\\\\a;","str":"?\\\\a;","parts":["?\\\\a;"],"absolute":false,"name":"?\\\\a;","parents":["."]}, +{"flavor":"posix","text":"//","str":"//","parts":["//"],"absolute":true,"name":"","parents":[]}, +{"flavor":"posix","text":"?","str":"?","parts":["?"],"absolute":false,"name":"?","parents":["."]}, +{"flavor":"posix","text":"?;/\u00e9UNC\\","str":"?;/\u00e9UNC\\","parts":["?;","\u00e9UNC\\"],"absolute":false,"name":"\u00e9UNC\\","parents":["?;","."]}, +{"flavor":"posix","text":" ?c..b","str":" ?c..b","parts":[" ?c..b"],"absolute":false,"name":" ?c..b","parents":["."]}, +{"flavor":"posix","text":";/C\u00e9.","str":";/C\u00e9.","parts":[";","C\u00e9."],"absolute":false,"name":"C\u00e9.","parents":[";","."]}, +{"flavor":"posix","text":";;bUNC\\\\..","str":";;bUNC\\\\..","parts":[";;bUNC\\\\.."],"absolute":false,"name":";;bUNC\\\\..","parents":["."]}, +{"flavor":"posix","text":" \\ \\//","str":" \\ \\","parts":[" \\ \\"],"absolute":false,"name":" \\ \\","parents":["."]}, +{"flavor":"posix","text":"cca\\\\ ","str":"cca\\\\ ","parts":["cca\\\\ "],"absolute":false,"name":"cca\\\\ ","parents":["."]}, +{"flavor":"posix","text":"/.\\\\cCUNC//","str":"/.\\\\cCUNC","parts":["/",".\\\\cCUNC"],"absolute":true,"name":".\\\\cCUNC","parents":["/"]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"b/c","str":"b/c","parts":["b","c"],"absolute":false,"name":"c","parents":["b","."]}, +{"flavor":"posix","text":"UNCx,y? unc","str":"UNCx,y? unc","parts":["UNCx,y? unc"],"absolute":false,"name":"UNCx,y? unc","parents":["."]}, +{"flavor":"posix","text":"UNC.unc\\.","str":"UNC.unc\\.","parts":["UNC.unc\\."],"absolute":false,"name":"UNC.unc\\.","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"//cC//..x,yC","str":"//cC/..x,yC","parts":["//","cC","..x,yC"],"absolute":true,"name":"..x,yC","parents":["//cC","//"]}, +{"flavor":"posix","text":"x,y ....","str":"x,y ....","parts":["x,y ...."],"absolute":false,"name":"x,y ....","parents":["."]}, +{"flavor":"posix","text":"; \\\\/:\u0000?","str":"; \\\\/:\u0000?","parts":["; \\\\",":\u0000?"],"absolute":false,"name":":\u0000?","parents":["; \\\\","."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"UNC?a\\\\","str":"UNC?a\\\\","parts":["UNC?a\\\\"],"absolute":false,"name":"UNC?a\\\\","parents":["."]}, +{"flavor":"posix","text":"/unc","str":"/unc","parts":["/","unc"],"absolute":true,"name":"unc","parents":["/"]}, +{"flavor":"posix","text":"/a\\..c?","str":"/a\\..c?","parts":["/","a\\..c?"],"absolute":true,"name":"a\\..c?","parents":["/"]}, +{"flavor":"posix","text":"//;:a","str":"//;:a","parts":["//",";:a"],"absolute":true,"name":";:a","parents":["//"]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\u00e9/b?:;","str":"\u00e9/b?:;","parts":["\u00e9","b?:;"],"absolute":false,"name":"b?:;","parents":["\u00e9","."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"unc.//\u00e9/ :;","str":"unc./\u00e9/ :;","parts":["unc.","\u00e9"," :;"],"absolute":false,"name":" :;","parents":["unc./\u00e9","unc.","."]}, +{"flavor":"posix","text":" //;b?c.:","str":" /;b?c.:","parts":[" ",";b?c.:"],"absolute":false,"name":";b?c.:","parents":[" ","."]}, +{"flavor":"posix","text":"b\\a\\..?.","str":"b\\a\\..?.","parts":["b\\a\\..?."],"absolute":false,"name":"b\\a\\..?.","parents":["."]}, +{"flavor":"posix","text":"//","str":"//","parts":["//"],"absolute":true,"name":"","parents":[]}, +{"flavor":"posix","text":"//..\\C/..\u0000\u00e9","str":"//..\\C/..\u0000\u00e9","parts":["//","..\\C","..\u0000\u00e9"],"absolute":true,"name":"..\u0000\u00e9","parents":["//..\\C","//"]}, +{"flavor":"posix","text":"\\\\x,ya;bc","str":"\\\\x,ya;bc","parts":["\\\\x,ya;bc"],"absolute":false,"name":"\\\\x,ya;bc","parents":["."]}, +{"flavor":"posix","text":"UNCunc\\\\;.","str":"UNCunc\\\\;.","parts":["UNCunc\\\\;."],"absolute":false,"name":"UNCunc\\\\;.","parents":["."]}, +{"flavor":"posix","text":"cx,yc\\b","str":"cx,yc\\b","parts":["cx,yc\\b"],"absolute":false,"name":"cx,yc\\b","parents":["."]}, +{"flavor":"posix","text":"aCa\u0000C?","str":"aCa\u0000C?","parts":["aCa\u0000C?"],"absolute":false,"name":"aCa\u0000C?","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"x,y","str":"x,y","parts":["x,y"],"absolute":false,"name":"x,y","parents":["."]}, +{"flavor":"posix","text":"UNCx,y\u00e9","str":"UNCx,y\u00e9","parts":["UNCx,y\u00e9"],"absolute":false,"name":"UNCx,y\u00e9","parents":["."]}, +{"flavor":"posix","text":"a ","str":"a ","parts":["a "],"absolute":false,"name":"a ","parents":["."]}, +{"flavor":"posix","text":"?unc x,y//;a","str":"?unc x,y/;a","parts":["?unc x,y",";a"],"absolute":false,"name":";a","parents":["?unc x,y","."]}, +{"flavor":"posix","text":".../\u0000\\c","str":".../\u0000\\c","parts":["...","\u0000\\c"],"absolute":false,"name":"\u0000\\c","parents":["...","."]}, +{"flavor":"posix","text":"a?/\u00e9CCb?","str":"a?/\u00e9CCb?","parts":["a?","\u00e9CCb?"],"absolute":false,"name":"\u00e9CCb?","parents":["a?","."]}, +{"flavor":"posix","text":"b","str":"b","parts":["b"],"absolute":false,"name":"b","parents":["."]}, +{"flavor":"posix","text":"UNCUNC\\\\","str":"UNCUNC\\\\","parts":["UNCUNC\\\\"],"absolute":false,"name":"UNCUNC\\\\","parents":["."]}, +{"flavor":"posix","text":"b.","str":"b.","parts":["b."],"absolute":false,"name":"b.","parents":["."]}, +{"flavor":"posix","text":":UNC\u00e9\u00e9a/..","str":":UNC\u00e9\u00e9a/..","parts":[":UNC\u00e9\u00e9a",".."],"absolute":false,"name":"..","parents":[":UNC\u00e9\u00e9a","."]}, +{"flavor":"posix","text":"C?x,yx,y.. \\?","str":"C?x,yx,y.. \\?","parts":["C?x,yx,y.. \\?"],"absolute":false,"name":"C?x,yx,y.. \\?","parents":["."]}, +{"flavor":"posix","text":"c:b cC..","str":"c:b cC..","parts":["c:b cC.."],"absolute":false,"name":"c:b cC..","parents":["."]}, +{"flavor":"posix","text":"///..\u0000 \u0000//","str":"/..\u0000 \u0000","parts":["/","..\u0000 \u0000"],"absolute":true,"name":"..\u0000 \u0000","parents":["/"]}, +{"flavor":"posix","text":";;a?x,y\\\\ ?","str":";;a?x,y\\\\ ?","parts":[";;a?x,y\\\\ ?"],"absolute":false,"name":";;a?x,y\\\\ ?","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"posix","text":"cx,ya//unc?","str":"cx,ya/unc?","parts":["cx,ya","unc?"],"absolute":false,"name":"unc?","parents":["cx,ya","."]}, +{"flavor":"posix","text":"//","str":"//","parts":["//"],"absolute":true,"name":"","parents":[]}, +{"flavor":"posix","text":":/","str":":","parts":[":"],"absolute":false,"name":":","parents":["."]}, +{"flavor":"posix","text":";b\u00e9..;.:\u0000","str":";b\u00e9..;.:\u0000","parts":[";b\u00e9..;.:\u0000"],"absolute":false,"name":";b\u00e9..;.:\u0000","parents":["."]}, +{"flavor":"posix","text":"c;","str":"c;","parts":["c;"],"absolute":false,"name":"c;","parents":["."]}, +{"flavor":"posix","text":"//","str":"//","parts":["//"],"absolute":true,"name":"","parents":[]}, +{"flavor":"posix","text":" //..\u0000 a","str":" /..\u0000 a","parts":[" ","..\u0000 a"],"absolute":false,"name":"..\u0000 a","parents":[" ","."]}, +{"flavor":"posix","text":":cC.","str":":cC.","parts":[":cC."],"absolute":false,"name":":cC.","parents":["."]}, +{"flavor":"posix","text":"uncb\u00e9..\\\\a/..","str":"uncb\u00e9..\\\\a/..","parts":["uncb\u00e9..\\\\a",".."],"absolute":false,"name":"..","parents":["uncb\u00e9..\\\\a","."]}, +{"flavor":"posix","text":"\u00e9..\\\\b. UNCUNC","str":"\u00e9..\\\\b. UNCUNC","parts":["\u00e9..\\\\b. UNCUNC"],"absolute":false,"name":"\u00e9..\\\\b. UNCUNC","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"c\\ \u0000","str":"c\\ \u0000","parts":["c\\ \u0000"],"absolute":false,"name":"c\\ \u0000","parents":["."]}, +{"flavor":"posix","text":":","str":":","parts":[":"],"absolute":false,"name":":","parents":["."]}, +{"flavor":"posix","text":" ","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"posix","text":"?C...","str":"?C...","parts":["?C..."],"absolute":false,"name":"?C...","parents":["."]}, +{"flavor":"posix","text":"cunc?\u0000","str":"cunc?\u0000","parts":["cunc?\u0000"],"absolute":false,"name":"cunc?\u0000","parents":["."]}, +{"flavor":"posix","text":" \\c\u00e9\\\\\\//","str":" \\c\u00e9\\\\\\","parts":[" \\c\u00e9\\\\\\"],"absolute":false,"name":" \\c\u00e9\\\\\\","parents":["."]}, +{"flavor":"posix","text":"UNCx,y","str":"UNCx,y","parts":["UNCx,y"],"absolute":false,"name":"UNCx,y","parents":["."]}, +{"flavor":"posix","text":"cUNC; UNC?.c","str":"cUNC; UNC?.c","parts":["cUNC; UNC?.c"],"absolute":false,"name":"cUNC; UNC?.c","parents":["."]}, +{"flavor":"posix","text":"?..","str":"?..","parts":["?.."],"absolute":false,"name":"?..","parents":["."]}, +{"flavor":"posix","text":" b ac../","str":" b ac..","parts":[" b ac.."],"absolute":false,"name":" b ac..","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"..\\?:x,y;\\\\\u00e9","str":"..\\?:x,y;\\\\\u00e9","parts":["..\\?:x,y;\\\\\u00e9"],"absolute":false,"name":"..\\?:x,y;\\\\\u00e9","parents":["."]}, +{"flavor":"posix","text":"\\.unc/..","str":"\\.unc/..","parts":["\\.unc",".."],"absolute":false,"name":"..","parents":["\\.unc","."]}, +{"flavor":"posix","text":"/....\\\\ a\u00e9","str":"/....\\\\ a\u00e9","parts":["/","....\\\\ a\u00e9"],"absolute":true,"name":"....\\\\ a\u00e9","parents":["/"]}, +{"flavor":"posix","text":"/\u00e9UNCc..\\b","str":"/\u00e9UNCc..\\b","parts":["/","\u00e9UNCc..\\b"],"absolute":true,"name":"\u00e9UNCc..\\b","parents":["/"]}, +{"flavor":"posix","text":"\\b","str":"\\b","parts":["\\b"],"absolute":false,"name":"\\b","parents":["."]}, +{"flavor":"posix","text":".:x,y::UNCb","str":".:x,y::UNCb","parts":[".:x,y::UNCb"],"absolute":false,"name":".:x,y::UNCb","parents":["."]}, +{"flavor":"posix","text":"?:;b\u0000a","str":"?:;b\u0000a","parts":["?:;b\u0000a"],"absolute":false,"name":"?:;b\u0000a","parents":["."]}, +{"flavor":"posix","text":"bunc//:x,y","str":"bunc/:x,y","parts":["bunc",":x,y"],"absolute":false,"name":":x,y","parents":["bunc","."]}, +{"flavor":"posix","text":"?\u0000\u0000/ \u00e9\u0000","str":"?\u0000\u0000/ \u00e9\u0000","parts":["?\u0000\u0000"," \u00e9\u0000"],"absolute":false,"name":" \u00e9\u0000","parents":["?\u0000\u0000","."]}, +{"flavor":"posix","text":"//C.\u00e9.","str":"//C.\u00e9.","parts":["//","C.\u00e9."],"absolute":true,"name":"C.\u00e9.","parents":["//"]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"CUNC","str":"CUNC","parts":["CUNC"],"absolute":false,"name":"CUNC","parents":["."]}, +{"flavor":"posix","text":"a unc? C\\\\","str":"a unc? C\\\\","parts":["a unc? C\\\\"],"absolute":false,"name":"a unc? C\\\\","parents":["."]}, +{"flavor":"posix","text":"\\.","str":"\\.","parts":["\\."],"absolute":false,"name":"\\.","parents":["."]}, +{"flavor":"posix","text":"b","str":"b","parts":["b"],"absolute":false,"name":"b","parents":["."]}, +{"flavor":"posix","text":"x,yc;unc.. ","str":"x,yc;unc.. ","parts":["x,yc;unc.. "],"absolute":false,"name":"x,yc;unc.. ","parents":["."]}, +{"flavor":"posix","text":".x,y","str":".x,y","parts":[".x,y"],"absolute":false,"name":".x,y","parents":["."]}, +{"flavor":"posix","text":"\\\u0000.","str":"\\\u0000.","parts":["\\\u0000."],"absolute":false,"name":"\\\u0000.","parents":["."]}, +{"flavor":"posix","text":";..C ","str":";..C ","parts":[";..C "],"absolute":false,"name":";..C ","parents":["."]}, +{"flavor":"posix","text":"\\unc;..?: ..","str":"\\unc;..?: ..","parts":["\\unc;..?: .."],"absolute":false,"name":"\\unc;..?: ..","parents":["."]}, +{"flavor":"posix","text":" cunc \\","str":" cunc \\","parts":[" cunc \\"],"absolute":false,"name":" cunc \\","parents":["."]}, +{"flavor":"posix","text":"b","str":"b","parts":["b"],"absolute":false,"name":"b","parents":["."]}, +{"flavor":"posix","text":" c\u0000","str":" c\u0000","parts":[" c\u0000"],"absolute":false,"name":" c\u0000","parents":["."]}, +{"flavor":"posix","text":"\\\\","str":"\\\\","parts":["\\\\"],"absolute":false,"name":"\\\\","parents":["."]}, +{"flavor":"posix","text":"C \\?","str":"C \\?","parts":["C \\?"],"absolute":false,"name":"C \\?","parents":["."]}, +{"flavor":"posix","text":"ca//C","str":"ca/C","parts":["ca","C"],"absolute":false,"name":"C","parents":["ca","."]}, +{"flavor":"posix","text":"..","str":"..","parts":[".."],"absolute":false,"name":"..","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"\u0000\u0000\\C\u00e9","str":"\u0000\u0000\\C\u00e9","parts":["\u0000\u0000\\C\u00e9"],"absolute":false,"name":"\u0000\u0000\\C\u00e9","parents":["."]}, +{"flavor":"posix","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"posix","text":"UNCbUNC","str":"UNCbUNC","parts":["UNCbUNC"],"absolute":false,"name":"UNCbUNC","parents":["."]}, +{"flavor":"posix","text":"ca\u00e9a","str":"ca\u00e9a","parts":["ca\u00e9a"],"absolute":false,"name":"ca\u00e9a","parents":["."]}, +{"flavor":"posix","text":";//;UNC","str":";/;UNC","parts":[";",";UNC"],"absolute":false,"name":";UNC","parents":[";","."]}, +{"flavor":"posix","text":"cCx,yunc","str":"cCx,yunc","parts":["cCx,yunc"],"absolute":false,"name":"cCx,yunc","parents":["."]}, +{"flavor":"posix","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"posix","text":".//\u00e9?\u00e9b\u00e9","str":"\u00e9?\u00e9b\u00e9","parts":["\u00e9?\u00e9b\u00e9"],"absolute":false,"name":"\u00e9?\u00e9b\u00e9","parents":["."]}, +{"flavor":"posix","text":"\u00e9","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"posix","text":":\\\\CUNCa:?unc","str":":\\\\CUNCa:?unc","parts":[":\\\\CUNCa:?unc"],"absolute":false,"name":":\\\\CUNCa:?unc","parents":["."]}, +{"flavor":"posix","text":"UNC","str":"UNC","parts":["UNC"],"absolute":false,"name":"UNC","parents":["."]}, +{"flavor":"posix","text":" a..;/a ","str":" a..;/a ","parts":[" a..;","a "],"absolute":false,"name":"a ","parents":[" a..;","."]}, +{"flavor":"posix","text":"\u00e9.c","str":"\u00e9.c","parts":["\u00e9.c"],"absolute":false,"name":"\u00e9.c","parents":["."]}, +{"flavor":"posix","text":"...//UNCb/a","str":".../UNCb/a","parts":["...","UNCb","a"],"absolute":false,"name":"a","parents":[".../UNCb","...","."]}, +{"flavor":"posix","text":"////;\\\\;\\\\//c","str":"/;\\\\;\\\\/c","parts":["/",";\\\\;\\\\","c"],"absolute":true,"name":"c","parents":["/;\\\\;\\\\","/"]}, +{"flavor":"posix","text":"b;..\u0000UNC\u0000unc","str":"b;..\u0000UNC\u0000unc","parts":["b;..\u0000UNC\u0000unc"],"absolute":false,"name":"b;..\u0000UNC\u0000unc","parents":["."]}, +{"flavor":"posix","text":"?","str":"?","parts":["?"],"absolute":false,"name":"?","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":".","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"./a","str":"a","parts":["a"],"absolute":false,"name":"a","parents":["."]}, +{"flavor":"windows","text":"a/./b/","str":"a\\b","parts":["a","b"],"absolute":false,"name":"b","parents":["a","."]}, +{"flavor":"windows","text":"a//b","str":"a\\b","parts":["a","b"],"absolute":false,"name":"b","parents":["a","."]}, +{"flavor":"windows","text":"a/../b","str":"a\\..\\b","parts":["a","..","b"],"absolute":false,"name":"b","parents":["a\\..","a","."]}, +{"flavor":"windows","text":"/a","str":"\\a","parts":["\\","a"],"absolute":false,"name":"a","parents":["\\"]}, +{"flavor":"windows","text":"//a","str":"\\\\a","parts":["\\\\a"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"///a","str":"\\\\\\a","parts":["\\\\\\a"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"a\\b","str":"a\\b","parts":["a","b"],"absolute":false,"name":"b","parents":["a","."]}, +{"flavor":"windows","text":"c:x","str":"c:x","parts":["c:","x"],"absolute":false,"name":"x","parents":["c:"]}, +{"flavor":"windows","text":"C:/a/b/","str":"C:\\a\\b","parts":["C:\\","a","b"],"absolute":true,"name":"b","parents":["C:\\a","C:\\"]}, +{"flavor":"windows","text":"c:","str":"c:","parts":["c:"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"./c:x","str":".\\c:x","parts":["c:x"],"absolute":false,"name":"c:x","parents":["."]}, +{"flavor":"windows","text":"//server/share","str":"\\\\server\\share\\","parts":["\\\\server\\share\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"//server/share/x","str":"\\\\server\\share\\x","parts":["\\\\server\\share\\","x"],"absolute":true,"name":"x","parents":["\\\\server\\share\\"]}, +{"flavor":"windows","text":"\\\\?\\C:\\x","str":"\\\\?\\C:\\x","parts":["\\\\?\\C:\\","x"],"absolute":true,"name":"x","parents":["\\\\?\\C:\\"]}, +{"flavor":"windows","text":"//?/UNC/server/share","str":"\\\\?\\UNC\\server\\share\\","parts":["\\\\?\\UNC\\server\\share\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"//?/UNC/server","str":"\\\\?\\UNC\\server","parts":["\\\\?\\UNC\\server"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"\\\\.\\device\\x","str":"\\\\.\\device\\x","parts":["\\\\.\\device\\","x"],"absolute":true,"name":"x","parents":["\\\\.\\device\\"]}, +{"flavor":"windows","text":"\\a","str":"\\a","parts":["\\","a"],"absolute":false,"name":"a","parents":["\\"]}, +{"flavor":"windows","text":"\u00e9:x","str":"\u00e9:x","parts":["\u00e9:","x"],"absolute":false,"name":"x","parents":["\u00e9:"]}, +{"flavor":"windows","text":"\\\\\\x","str":"\\\\\\x","parts":["\\\\\\x"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"//?/","str":"\\\\?\\","parts":["\\\\?\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"x:y:z","str":"x:y:z","parts":["x:","y:z"],"absolute":false,"name":"y:z","parents":["x:"]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"uncb..x,y/\\/","str":"uncb..x,y","parts":["uncb..x,y"],"absolute":false,"name":"uncb..x,y","parents":["."]}, +{"flavor":"windows","text":"\u00e9/","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":";\u0000?\u00e9","str":";\u0000?\u00e9","parts":[";\u0000?\u00e9"],"absolute":false,"name":";\u0000?\u00e9","parents":["."]}, +{"flavor":"windows","text":"x,y//.?\\..\u0000","str":"x,y\\.?\\..\u0000","parts":["x,y",".?","..\u0000"],"absolute":false,"name":"..\u0000","parents":["x,y\\.?","x,y","."]}, +{"flavor":"windows","text":"x,y\u0000;..","str":"x,y\u0000;..","parts":["x,y\u0000;.."],"absolute":false,"name":"x,y\u0000;..","parents":["."]}, +{"flavor":"windows","text":"\\\\..uncC","str":"\\\\..uncC","parts":["\\\\..uncC"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"a\\\\uncb//","str":"a\\uncb","parts":["a","uncb"],"absolute":false,"name":"uncb","parents":["a","."]}, +{"flavor":"windows","text":":a\u0000","str":":a\u0000","parts":[":a\u0000"],"absolute":false,"name":":a\u0000","parents":["."]}, +{"flavor":"windows","text":";UNC\\\\bUNC","str":";UNC\\bUNC","parts":[";UNC","bUNC"],"absolute":false,"name":"bUNC","parents":[";UNC","."]}, +{"flavor":"windows","text":"\u00e9c?;aCb\u0000","str":"\u00e9c?;aCb\u0000","parts":["\u00e9c?;aCb\u0000"],"absolute":false,"name":"\u00e9c?;aCb\u0000","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":":..","str":":..","parts":[":.."],"absolute":false,"name":":..","parents":["."]}, +{"flavor":"windows","text":":C\u0000.\u0000.","str":":C\u0000.\u0000.","parts":[":C\u0000.\u0000."],"absolute":false,"name":":C\u0000.\u0000.","parents":["."]}, +{"flavor":"windows","text":"C:...","str":"C:...","parts":["C:","..."],"absolute":false,"name":"...","parents":["C:"]}, +{"flavor":"windows","text":"C\u00e9;\\\\Cunc\u00e9/","str":"C\u00e9;\\Cunc\u00e9","parts":["C\u00e9;","Cunc\u00e9"],"absolute":false,"name":"Cunc\u00e9","parents":["C\u00e9;","."]}, +{"flavor":"windows","text":"\\\\","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":":C\u0000 \u00e9","str":":C\u0000 \u00e9","parts":[":C\u0000 \u00e9"],"absolute":false,"name":":C\u0000 \u00e9","parents":["."]}, +{"flavor":"windows","text":"UNC\\\\\\..cc","str":"UNC\\..cc","parts":["UNC","..cc"],"absolute":false,"name":"..cc","parents":["UNC","."]}, +{"flavor":"windows","text":" ////","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"windows","text":".?..","str":".?..","parts":[".?.."],"absolute":false,"name":".?..","parents":["."]}, +{"flavor":"windows","text":"?UNC//","str":"?UNC","parts":["?UNC"],"absolute":false,"name":"?UNC","parents":["."]}, +{"flavor":"windows","text":"..//\u00e9\u0000\u0000unc\u00e9","str":"..\\\u00e9\u0000\u0000unc\u00e9","parts":["..","\u00e9\u0000\u0000unc\u00e9"],"absolute":false,"name":"\u00e9\u0000\u0000unc\u00e9","parents":["..","."]}, +{"flavor":"windows","text":".bb\u0000 //\\\\a","str":".bb\u0000 \\a","parts":[".bb\u0000 ","a"],"absolute":false,"name":"a","parents":[".bb\u0000 ","."]}, +{"flavor":"windows","text":"bbx,yx,yx,y/ ","str":"bbx,yx,yx,y\\ ","parts":["bbx,yx,yx,y"," "],"absolute":false,"name":" ","parents":["bbx,yx,yx,y","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\\?:","str":"\\?:","parts":["\\","?:"],"absolute":false,"name":"?:","parents":["\\"]}, +{"flavor":"windows","text":"unc;\u00e9;..\\\\","str":"unc;\u00e9;..","parts":["unc;\u00e9;.."],"absolute":false,"name":"unc;\u00e9;..","parents":["."]}, +{"flavor":"windows","text":"/?\\\u00e9?:","str":"\\?\\\u00e9?:","parts":["\\","?","\u00e9?:"],"absolute":false,"name":"\u00e9?:","parents":["\\?","\\"]}, +{"flavor":"windows","text":" ac..Cx,y","str":" ac..Cx,y","parts":[" ac..Cx,y"],"absolute":false,"name":" ac..Cx,y","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":" ?cx,yC","str":" ?cx,yC","parts":[" ?cx,yC"],"absolute":false,"name":" ?cx,yC","parents":["."]}, +{"flavor":"windows","text":"C: C\u00e9unc;unc","str":"C: C\u00e9unc;unc","parts":["C:"," C\u00e9unc;unc"],"absolute":false,"name":" C\u00e9unc;unc","parents":["C:"]}, +{"flavor":"windows","text":"\u0000uncUNC?","str":"\u0000uncUNC?","parts":["\u0000uncUNC?"],"absolute":false,"name":"\u0000uncUNC?","parents":["."]}, +{"flavor":"windows","text":"\\\\b/c","str":"\\\\b\\c\\","parts":["\\\\b\\c\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"\\\\ C","str":"\\\\ C","parts":["\\\\ C"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"C\\;unc\\\\;","str":"C\\;unc\\;","parts":["C",";unc",";"],"absolute":false,"name":";","parents":["C\\;unc","C","."]}, +{"flavor":"windows","text":"?bunc\u0000c?unc","str":"?bunc\u0000c?unc","parts":["?bunc\u0000c?unc"],"absolute":false,"name":"?bunc\u0000c?unc","parents":["."]}, +{"flavor":"windows","text":"b?","str":"b?","parts":["b?"],"absolute":false,"name":"b?","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\\\\Cbx,y;c","str":"\\\\Cbx,y;c","parts":["\\\\Cbx,y;c"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"\u0000Cunc ;x,y","str":"\u0000Cunc ;x,y","parts":["\u0000Cunc ;x,y"],"absolute":false,"name":"\u0000Cunc ;x,y","parents":["."]}, +{"flavor":"windows","text":"..c\u0000\\\\..\u0000","str":"..c\u0000\\..\u0000","parts":["..c\u0000","..\u0000"],"absolute":false,"name":"..\u0000","parents":["..c\u0000","."]}, +{"flavor":"windows","text":"\\\\a","str":"\\\\a","parts":["\\\\a"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"/UNCb // ","str":"\\UNCb \\ ","parts":["\\","UNCb "," "],"absolute":false,"name":" ","parents":["\\UNCb ","\\"]}, +{"flavor":"windows","text":":uncuncUNC\\\\\\","str":":uncuncUNC","parts":[":uncuncUNC"],"absolute":false,"name":":uncuncUNC","parents":["."]}, +{"flavor":"windows","text":"\\\\C..b\u0000a","str":"\\\\C..b\u0000a","parts":["\\\\C..b\u0000a"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"\u0000\u00e9C//uncC:","str":"\u0000\u00e9C\\uncC:","parts":["\u0000\u00e9C","uncC:"],"absolute":false,"name":"uncC:","parents":["\u0000\u00e9C","."]}, +{"flavor":"windows","text":";","str":";","parts":[";"],"absolute":false,"name":";","parents":["."]}, +{"flavor":"windows","text":"//x,y","str":"\\\\x,y","parts":["\\\\x,y"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"/unc","str":"\\unc","parts":["\\","unc"],"absolute":false,"name":"unc","parents":["\\"]}, +{"flavor":"windows","text":".. c","str":".. c","parts":[".. c"],"absolute":false,"name":".. c","parents":["."]}, +{"flavor":"windows","text":"..\\\\a\u0000","str":"..\\a\u0000","parts":["..","a\u0000"],"absolute":false,"name":"a\u0000","parents":["..","."]}, +{"flavor":"windows","text":"/CCUNC:","str":"\\CCUNC:","parts":["\\","CCUNC:"],"absolute":false,"name":"CCUNC:","parents":["\\"]}, +{"flavor":"windows","text":";C\u0000","str":";C\u0000","parts":[";C\u0000"],"absolute":false,"name":";C\u0000","parents":["."]}, +{"flavor":"windows","text":":\u0000:.\\\\uncunc;","str":":\u0000:.\\uncunc;","parts":[":\u0000:.","uncunc;"],"absolute":false,"name":"uncunc;","parents":[":\u0000:.","."]}, +{"flavor":"windows","text":"uncUNC;.\u00e9\u00e9uncb","str":"uncUNC;.\u00e9\u00e9uncb","parts":["uncUNC;.\u00e9\u00e9uncb"],"absolute":false,"name":"uncUNC;.\u00e9\u00e9uncb","parents":["."]}, +{"flavor":"windows","text":"?C.. \\\\","str":"?C.. ","parts":["?C.. "],"absolute":false,"name":"?C.. ","parents":["."]}, +{"flavor":"windows","text":"\u0000\\\\cb:","str":"\u0000\\cb:","parts":["\u0000","cb:"],"absolute":false,"name":"cb:","parents":["\u0000","."]}, +{"flavor":"windows","text":"\u0000: /Cx,yx,y..","str":"\u0000: \\Cx,yx,y..","parts":["\u0000:"," ","Cx,yx,y.."],"absolute":false,"name":"Cx,yx,y..","parents":["\u0000: ","\u0000:"]}, +{"flavor":"windows","text":"\\x,y..;b.","str":"\\x,y..;b.","parts":["\\","x,y..;b."],"absolute":false,"name":"x,y..;b.","parents":["\\"]}, +{"flavor":"windows","text":"\u00e9 b..:","str":"\u00e9 b..:","parts":["\u00e9 b..:"],"absolute":false,"name":"\u00e9 b..:","parents":["."]}, +{"flavor":"windows","text":"\u00e9","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"x,y","str":"x,y","parts":["x,y"],"absolute":false,"name":"x,y","parents":["."]}, +{"flavor":"windows","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"?;bc ","str":"?;bc ","parts":["?;bc "],"absolute":false,"name":"?;bc ","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\u0000C... \\\\//","str":"\u0000C... ","parts":["\u0000C... "],"absolute":false,"name":"\u0000C... ","parents":["."]}, +{"flavor":"windows","text":"unc/\u00e9x,yC/c","str":"unc\\\u00e9x,yC\\c","parts":["unc","\u00e9x,yC","c"],"absolute":false,"name":"c","parents":["unc\\\u00e9x,yC","unc","."]}, +{"flavor":"windows","text":"C..////C\\","str":"C..\\C","parts":["C..","C"],"absolute":false,"name":"C","parents":["C..","."]}, +{"flavor":"windows","text":"..x,yUNC\\.a::","str":"..x,yUNC\\.a::","parts":["..x,yUNC",".a::"],"absolute":false,"name":".a::","parents":["..x,yUNC","."]}, +{"flavor":"windows","text":"UNCC..ax,y","str":"UNCC..ax,y","parts":["UNCC..ax,y"],"absolute":false,"name":"UNCC..ax,y","parents":["."]}, +{"flavor":"windows","text":"UNCunc\\\\:.. :","str":"UNCunc\\:.. :","parts":["UNCunc",":.. :"],"absolute":false,"name":":.. :","parents":["UNCunc","."]}, +{"flavor":"windows","text":"/? a","str":"\\? a","parts":["\\","? a"],"absolute":false,"name":"? a","parents":["\\"]}, +{"flavor":"windows","text":"/\\","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"uncC\\:ca. ","str":"uncC\\:ca. ","parts":["uncC",":ca. "],"absolute":false,"name":":ca. ","parents":["uncC","."]}, +{"flavor":"windows","text":";b\\\\?b?:.","str":";b\\?b?:.","parts":[";b","?b?:."],"absolute":false,"name":"?b?:.","parents":[";b","."]}, +{"flavor":"windows","text":"\\\\ a;c","str":"\\\\ a;c","parts":["\\\\ a;c"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"b? ///","str":"b? ","parts":["b? "],"absolute":false,"name":"b? ","parents":["."]}, +{"flavor":"windows","text":"\u00e9","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"windows","text":" :\\\\\u00e9Cuncx,y\u0000","str":" :\\\u00e9Cuncx,y\u0000","parts":[" :\\","\u00e9Cuncx,y\u0000"],"absolute":true,"name":"\u00e9Cuncx,y\u0000","parents":[" :\\"]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\\\\.","str":"\\\\.","parts":["\\\\."],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"..","str":"..","parts":[".."],"absolute":false,"name":"..","parents":["."]}, +{"flavor":"windows","text":"//","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"C\\\\","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"windows","text":"uncb\u0000unc","str":"uncb\u0000unc","parts":["uncb\u0000unc"],"absolute":false,"name":"uncb\u0000unc","parents":["."]}, +{"flavor":"windows","text":"..c..a\u0000","str":"..c..a\u0000","parts":["..c..a\u0000"],"absolute":false,"name":"..c..a\u0000","parents":["."]}, +{"flavor":"windows","text":"\\\\\\\\x,yunc\\\\\u00e9?","str":"\\\\\\\\x,yunc\\\u00e9?","parts":["\\\\\\\\","x,yunc","\u00e9?"],"absolute":true,"name":"\u00e9?","parents":["\\\\\\\\x,yunc","\\\\\\\\"]}, +{"flavor":"windows","text":"x,yax,y/UNC:.","str":"x,yax,y\\UNC:.","parts":["x,yax,y","UNC:."],"absolute":false,"name":"UNC:.","parents":["x,yax,y","."]}, +{"flavor":"windows","text":"/","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"a.:\\/","str":"a.:","parts":["a.:"],"absolute":false,"name":"a.:","parents":["."]}, +{"flavor":"windows","text":"Cc;\u0000","str":"Cc;\u0000","parts":["Cc;\u0000"],"absolute":false,"name":"Cc;\u0000","parents":["."]}, +{"flavor":"windows","text":"\\\\ac//\\\\","str":"\\\\ac\\\\","parts":["\\\\ac\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"\u0000b x,yUNC\\","str":"\u0000b x,yUNC","parts":["\u0000b x,yUNC"],"absolute":false,"name":"\u0000b x,yUNC","parents":["."]}, +{"flavor":"windows","text":":CcCx,y//\\\\","str":":CcCx,y","parts":[":CcCx,y"],"absolute":false,"name":":CcCx,y","parents":["."]}, +{"flavor":"windows","text":"?C\u0000;C/ ","str":"?C\u0000;C\\ ","parts":["?C\u0000;C"," "],"absolute":false,"name":" ","parents":["?C\u0000;C","."]}, +{"flavor":"windows","text":"\\\\//;a\\\\UNC","str":"\\\\\\\\;a\\UNC","parts":["\\\\\\\\",";a","UNC"],"absolute":true,"name":"UNC","parents":["\\\\\\\\;a","\\\\\\\\"]}, +{"flavor":"windows","text":";;.b","str":";;.b","parts":[";;.b"],"absolute":false,"name":";;.b","parents":["."]}, +{"flavor":"windows","text":"x,y\\\\bunc:;","str":"x,y\\bunc:;","parts":["x,y","bunc:;"],"absolute":false,"name":"bunc:;","parents":["x,y","."]}, +{"flavor":"windows","text":"\\\\C:\u00e9","str":"\\\\C:\u00e9","parts":["\\\\C:\u00e9"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"unc\\..c?unc","str":"unc\\..c?unc","parts":["unc","..c?unc"],"absolute":false,"name":"..c?unc","parents":["unc","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"unc","str":"unc","parts":["unc"],"absolute":false,"name":"unc","parents":["."]}, +{"flavor":"windows","text":". ?x,y","str":". ?x,y","parts":[". ?x,y"],"absolute":false,"name":". ?x,y","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\u0000\\\\","str":"\u0000","parts":["\u0000"],"absolute":false,"name":"\u0000","parents":["."]}, +{"flavor":"windows","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"UNC/\u0000C","str":"UNC\\\u0000C","parts":["UNC","\u0000C"],"absolute":false,"name":"\u0000C","parents":["UNC","."]}, +{"flavor":"windows","text":" uncunc","str":" uncunc","parts":[" uncunc"],"absolute":false,"name":" uncunc","parents":["."]}, +{"flavor":"windows","text":"\u0000c.?..bC..","str":"\u0000c.?..bC..","parts":["\u0000c.?..bC.."],"absolute":false,"name":"\u0000c.?..bC..","parents":["."]}, +{"flavor":"windows","text":".UNC//;UNCCc//","str":".UNC\\;UNCCc","parts":[".UNC",";UNCCc"],"absolute":false,"name":";UNCCc","parents":[".UNC","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":".x,y","str":".x,y","parts":[".x,y"],"absolute":false,"name":".x,y","parents":["."]}, +{"flavor":"windows","text":": :","str":": :","parts":[": :"],"absolute":false,"name":": :","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"unca:?ba\u0000unc","str":"unca:?ba\u0000unc","parts":["unca:?ba\u0000unc"],"absolute":false,"name":"unca:?ba\u0000unc","parents":["."]}, +{"flavor":"windows","text":":b/:","str":":b\\:","parts":[":b",":"],"absolute":false,"name":":","parents":[":b","."]}, +{"flavor":"windows","text":"UNC\u00e9.\u00e9","str":"UNC\u00e9.\u00e9","parts":["UNC\u00e9.\u00e9"],"absolute":false,"name":"UNC\u00e9.\u00e9","parents":["."]}, +{"flavor":"windows","text":"c\\\\UNCunc\\\u00e9/x,y","str":"c\\UNCunc\\\u00e9\\x,y","parts":["c","UNCunc","\u00e9","x,y"],"absolute":false,"name":"x,y","parents":["c\\UNCunc\\\u00e9","c\\UNCunc","c","."]}, +{"flavor":"windows","text":"x,y\u00e9aUNC \\\\C","str":"x,y\u00e9aUNC \\C","parts":["x,y\u00e9aUNC ","C"],"absolute":false,"name":"C","parents":["x,y\u00e9aUNC ","."]}, +{"flavor":"windows","text":" ","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"windows","text":"..b.a","str":"..b.a","parts":["..b.a"],"absolute":false,"name":"..b.a","parents":["."]}, +{"flavor":"windows","text":"\\\\.C","str":"\\\\.C","parts":["\\\\.C"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":" // /","str":" \\ ","parts":[" "," "],"absolute":false,"name":" ","parents":[" ","."]}, +{"flavor":"windows","text":"\u0000? cCUNC;","str":"\u0000? cCUNC;","parts":["\u0000? cCUNC;"],"absolute":false,"name":"\u0000? cCUNC;","parents":["."]}, +{"flavor":"windows","text":"?\u00e9 \\\\","str":"?\u00e9 ","parts":["?\u00e9 "],"absolute":false,"name":"?\u00e9 ","parents":["."]}, +{"flavor":"windows","text":" ","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"windows","text":"cunc//","str":"cunc","parts":["cunc"],"absolute":false,"name":"cunc","parents":["."]}, +{"flavor":"windows","text":"\\\\\u00e9","str":"\\\\\u00e9","parts":["\\\\\u00e9"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"unc\u00e9\\\\","str":"unc\u00e9","parts":["unc\u00e9"],"absolute":false,"name":"unc\u00e9","parents":["."]}, +{"flavor":"windows","text":"\u0000","str":"\u0000","parts":["\u0000"],"absolute":false,"name":"\u0000","parents":["."]}, +{"flavor":"windows","text":"UNC;x,y","str":"UNC;x,y","parts":["UNC;x,y"],"absolute":false,"name":"UNC;x,y","parents":["."]}, +{"flavor":"windows","text":"UNC.c ","str":"UNC.c ","parts":["UNC.c "],"absolute":false,"name":"UNC.c ","parents":["."]}, +{"flavor":"windows","text":"a \u00e9..///","str":"a \u00e9..","parts":["a \u00e9.."],"absolute":false,"name":"a \u00e9..","parents":["."]}, +{"flavor":"windows","text":"x,y\u00e9x,y/ \\\u0000","str":"x,y\u00e9x,y\\ \\\u0000","parts":["x,y\u00e9x,y"," ","\u0000"],"absolute":false,"name":"\u0000","parents":["x,y\u00e9x,y\\ ","x,y\u00e9x,y","."]}, +{"flavor":"windows","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\\","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":" b.","str":" b.","parts":[" b."],"absolute":false,"name":" b.","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":";..//Ca;","str":";..\\Ca;","parts":[";..","Ca;"],"absolute":false,"name":"Ca;","parents":[";..","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"C\\/?UNC","str":"C\\?UNC","parts":["C","?UNC"],"absolute":false,"name":"?UNC","parents":["C","."]}, +{"flavor":"windows","text":".c","str":".c","parts":[".c"],"absolute":false,"name":".c","parents":["."]}, +{"flavor":"windows","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"windows","text":"..;UNCx,y;UNC/","str":"..;UNCx,y;UNC","parts":["..;UNCx,y;UNC"],"absolute":false,"name":"..;UNCx,y;UNC","parents":["."]}, +{"flavor":"windows","text":":/\\;//x,y","str":":\\;\\x,y","parts":[":",";","x,y"],"absolute":false,"name":"x,y","parents":[":\\;",":","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"aC ///","str":"aC ","parts":["aC "],"absolute":false,"name":"aC ","parents":["."]}, +{"flavor":"windows","text":"/C","str":"\\C","parts":["\\","C"],"absolute":false,"name":"C","parents":["\\"]}, +{"flavor":"windows","text":"x,yUNC??C?","str":"x,yUNC??C?","parts":["x,yUNC??C?"],"absolute":false,"name":"x,yUNC??C?","parents":["."]}, +{"flavor":"windows","text":"Cbb\u0000UNC","str":"Cbb\u0000UNC","parts":["Cbb\u0000UNC"],"absolute":false,"name":"Cbb\u0000UNC","parents":["."]}, +{"flavor":"windows","text":"a/","str":"a","parts":["a"],"absolute":false,"name":"a","parents":["."]}, +{"flavor":"windows","text":"cuncUNC\\\\...a","str":"cuncUNC\\...a","parts":["cuncUNC","...a"],"absolute":false,"name":"...a","parents":["cuncUNC","."]}, +{"flavor":"windows","text":"uncUNC.","str":"uncUNC.","parts":["uncUNC."],"absolute":false,"name":"uncUNC.","parents":["."]}, +{"flavor":"windows","text":"/\u00e9\\bCUNC;","str":"\\\u00e9\\bCUNC;","parts":["\\","\u00e9","bCUNC;"],"absolute":false,"name":"bCUNC;","parents":["\\\u00e9","\\"]}, +{"flavor":"windows","text":";","str":";","parts":[";"],"absolute":false,"name":";","parents":["."]}, +{"flavor":"windows","text":"\u00e9 UNCaC:","str":"\u00e9 UNCaC:","parts":["\u00e9 UNCaC:"],"absolute":false,"name":"\u00e9 UNCaC:","parents":["."]}, +{"flavor":"windows","text":".?","str":".?","parts":[".?"],"absolute":false,"name":".?","parents":["."]}, +{"flavor":"windows","text":"..","str":"..","parts":[".."],"absolute":false,"name":"..","parents":["."]}, +{"flavor":"windows","text":"x,yx,y","str":"x,yx,y","parts":["x,yx,y"],"absolute":false,"name":"x,yx,y","parents":["."]}, +{"flavor":"windows","text":"?/a/;UNCx,y","str":"?\\a\\;UNCx,y","parts":["?","a",";UNCx,y"],"absolute":false,"name":";UNCx,y","parents":["?\\a","?","."]}, +{"flavor":"windows","text":"\\Cb..","str":"\\Cb..","parts":["\\","Cb.."],"absolute":false,"name":"Cb..","parents":["\\"]}, +{"flavor":"windows","text":":?/\u00e9\\\\\u0000b","str":":?\\\u00e9\\\u0000b","parts":[":?","\u00e9","\u0000b"],"absolute":false,"name":"\u0000b","parents":[":?\\\u00e9",":?","."]}, +{"flavor":"windows","text":"/","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"?\\\\a;","str":"?\\a;","parts":["?","a;"],"absolute":false,"name":"a;","parents":["?","."]}, +{"flavor":"windows","text":"//","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"?","str":"?","parts":["?"],"absolute":false,"name":"?","parents":["."]}, +{"flavor":"windows","text":"?;/\u00e9UNC\\","str":"?;\\\u00e9UNC","parts":["?;","\u00e9UNC"],"absolute":false,"name":"\u00e9UNC","parents":["?;","."]}, +{"flavor":"windows","text":" ?c..b","str":" ?c..b","parts":[" ?c..b"],"absolute":false,"name":" ?c..b","parents":["."]}, +{"flavor":"windows","text":";/C\u00e9.","str":";\\C\u00e9.","parts":[";","C\u00e9."],"absolute":false,"name":"C\u00e9.","parents":[";","."]}, +{"flavor":"windows","text":";;bUNC\\\\..","str":";;bUNC\\..","parts":[";;bUNC",".."],"absolute":false,"name":"..","parents":[";;bUNC","."]}, +{"flavor":"windows","text":" \\ \\//","str":" \\ ","parts":[" "," "],"absolute":false,"name":" ","parents":[" ","."]}, +{"flavor":"windows","text":"cca\\\\ ","str":"cca\\ ","parts":["cca"," "],"absolute":false,"name":" ","parents":["cca","."]}, +{"flavor":"windows","text":"/.\\\\cCUNC//","str":"\\cCUNC","parts":["\\","cCUNC"],"absolute":false,"name":"cCUNC","parents":["\\"]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"b/c","str":"b\\c","parts":["b","c"],"absolute":false,"name":"c","parents":["b","."]}, +{"flavor":"windows","text":"UNCx,y? unc","str":"UNCx,y? unc","parts":["UNCx,y? unc"],"absolute":false,"name":"UNCx,y? unc","parents":["."]}, +{"flavor":"windows","text":"UNC.unc\\.","str":"UNC.unc","parts":["UNC.unc"],"absolute":false,"name":"UNC.unc","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"//cC//..x,yC","str":"\\\\cC\\\\..x,yC","parts":["\\\\cC\\\\","..x,yC"],"absolute":true,"name":"..x,yC","parents":["\\\\cC\\\\"]}, +{"flavor":"windows","text":"x,y ....","str":"x,y ....","parts":["x,y ...."],"absolute":false,"name":"x,y ....","parents":["."]}, +{"flavor":"windows","text":"; \\\\/:\u0000?","str":"; \\:\u0000?","parts":["; ",":\u0000?"],"absolute":false,"name":":\u0000?","parents":["; ","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"UNC?a\\\\","str":"UNC?a","parts":["UNC?a"],"absolute":false,"name":"UNC?a","parents":["."]}, +{"flavor":"windows","text":"/unc","str":"\\unc","parts":["\\","unc"],"absolute":false,"name":"unc","parents":["\\"]}, +{"flavor":"windows","text":"/a\\..c?","str":"\\a\\..c?","parts":["\\","a","..c?"],"absolute":false,"name":"..c?","parents":["\\a","\\"]}, +{"flavor":"windows","text":"//;:a","str":"\\\\;:a","parts":["\\\\;:a"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\u00e9/b?:;","str":"\u00e9\\b?:;","parts":["\u00e9","b?:;"],"absolute":false,"name":"b?:;","parents":["\u00e9","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"unc.//\u00e9/ :;","str":"unc.\\\u00e9\\ :;","parts":["unc.","\u00e9"," :;"],"absolute":false,"name":" :;","parents":["unc.\\\u00e9","unc.","."]}, +{"flavor":"windows","text":" //;b?c.:","str":" \\;b?c.:","parts":[" ",";b?c.:"],"absolute":false,"name":";b?c.:","parents":[" ","."]}, +{"flavor":"windows","text":"b\\a\\..?.","str":"b\\a\\..?.","parts":["b","a","..?."],"absolute":false,"name":"..?.","parents":["b\\a","b","."]}, +{"flavor":"windows","text":"//","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"//..\\C/..\u0000\u00e9","str":"\\\\..\\C\\..\u0000\u00e9","parts":["\\\\..\\C\\","..\u0000\u00e9"],"absolute":true,"name":"..\u0000\u00e9","parents":["\\\\..\\C\\"]}, +{"flavor":"windows","text":"\\\\x,ya;bc","str":"\\\\x,ya;bc","parts":["\\\\x,ya;bc"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"UNCunc\\\\;.","str":"UNCunc\\;.","parts":["UNCunc",";."],"absolute":false,"name":";.","parents":["UNCunc","."]}, +{"flavor":"windows","text":"cx,yc\\b","str":"cx,yc\\b","parts":["cx,yc","b"],"absolute":false,"name":"b","parents":["cx,yc","."]}, +{"flavor":"windows","text":"aCa\u0000C?","str":"aCa\u0000C?","parts":["aCa\u0000C?"],"absolute":false,"name":"aCa\u0000C?","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"x,y","str":"x,y","parts":["x,y"],"absolute":false,"name":"x,y","parents":["."]}, +{"flavor":"windows","text":"UNCx,y\u00e9","str":"UNCx,y\u00e9","parts":["UNCx,y\u00e9"],"absolute":false,"name":"UNCx,y\u00e9","parents":["."]}, +{"flavor":"windows","text":"a ","str":"a ","parts":["a "],"absolute":false,"name":"a ","parents":["."]}, +{"flavor":"windows","text":"?unc x,y//;a","str":"?unc x,y\\;a","parts":["?unc x,y",";a"],"absolute":false,"name":";a","parents":["?unc x,y","."]}, +{"flavor":"windows","text":".../\u0000\\c","str":"...\\\u0000\\c","parts":["...","\u0000","c"],"absolute":false,"name":"c","parents":["...\\\u0000","...","."]}, +{"flavor":"windows","text":"a?/\u00e9CCb?","str":"a?\\\u00e9CCb?","parts":["a?","\u00e9CCb?"],"absolute":false,"name":"\u00e9CCb?","parents":["a?","."]}, +{"flavor":"windows","text":"b","str":"b","parts":["b"],"absolute":false,"name":"b","parents":["."]}, +{"flavor":"windows","text":"UNCUNC\\\\","str":"UNCUNC","parts":["UNCUNC"],"absolute":false,"name":"UNCUNC","parents":["."]}, +{"flavor":"windows","text":"b.","str":"b.","parts":["b."],"absolute":false,"name":"b.","parents":["."]}, +{"flavor":"windows","text":":UNC\u00e9\u00e9a/..","str":":UNC\u00e9\u00e9a\\..","parts":[":UNC\u00e9\u00e9a",".."],"absolute":false,"name":"..","parents":[":UNC\u00e9\u00e9a","."]}, +{"flavor":"windows","text":"C?x,yx,y.. \\?","str":"C?x,yx,y.. \\?","parts":["C?x,yx,y.. ","?"],"absolute":false,"name":"?","parents":["C?x,yx,y.. ","."]}, +{"flavor":"windows","text":"c:b cC..","str":"c:b cC..","parts":["c:","b cC.."],"absolute":false,"name":"b cC..","parents":["c:"]}, +{"flavor":"windows","text":"///..\u0000 \u0000//","str":"\\\\\\..\u0000 \u0000\\","parts":["\\\\\\..\u0000 \u0000\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":";;a?x,y\\\\ ?","str":";;a?x,y\\ ?","parts":[";;a?x,y"," ?"],"absolute":false,"name":" ?","parents":[";;a?x,y","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"windows","text":"cx,ya//unc?","str":"cx,ya\\unc?","parts":["cx,ya","unc?"],"absolute":false,"name":"unc?","parents":["cx,ya","."]}, +{"flavor":"windows","text":"//","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":":/","str":":","parts":[":"],"absolute":false,"name":":","parents":["."]}, +{"flavor":"windows","text":";b\u00e9..;.:\u0000","str":";b\u00e9..;.:\u0000","parts":[";b\u00e9..;.:\u0000"],"absolute":false,"name":";b\u00e9..;.:\u0000","parents":["."]}, +{"flavor":"windows","text":"c;","str":"c;","parts":["c;"],"absolute":false,"name":"c;","parents":["."]}, +{"flavor":"windows","text":"//","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":" //..\u0000 a","str":" \\..\u0000 a","parts":[" ","..\u0000 a"],"absolute":false,"name":"..\u0000 a","parents":[" ","."]}, +{"flavor":"windows","text":":cC.","str":":cC.","parts":[":cC."],"absolute":false,"name":":cC.","parents":["."]}, +{"flavor":"windows","text":"uncb\u00e9..\\\\a/..","str":"uncb\u00e9..\\a\\..","parts":["uncb\u00e9..","a",".."],"absolute":false,"name":"..","parents":["uncb\u00e9..\\a","uncb\u00e9..","."]}, +{"flavor":"windows","text":"\u00e9..\\\\b. UNCUNC","str":"\u00e9..\\b. UNCUNC","parts":["\u00e9..","b. UNCUNC"],"absolute":false,"name":"b. UNCUNC","parents":["\u00e9..","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"c\\ \u0000","str":"c\\ \u0000","parts":["c"," \u0000"],"absolute":false,"name":" \u0000","parents":["c","."]}, +{"flavor":"windows","text":":","str":":","parts":[":"],"absolute":false,"name":":","parents":["."]}, +{"flavor":"windows","text":" ","str":" ","parts":[" "],"absolute":false,"name":" ","parents":["."]}, +{"flavor":"windows","text":"?C...","str":"?C...","parts":["?C..."],"absolute":false,"name":"?C...","parents":["."]}, +{"flavor":"windows","text":"cunc?\u0000","str":"cunc?\u0000","parts":["cunc?\u0000"],"absolute":false,"name":"cunc?\u0000","parents":["."]}, +{"flavor":"windows","text":" \\c\u00e9\\\\\\//","str":" \\c\u00e9","parts":[" ","c\u00e9"],"absolute":false,"name":"c\u00e9","parents":[" ","."]}, +{"flavor":"windows","text":"UNCx,y","str":"UNCx,y","parts":["UNCx,y"],"absolute":false,"name":"UNCx,y","parents":["."]}, +{"flavor":"windows","text":"cUNC; UNC?.c","str":"cUNC; UNC?.c","parts":["cUNC; UNC?.c"],"absolute":false,"name":"cUNC; UNC?.c","parents":["."]}, +{"flavor":"windows","text":"?..","str":"?..","parts":["?.."],"absolute":false,"name":"?..","parents":["."]}, +{"flavor":"windows","text":" b ac../","str":" b ac..","parts":[" b ac.."],"absolute":false,"name":" b ac..","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"..\\?:x,y;\\\\\u00e9","str":"..\\?:x,y;\\\u00e9","parts":["..","?:x,y;","\u00e9"],"absolute":false,"name":"\u00e9","parents":["..\\?:x,y;","..","."]}, +{"flavor":"windows","text":"\\.unc/..","str":"\\.unc\\..","parts":["\\",".unc",".."],"absolute":false,"name":"..","parents":["\\.unc","\\"]}, +{"flavor":"windows","text":"/....\\\\ a\u00e9","str":"\\....\\ a\u00e9","parts":["\\","...."," a\u00e9"],"absolute":false,"name":" a\u00e9","parents":["\\....","\\"]}, +{"flavor":"windows","text":"/\u00e9UNCc..\\b","str":"\\\u00e9UNCc..\\b","parts":["\\","\u00e9UNCc..","b"],"absolute":false,"name":"b","parents":["\\\u00e9UNCc..","\\"]}, +{"flavor":"windows","text":"\\b","str":"\\b","parts":["\\","b"],"absolute":false,"name":"b","parents":["\\"]}, +{"flavor":"windows","text":".:x,y::UNCb","str":".:x,y::UNCb","parts":[".:","x,y::UNCb"],"absolute":false,"name":"x,y::UNCb","parents":[".:"]}, +{"flavor":"windows","text":"?:;b\u0000a","str":"?:;b\u0000a","parts":["?:",";b\u0000a"],"absolute":false,"name":";b\u0000a","parents":["?:"]}, +{"flavor":"windows","text":"bunc//:x,y","str":"bunc\\:x,y","parts":["bunc",":x,y"],"absolute":false,"name":":x,y","parents":["bunc","."]}, +{"flavor":"windows","text":"?\u0000\u0000/ \u00e9\u0000","str":"?\u0000\u0000\\ \u00e9\u0000","parts":["?\u0000\u0000"," \u00e9\u0000"],"absolute":false,"name":" \u00e9\u0000","parents":["?\u0000\u0000","."]}, +{"flavor":"windows","text":"//C.\u00e9.","str":"\\\\C.\u00e9.","parts":["\\\\C.\u00e9."],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"CUNC","str":"CUNC","parts":["CUNC"],"absolute":false,"name":"CUNC","parents":["."]}, +{"flavor":"windows","text":"a unc? C\\\\","str":"a unc? C","parts":["a unc? C"],"absolute":false,"name":"a unc? C","parents":["."]}, +{"flavor":"windows","text":"\\.","str":"\\","parts":["\\"],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"b","str":"b","parts":["b"],"absolute":false,"name":"b","parents":["."]}, +{"flavor":"windows","text":"x,yc;unc.. ","str":"x,yc;unc.. ","parts":["x,yc;unc.. "],"absolute":false,"name":"x,yc;unc.. ","parents":["."]}, +{"flavor":"windows","text":".x,y","str":".x,y","parts":[".x,y"],"absolute":false,"name":".x,y","parents":["."]}, +{"flavor":"windows","text":"\\\u0000.","str":"\\\u0000.","parts":["\\","\u0000."],"absolute":false,"name":"\u0000.","parents":["\\"]}, +{"flavor":"windows","text":";..C ","str":";..C ","parts":[";..C "],"absolute":false,"name":";..C ","parents":["."]}, +{"flavor":"windows","text":"\\unc;..?: ..","str":"\\unc;..?: ..","parts":["\\","unc;..?: .."],"absolute":false,"name":"unc;..?: ..","parents":["\\"]}, +{"flavor":"windows","text":" cunc \\","str":" cunc ","parts":[" cunc "],"absolute":false,"name":" cunc ","parents":["."]}, +{"flavor":"windows","text":"b","str":"b","parts":["b"],"absolute":false,"name":"b","parents":["."]}, +{"flavor":"windows","text":" c\u0000","str":" c\u0000","parts":[" c\u0000"],"absolute":false,"name":" c\u0000","parents":["."]}, +{"flavor":"windows","text":"\\\\","str":"\\\\","parts":["\\\\"],"absolute":true,"name":"","parents":[]}, +{"flavor":"windows","text":"C \\?","str":"C \\?","parts":["C ","?"],"absolute":false,"name":"?","parents":["C ","."]}, +{"flavor":"windows","text":"ca//C","str":"ca\\C","parts":["ca","C"],"absolute":false,"name":"C","parents":["ca","."]}, +{"flavor":"windows","text":"..","str":"..","parts":[".."],"absolute":false,"name":"..","parents":["."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"\u0000\u0000\\C\u00e9","str":"\u0000\u0000\\C\u00e9","parts":["\u0000\u0000","C\u00e9"],"absolute":false,"name":"C\u00e9","parents":["\u0000\u0000","."]}, +{"flavor":"windows","text":"","str":".","parts":[],"absolute":false,"name":"","parents":[]}, +{"flavor":"windows","text":"UNCbUNC","str":"UNCbUNC","parts":["UNCbUNC"],"absolute":false,"name":"UNCbUNC","parents":["."]}, +{"flavor":"windows","text":"ca\u00e9a","str":"ca\u00e9a","parts":["ca\u00e9a"],"absolute":false,"name":"ca\u00e9a","parents":["."]}, +{"flavor":"windows","text":";//;UNC","str":";\\;UNC","parts":[";",";UNC"],"absolute":false,"name":";UNC","parents":[";","."]}, +{"flavor":"windows","text":"cCx,yunc","str":"cCx,yunc","parts":["cCx,yunc"],"absolute":false,"name":"cCx,yunc","parents":["."]}, +{"flavor":"windows","text":"C","str":"C","parts":["C"],"absolute":false,"name":"C","parents":["."]}, +{"flavor":"windows","text":".//\u00e9?\u00e9b\u00e9","str":"\u00e9?\u00e9b\u00e9","parts":["\u00e9?\u00e9b\u00e9"],"absolute":false,"name":"\u00e9?\u00e9b\u00e9","parents":["."]}, +{"flavor":"windows","text":"\u00e9","str":"\u00e9","parts":["\u00e9"],"absolute":false,"name":"\u00e9","parents":["."]}, +{"flavor":"windows","text":":\\\\CUNCa:?unc","str":":\\CUNCa:?unc","parts":[":","CUNCa:?unc"],"absolute":false,"name":"CUNCa:?unc","parents":[":","."]}, +{"flavor":"windows","text":"UNC","str":"UNC","parts":["UNC"],"absolute":false,"name":"UNC","parents":["."]}, +{"flavor":"windows","text":" a..;/a ","str":" a..;\\a ","parts":[" a..;","a "],"absolute":false,"name":"a ","parents":[" a..;","."]}, +{"flavor":"windows","text":"\u00e9.c","str":"\u00e9.c","parts":["\u00e9.c"],"absolute":false,"name":"\u00e9.c","parents":["."]}, +{"flavor":"windows","text":"...//UNCb/a","str":"...\\UNCb\\a","parts":["...","UNCb","a"],"absolute":false,"name":"a","parents":["...\\UNCb","...","."]}, +{"flavor":"windows","text":"////;\\\\;\\\\//c","str":"\\\\\\\\;\\;\\c","parts":["\\\\\\\\",";",";","c"],"absolute":true,"name":"c","parents":["\\\\\\\\;\\;","\\\\\\\\;","\\\\\\\\"]}, +{"flavor":"windows","text":"b;..\u0000UNC\u0000unc","str":"b;..\u0000UNC\u0000unc","parts":["b;..\u0000UNC\u0000unc"],"absolute":false,"name":"b;..\u0000UNC\u0000unc","parents":["."]}, +{"flavor":"windows","text":"?","str":"?","parts":["?"],"absolute":false,"name":"?","parents":["."]} +], +"joins": [ +{"flavor":"posix","left":"/a/b","right":"c","str":"/a/b/c"}, +{"flavor":"posix","left":"/a/b","right":"/c","str":"/c"}, +{"flavor":"posix","left":"C:\\cwd","right":"a/b","str":"C:\\cwd/a/b"}, +{"flavor":"posix","left":"C:\\cwd","right":"C:x","str":"C:\\cwd/C:x"}, +{"flavor":"posix","left":"C:\\cwd","right":"c:x","str":"C:\\cwd/c:x"}, +{"flavor":"posix","left":"C:\\cwd","right":"D:x","str":"C:\\cwd/D:x"}, +{"flavor":"posix","left":"C:\\cwd","right":"\\x","str":"C:\\cwd/\\x"}, +{"flavor":"posix","left":"//server/share","right":"x","str":"//server/share/x"}, +{"flavor":"posix","left":"C:","right":"x","str":"C:/x"}, +{"flavor":"posix","left":"","right":"x","str":"x"}, +{"flavor":"posix","left":"a","right":"","str":"a"}, +{"flavor":"posix","left":"","right":"a","str":"a"}, +{"flavor":"posix","left":"\\\\","right":"Cb","str":"\\\\/Cb"}, +{"flavor":"posix","left":"/","right":"a\\\\..","str":"/a\\\\.."}, +{"flavor":"posix","left":"b/CcUNC","right":"//x,yba","str":"//x,yba"}, +{"flavor":"posix","left":"b..unc","right":"c\u0000\u00e9//","str":"b..unc/c\u0000\u00e9"}, +{"flavor":"posix","left":"x,y","right":".\u00e9..\u0000","str":"x,y/.\u00e9..\u0000"}, +{"flavor":"posix","left":"?","right":"","str":"?"}, +{"flavor":"posix","left":"/://\u00e9:c","right":"cCa","str":"/:/\u00e9:c/cCa"}, +{"flavor":"posix","left":" ","right":"\\\\CUNCCunc","str":" /\\\\CUNCCunc"}, +{"flavor":"posix","left":".;aUNC..x,y","right":"c..\u0000:;","str":".;aUNC..x,y/c..\u0000:;"}, +{"flavor":"posix","left":"Cc....;..","right":"\\\\;x,y\\\\unc;","str":"Cc....;../\\\\;x,y\\\\unc;"}, +{"flavor":"posix","left":"a","right":"\u00e9/","str":"a/\u00e9"}, +{"flavor":"posix","left":"/UNCuncC?unc","right":"//unc\u00e9??","str":"//unc\u00e9??"}, +{"flavor":"posix","left":"/\u00e9;b\u0000\u00e9","right":";Cb","str":"/\u00e9;b\u0000\u00e9/;Cb"}, +{"flavor":"posix","left":" a\\.//","right":"cbC/","str":" a\\./cbC"}, +{"flavor":"posix","left":":","right":"","str":":"}, +{"flavor":"posix","left":"/.unc","right":"C","str":"/.unc/C"}, +{"flavor":"posix","left":"\\\\//?","right":"\\\\","str":"\\\\/?/\\\\"}, +{"flavor":"posix","left":"cx,y....\\:","right":"//","str":"//"}, +{"flavor":"posix","left":"b;\\\\unc","right":"\u00e9b//:.","str":"b;\\\\unc/\u00e9b/:."}, +{"flavor":"posix","left":".b\\\\","right":"a?unc/:b","str":".b\\\\/a?unc/:b"}, +{"flavor":"posix","left":"\u00e9;\\uncb..","right":"a..:","str":"\u00e9;\\uncb../a..:"}, +{"flavor":"posix","left":"cunca","right":":","str":"cunca/:"}, +{"flavor":"posix","left":" \u0000?//","right":"\u00e9?..;","str":" \u0000?/\u00e9?..;"}, +{"flavor":"posix","left":"","right":";//UNCx,ya","str":";/UNCx,ya"}, +{"flavor":"posix","left":"","right":"\\\u0000\u00e9.","str":"\\\u0000\u00e9."}, +{"flavor":"posix","left":"\\\\\\\\\u0000","right":";\u0000 ","str":"\\\\\\\\\u0000/;\u0000 "}, +{"flavor":"posix","left":"unc","right":".\\?//","str":"unc/.\\?"}, +{"flavor":"posix","left":"\u00e9.b\u00e9\\","right":";.../.","str":"\u00e9.b\u00e9\\/;..."}, +{"flavor":"posix","left":"unc:","right":"c\\\\","str":"unc:/c\\\\"}, +{"flavor":"posix","left":"unc/","right":"","str":"unc"}, +{"flavor":"posix","left":"cUNCC\\.","right":"","str":"cUNCC\\."}, +{"flavor":"posix","left":"","right":"x,yac","str":"x,yac"}, +{"flavor":"posix","left":"\\\\\u00e9;unc/","right":"a\\\u0000x,y..","str":"\\\\\u00e9;unc/a\\\u0000x,y.."}, +{"flavor":"posix","left":"..;//\u0000x,y","right":"/C;b\u00e9","str":"/C;b\u00e9"}, +{"flavor":"posix","left":"..;x,y//","right":"\u0000","str":"..;x,y/\u0000"}, +{"flavor":"posix","left":"\u0000","right":"aa//x,y","str":"\u0000/aa/x,y"}, +{"flavor":"posix","left":"?/","right":"\\\\\\\\;","str":"?/\\\\\\\\;"}, +{"flavor":"posix","left":"\u0000","right":"\\","str":"\u0000/\\"}, +{"flavor":"posix","left":"\u0000/\u0000UNC\u0000","right":"\u0000?....\u00e9C","str":"\u0000/\u0000UNC\u0000/\u0000?....\u00e9C"}, +{"flavor":"posix","left":"\u00e9//","right":".///x,y\u00e9.","str":"\u00e9/x,y\u00e9."}, +{"flavor":"posix","left":";","right":"..","str":";/.."}, +{"flavor":"posix","left":".","right":"unc\\\\unc\\\\UNC\\","str":"unc\\\\unc\\\\UNC\\"}, +{"flavor":"posix","left":"..","right":"/;;","str":"/;;"}, +{"flavor":"posix","left":"..","right":"\\","str":"../\\"}, +{"flavor":"posix","left":"c//C\\\\","right":"//a","str":"//a"}, +{"flavor":"posix","left":"unc:","right":"c","str":"unc:/c"}, +{"flavor":"posix","left":"?//.","right":"unc","str":"?/unc"}, +{"flavor":"posix","left":"\u00e9a//c\\\\","right":"C","str":"\u00e9a/c\\\\/C"}, +{"flavor":"posix","left":"","right":"\\","str":"\\"}, +{"flavor":"posix","left":":c:","right":". ","str":":c:/. "}, +{"flavor":"posix","left":"","right":"...;?","str":"...;?"}, +{"flavor":"posix","left":":\\b","right":"UNC\\///a","str":":\\b/UNC\\/a"}, +{"flavor":"posix","left":"c..;b","right":"C\u0000c","str":"c..;b/C\u0000c"}, +{"flavor":"posix","left":"::UNC.b","right":"x,y;","str":"::UNC.b/x,y;"}, +{"flavor":"posix","left":"Cx,yca","right":":.cC","str":"Cx,yca/:.cC"}, +{"flavor":"posix","left":"b.","right":"","str":"b."}, +{"flavor":"posix","left":"C..//","right":"...//\u00e9\u00e9","str":"C../.../\u00e9\u00e9"}, +{"flavor":"posix","left":"..x,y","right":"\u00e9:\\x,yUNC","str":"..x,y/\u00e9:\\x,yUNC"}, +{"flavor":"posix","left":".//..C?","right":"\u0000a","str":"..C?/\u0000a"}, +{"flavor":"posix","left":"unc\u00e9;.uncunc","right":" ..b:a","str":"unc\u00e9;.uncunc/ ..b:a"}, +{"flavor":"posix","left":" \u00e9\\\\\\\\ca","right":"\u00e9\u0000","str":" \u00e9\\\\\\\\ca/\u00e9\u0000"}, +{"flavor":"posix","left":"..\u0000?Cb.","right":"\u00e9UNC","str":"..\u0000?Cb./\u00e9UNC"}, +{"flavor":"posix","left":"\\\u00e9//unc","right":"///\u00e9\\..","str":"/\u00e9\\.."}, +{"flavor":"posix","left":"","right":"\u0000::","str":"\u0000::"}, +{"flavor":"posix","left":"UNC\\\\x,y","right":"a\\unc\u0000","str":"UNC\\\\x,y/a\\unc\u0000"}, +{"flavor":"posix","left":"","right":" ","str":" "}, +{"flavor":"posix","left":"..\\\\/.unc","right":"\\\\..C;","str":"..\\\\/.unc/\\\\..C;"}, +{"flavor":"posix","left":"","right":":a?Cc","str":":a?Cc"}, +{"flavor":"posix","left":".:","right":"\u00e9?","str":".:/\u00e9?"}, +{"flavor":"posix","left":"","right":"aCx,y","str":"aCx,y"}, +{"flavor":"posix","left":"aunc","right":"","str":"aunc"}, +{"flavor":"posix","left":"unc\u00e9;unc","right":"..\u0000\u00e9","str":"unc\u00e9;unc/..\u0000\u00e9"}, +{"flavor":"posix","left":"a. ","right":".b../b","str":"a. /.b../b"}, +{"flavor":"posix","left":"bc\\\\\u0000","right":"..\u0000 .\u0000","str":"bc\\\\\u0000/..\u0000 .\u0000"}, +{"flavor":"posix","left":"x,y","right":"b\\?/","str":"x,y/b\\?"}, +{"flavor":"posix","left":":x,ya","right":"?UNC..unc","str":":x,ya/?UNC..unc"}, +{"flavor":"posix","left":"...UNCunc","right":"\u0000\u00e9\\a","str":"...UNCunc/\u0000\u00e9\\a"}, +{"flavor":"posix","left":"\\\\:UNC\u0000?b","right":"","str":"\\\\:UNC\u0000?b"}, +{"flavor":"posix","left":"cx,y:a: ","right":" UNCUNC\u0000","str":"cx,y:a: / UNCUNC\u0000"}, +{"flavor":"posix","left":"a bx,y\\\\","right":"b:","str":"a bx,y\\\\/b:"}, +{"flavor":"posix","left":":","right":"C\u0000unc;c","str":":/C\u0000unc;c"}, +{"flavor":"posix","left":"\u00e9UNC","right":" C..a\\\\\u00e9","str":"\u00e9UNC/ C..a\\\\\u00e9"}, +{"flavor":"posix","left":"C..?//","right":"c:c\u00e9","str":"C..?/c:c\u00e9"}, +{"flavor":"posix","left":"\\:c?\\\\","right":"..;;c\u0000 ","str":"\\:c?\\\\/..;;c\u0000 "}, +{"flavor":"posix","left":"\\x,y. ; ","right":"unc","str":"\\x,y. ; /unc"}, +{"flavor":"posix","left":"\\\\\\..","right":"","str":"\\\\\\.."}, +{"flavor":"posix","left":"c.\u0000","right":"cx,yC","str":"c.\u0000/cx,yC"}, +{"flavor":"posix","left":"uncb","right":"b//UNC .;","str":"uncb/b/UNC .;"}, +{"flavor":"posix","left":"?/","right":"\\","str":"?/\\"}, +{"flavor":"posix","left":"\\\u0000b","right":";UNC\\//","str":"\\\u0000b/;UNC\\"}, +{"flavor":"posix","left":"x,yuncUNC","right":"\u00e9\\\\UNC","str":"x,yuncUNC/\u00e9\\\\UNC"}, +{"flavor":"posix","left":"\u0000","right":"?\u0000a?","str":"\u0000/?\u0000a?"}, +{"flavor":"posix","left":"","right":";C\\\\UNC","str":";C\\\\UNC"}, +{"flavor":"posix","left":"?/x,y","right":"a//","str":"?/x,y/a"}, +{"flavor":"posix","left":".\u00e9.;unc","right":"\u0000","str":".\u00e9.;unc/\u0000"}, +{"flavor":"posix","left":"unccunc \\UNC","right":"","str":"unccunc \\UNC"}, +{"flavor":"posix","left":"caUNC;..UNC","right":"UNCx,y:.\u0000","str":"caUNC;..UNC/UNCx,y:.\u0000"}, +{"flavor":"posix","left":"","right":"UNC;\\:\u00e9","str":"UNC;\\:\u00e9"}, +{"flavor":"posix","left":"b?c","right":"\u0000","str":"b?c/\u0000"}, +{"flavor":"posix","left":"a","right":"\\\\\\b\\\\?","str":"a/\\\\\\b\\\\?"}, +{"flavor":"posix","left":":\u0000..c","right":"....","str":":\u0000..c/...."}, +{"flavor":"posix","left":"?\u0000\\\\unc","right":":\\a\u00e9unc","str":"?\u0000\\\\unc/:\\a\u00e9unc"}, +{"flavor":"posix","left":"?UNC.","right":"","str":"?UNC."}, +{"flavor":"posix","left":"x,y\u00e9","right":"x,y","str":"x,y\u00e9/x,y"}, +{"flavor":"posix","left":"//;b\\b","right":"\u0000\u0000\\\\x,y.c","str":"//;b\\b/\u0000\u0000\\\\x,y.c"}, +{"flavor":"posix","left":"UNC\\","right":" x,y:?","str":"UNC\\/ x,y:?"}, +{"flavor":"posix","left":"","right":" x,y?\\","str":" x,y?\\"}, +{"flavor":"posix","left":";/\\//","right":"x,y","str":";/\\/x,y"}, +{"flavor":"posix","left":";\u0000\\/;","right":"b/a","str":";\u0000\\/;/b/a"}, +{"flavor":"posix","left":"aac/","right":"...cUNC","str":"aac/...cUNC"}, +{"flavor":"posix","left":"b\u0000","right":"c\u00e9","str":"b\u0000/c\u00e9"}, +{"flavor":"posix","left":"","right":"\\C\u00e9\\\\CC","str":"\\C\u00e9\\\\CC"}, +{"flavor":"posix","left":"\u00e9","right":" UNC/uncc","str":"\u00e9/ UNC/uncc"}, +{"flavor":"posix","left":"","right":" \\\\;","str":" \\\\;"}, +{"flavor":"posix","left":"C\\UNC","right":"","str":"C\\UNC"}, +{"flavor":"posix","left":" //\\:unc","right":"UNCb;..;","str":" /\\:unc/UNCb;..;"}, +{"flavor":"posix","left":":","right":"unc\\uncunc","str":":/unc\\uncunc"}, +{"flavor":"posix","left":"/UNC\\\\b:","right":"?x,ybc\\\\UNC","str":"/UNC\\\\b:/?x,ybc\\\\UNC"}, +{"flavor":"posix","left":"ac\u00e9/c","right":"","str":"ac\u00e9/c"}, +{"flavor":"posix","left":"","right":"","str":"."}, +{"flavor":"posix","left":"x,yunc","right":"\\\\//////","str":"x,yunc/\\\\"}, +{"flavor":"posix","left":"///","right":"x,y////UNCb","str":"/x,y/UNCb"}, +{"flavor":"posix","left":"b","right":"\\\\Cunc\u0000","str":"b/\\\\Cunc\u0000"}, +{"flavor":"posix","left":"\\\\:a","right":"/b..\\/","str":"/b..\\"}, +{"flavor":"posix","left":"..a..x,yunc","right":"..\\\\;//\u00e9unc","str":"..a..x,yunc/..\\\\;/\u00e9unc"}, +{"flavor":"posix","left":"","right":";\u0000","str":";\u0000"}, +{"flavor":"posix","left":"c/\\","right":":","str":"c/\\/:"}, +{"flavor":"posix","left":"x,y","right":"x,y:","str":"x,y/x,y:"}, +{"flavor":"posix","left":"","right":"x,y:.c","str":"x,y:.c"}, +{"flavor":"posix","left":"","right":"..?b","str":"..?b"}, +{"flavor":"posix","left":"..;uncaa","right":"\\\\uncuncb\u00e9","str":"..;uncaa/\\\\uncuncb\u00e9"}, +{"flavor":"posix","left":"","right":"\\\\..x,yc","str":"\\\\..x,yc"}, +{"flavor":"posix","left":"\u00e9unc\\??","right":"unc","str":"\u00e9unc\\??/unc"}, +{"flavor":"posix","left":"UNCC\u00e9","right":"://\\UNCUNC","str":"UNCC\u00e9/:/\\UNCUNC"}, +{"flavor":"posix","left":"\\ ","right":" \u0000a..","str":"\\ / \u0000a.."}, +{"flavor":"posix","left":"unc\\?","right":"\\\\?b ","str":"unc\\?/\\\\?b "}, +{"flavor":"posix","left":" ///.unc","right":"/x,y..","str":"/x,y.."}, +{"flavor":"posix","left":":aC","right":"://a//\\\\","str":":aC/:/a/\\\\"}, +{"flavor":"posix","left":" ::","right":"","str":" ::"}, +{"flavor":"posix","left":" UNC:\u00e9","right":"","str":" UNC:\u00e9"}, +{"flavor":"posix","left":"\\x,y..","right":"\\ .:UNC","str":"\\x,y../\\ .:UNC"}, +{"flavor":"posix","left":" \u0000x,y;;unc","right":"unc\\","str":" \u0000x,y;;unc/unc\\"}, +{"flavor":"posix","left":"","right":"\\\\;uncCx,y.","str":"\\\\;uncCx,y."}, +{"flavor":"posix","left":"","right":";bx,y\u0000//UNC","str":";bx,y\u0000/UNC"}, +{"flavor":"posix","left":"","right":"?.C..","str":"?.C.."}, +{"flavor":"posix","left":"","right":"bC\\\\/\\\\","str":"bC\\\\/\\\\"}, +{"flavor":"posix","left":";C","right":":","str":";C/:"}, +{"flavor":"posix","left":"//a\\\\uncb?","right":".\u00e9b\u00e9?\u0000","str":"//a\\\\uncb?/.\u00e9b\u00e9?\u0000"}, +{"flavor":"posix","left":"\\\u00e9b","right":"\\b","str":"\\\u00e9b/\\b"}, +{"flavor":"posix","left":";:;:","right":"","str":";:;:"}, +{"flavor":"posix","left":"\u00e9UNC","right":"\\\\:\\\\\u00e9\u00e9","str":"\u00e9UNC/\\\\:\\\\\u00e9\u00e9"}, +{"flavor":"posix","left":"\\/\\","right":"","str":"\\/\\"}, +{"flavor":"posix","left":"a","right":" ","str":"a/ "}, +{"flavor":"posix","left":".","right":"?\\UNCUNC\\","str":"?\\UNCUNC\\"}, +{"flavor":"posix","left":"b\u00e9","right":"","str":"b\u00e9"}, +{"flavor":"posix","left":"//C","right":"C","str":"//C/C"}, +{"flavor":"posix","left":"unc// ","right":"C","str":"unc/ /C"}, +{"flavor":"posix","left":".c\u0000","right":"C","str":".c\u0000/C"}, +{"flavor":"posix","left":"/","right":"...C\u0000a","str":"/...C\u0000a"}, +{"flavor":"posix","left":"x,yC","right":"b;cccc","str":"x,yC/b;cccc"}, +{"flavor":"posix","left":"?.","right":"//\\x,y;x,y","str":"//\\x,y;x,y"}, +{"flavor":"posix","left":"//C ?cx,y","right":"?","str":"//C ?cx,y/?"}, +{"flavor":"posix","left":" \\\\","right":"\u00e9\\\\\u0000C\\..","str":" \\\\/\u00e9\\\\\u0000C\\.."}, +{"flavor":"posix","left":"\u0000:b","right":"unc","str":"\u0000:b/unc"}, +{"flavor":"posix","left":";a;;?","right":"uncx,yb:","str":";a;;?/uncx,yb:"}, +{"flavor":"posix","left":"\u0000.","right":"c\\","str":"\u0000./c\\"}, +{"flavor":"posix","left":" ?//","right":"\\x,y/","str":" ?/\\x,y"}, +{"flavor":"posix","left":" ","right":"\\\\b://\u0000\\\\","str":" /\\\\b:/\u0000\\\\"}, +{"flavor":"posix","left":";","right":"x,y\\\\","str":";/x,y\\\\"}, +{"flavor":"posix","left":"\\\\x,y/c.","right":"c\\\\\\","str":"\\\\x,y/c./c\\\\\\"}, +{"flavor":"posix","left":"\u0000c?;","right":"\u00e9unc/\u00e9x,y","str":"\u0000c?;/\u00e9unc/\u00e9x,y"}, +{"flavor":"posix","left":" C","right":"","str":" C"}, +{"flavor":"posix","left":"","right":";;a","str":";;a"}, +{"flavor":"posix","left":".","right":"b..","str":"b.."}, +{"flavor":"posix","left":"","right":"\u00e9/unc","str":"\u00e9/unc"}, +{"flavor":"posix","left":"x,yUNC","right":"c\u00e9\u0000","str":"x,yUNC/c\u00e9\u0000"}, +{"flavor":"posix","left":"; \\\\b","right":"","str":"; \\\\b"}, +{"flavor":"posix","left":"","right":":b;","str":":b;"}, +{"flavor":"posix","left":"\u00e9\\\\..","right":"///","str":"/"}, +{"flavor":"posix","left":"\\x,y\u0000ax,y","right":"unc.\\","str":"\\x,y\u0000ax,y/unc.\\"}, +{"flavor":"posix","left":"","right":"","str":"."}, +{"flavor":"posix","left":"unc","right":"UNCbUNCb:\u0000","str":"unc/UNCbUNCb:\u0000"}, +{"flavor":"posix","left":"..UNC..UNCb","right":"\\;C/","str":"..UNC..UNCb/\\;C"}, +{"flavor":"posix","left":"\u0000..\\;///","right":"/a?","str":"/a?"}, +{"flavor":"posix","left":"..UNC?","right":"","str":"..UNC?"}, +{"flavor":"posix","left":"/ x,y\\","right":"\u00e9..a","str":"/ x,y\\/\u00e9..a"}, +{"flavor":"posix","left":"\\ab","right":";x,yb/","str":"\\ab/;x,yb"}, +{"flavor":"posix","left":";UNC//","right":"\u0000 ","str":";UNC/\u0000 "}, +{"flavor":"posix","left":"UNC?UNCca;","right":"UNC","str":"UNC?UNCca;/UNC"}, +{"flavor":"posix","left":"b","right":"//C","str":"//C"}, +{"flavor":"posix","left":"uncc /UNC:","right":" Cc","str":"uncc /UNC:/ Cc"}, +{"flavor":"posix","left":".;?","right":":C\\\\;/","str":".;?/:C\\\\;"}, +{"flavor":"posix","left":"x,yc;//","right":"\u00e9\u00e9x,y","str":"x,yc;/\u00e9\u00e9x,y"}, +{"flavor":"posix","left":"b?a.\u0000","right":"//\u00e9..\\;?","str":"//\u00e9..\\;?"}, +{"flavor":"posix","left":"?:///.\\\\","right":"\u0000:a/","str":"?:/.\\\\/\u0000:a"}, +{"flavor":"posix","left":"","right":"..Cunc..","str":"..Cunc.."}, +{"flavor":"posix","left":";","right":"UNCc","str":";/UNCc"}, +{"flavor":"posix","left":"","right":"C.;unc\\","str":"C.;unc\\"}, +{"flavor":"posix","left":";.x,y..","right":"//\u00e9UNC;?","str":"//\u00e9UNC;?"}, +{"flavor":"windows","left":"/a/b","right":"c","str":"\\a\\b\\c"}, +{"flavor":"windows","left":"/a/b","right":"/c","str":"\\c"}, +{"flavor":"windows","left":"C:\\cwd","right":"a/b","str":"C:\\cwd\\a\\b"}, +{"flavor":"windows","left":"C:\\cwd","right":"C:x","str":"C:\\cwd\\x"}, +{"flavor":"windows","left":"C:\\cwd","right":"c:x","str":"c:\\cwd\\x"}, +{"flavor":"windows","left":"C:\\cwd","right":"D:x","str":"D:x"}, +{"flavor":"windows","left":"C:\\cwd","right":"\\x","str":"C:\\x"}, +{"flavor":"windows","left":"//server/share","right":"x","str":"\\\\server\\share\\x"}, +{"flavor":"windows","left":"C:","right":"x","str":"C:x"}, +{"flavor":"windows","left":"","right":"x","str":"x"}, +{"flavor":"windows","left":"a","right":"","str":"a"}, +{"flavor":"windows","left":"","right":"a","str":"a"}, +{"flavor":"windows","left":"\\\\","right":"Cb","str":"\\\\Cb"}, +{"flavor":"windows","left":"/","right":"a\\\\..","str":"\\a\\.."}, +{"flavor":"windows","left":"b/CcUNC","right":"//x,yba","str":"\\\\x,yba"}, +{"flavor":"windows","left":"b..unc","right":"c\u0000\u00e9//","str":"b..unc\\c\u0000\u00e9"}, +{"flavor":"windows","left":"x,y","right":".\u00e9..\u0000","str":"x,y\\.\u00e9..\u0000"}, +{"flavor":"windows","left":"?","right":"","str":"?"}, +{"flavor":"windows","left":"/://\u00e9:c","right":"cCa","str":"\\:\\\u00e9:c\\cCa"}, +{"flavor":"windows","left":" ","right":"\\\\CUNCCunc","str":"\\\\CUNCCunc"}, +{"flavor":"windows","left":".;aUNC..x,y","right":"c..\u0000:;","str":".;aUNC..x,y\\c..\u0000:;"}, +{"flavor":"windows","left":"Cc....;..","right":"\\\\;x,y\\\\unc;","str":"\\\\;x,y\\\\unc;"}, +{"flavor":"windows","left":"a","right":"\u00e9/","str":"a\\\u00e9"}, +{"flavor":"windows","left":"/UNCuncC?unc","right":"//unc\u00e9??","str":"\\\\unc\u00e9??"}, +{"flavor":"windows","left":"/\u00e9;b\u0000\u00e9","right":";Cb","str":"\\\u00e9;b\u0000\u00e9\\;Cb"}, +{"flavor":"windows","left":" a\\.//","right":"cbC/","str":" a\\cbC"}, +{"flavor":"windows","left":":","right":"","str":":"}, +{"flavor":"windows","left":"/.unc","right":"C","str":"\\.unc\\C"}, +{"flavor":"windows","left":"\\\\//?","right":"\\\\","str":"\\\\"}, +{"flavor":"windows","left":"cx,y....\\:","right":"//","str":"\\\\"}, +{"flavor":"windows","left":"b;\\\\unc","right":"\u00e9b//:.","str":"b;\\unc\\\u00e9b\\:."}, +{"flavor":"windows","left":".b\\\\","right":"a?unc/:b","str":".b\\a?unc\\:b"}, +{"flavor":"windows","left":"\u00e9;\\uncb..","right":"a..:","str":"\u00e9;\\uncb..\\a..:"}, +{"flavor":"windows","left":"cunca","right":":","str":"cunca\\:"}, +{"flavor":"windows","left":" \u0000?//","right":"\u00e9?..;","str":" \u0000?\\\u00e9?..;"}, +{"flavor":"windows","left":"","right":";//UNCx,ya","str":";\\UNCx,ya"}, +{"flavor":"windows","left":"","right":"\\\u0000\u00e9.","str":"\\\u0000\u00e9."}, +{"flavor":"windows","left":"\\\\\\\\\u0000","right":";\u0000 ","str":"\\\\\\\\\u0000\\;\u0000 "}, +{"flavor":"windows","left":"unc","right":".\\?//","str":"unc\\?"}, +{"flavor":"windows","left":"\u00e9.b\u00e9\\","right":";.../.","str":"\u00e9.b\u00e9\\;..."}, +{"flavor":"windows","left":"unc:","right":"c\\\\","str":"unc:\\c"}, +{"flavor":"windows","left":"unc/","right":"","str":"unc"}, +{"flavor":"windows","left":"cUNCC\\.","right":"","str":"cUNCC"}, +{"flavor":"windows","left":"","right":"x,yac","str":"x,yac"}, +{"flavor":"windows","left":"\\\\\u00e9;unc/","right":"a\\\u0000x,y..","str":"\\\\\u00e9;unc\\a\\\u0000x,y.."}, +{"flavor":"windows","left":"..;//\u0000x,y","right":"/C;b\u00e9","str":"\\C;b\u00e9"}, +{"flavor":"windows","left":"..;x,y//","right":"\u0000","str":"..;x,y\\\u0000"}, +{"flavor":"windows","left":"\u0000","right":"aa//x,y","str":"\u0000\\aa\\x,y"}, +{"flavor":"windows","left":"?/","right":"\\\\\\\\;","str":"\\\\\\\\;"}, +{"flavor":"windows","left":"\u0000","right":"\\","str":"\\"}, +{"flavor":"windows","left":"\u0000/\u0000UNC\u0000","right":"\u0000?....\u00e9C","str":"\u0000\\\u0000UNC\u0000\\\u0000?....\u00e9C"}, +{"flavor":"windows","left":"\u00e9//","right":".///x,y\u00e9.","str":"\u00e9\\x,y\u00e9."}, +{"flavor":"windows","left":";","right":"..","str":";\\.."}, +{"flavor":"windows","left":".","right":"unc\\\\unc\\\\UNC\\","str":"unc\\unc\\UNC"}, +{"flavor":"windows","left":"..","right":"/;;","str":"\\;;"}, +{"flavor":"windows","left":"..","right":"\\","str":"\\"}, +{"flavor":"windows","left":"c//C\\\\","right":"//a","str":"\\\\a"}, +{"flavor":"windows","left":"unc:","right":"c","str":"unc:\\c"}, +{"flavor":"windows","left":"?//.","right":"unc","str":"?\\unc"}, +{"flavor":"windows","left":"\u00e9a//c\\\\","right":"C","str":"\u00e9a\\c\\C"}, +{"flavor":"windows","left":"","right":"\\","str":"\\"}, +{"flavor":"windows","left":":c:","right":". ","str":":c:\\. "}, +{"flavor":"windows","left":"","right":"...;?","str":"...;?"}, +{"flavor":"windows","left":":\\b","right":"UNC\\///a","str":":\\b\\UNC\\a"}, +{"flavor":"windows","left":"c..;b","right":"C\u0000c","str":"c..;b\\C\u0000c"}, +{"flavor":"windows","left":"::UNC.b","right":"x,y;","str":"::UNC.b\\x,y;"}, +{"flavor":"windows","left":"Cx,yca","right":":.cC","str":"Cx,yca\\:.cC"}, +{"flavor":"windows","left":"b.","right":"","str":"b."}, +{"flavor":"windows","left":"C..//","right":"...//\u00e9\u00e9","str":"C..\\...\\\u00e9\u00e9"}, +{"flavor":"windows","left":"..x,y","right":"\u00e9:\\x,yUNC","str":"\u00e9:\\x,yUNC"}, +{"flavor":"windows","left":".//..C?","right":"\u0000a","str":"..C?\\\u0000a"}, +{"flavor":"windows","left":"unc\u00e9;.uncunc","right":" ..b:a","str":"unc\u00e9;.uncunc\\ ..b:a"}, +{"flavor":"windows","left":" \u00e9\\\\\\\\ca","right":"\u00e9\u0000","str":" \u00e9\\ca\\\u00e9\u0000"}, +{"flavor":"windows","left":"..\u0000?Cb.","right":"\u00e9UNC","str":"..\u0000?Cb.\\\u00e9UNC"}, +{"flavor":"windows","left":"\\\u00e9//unc","right":"///\u00e9\\..","str":"\\\\\\\u00e9\\.."}, +{"flavor":"windows","left":"","right":"\u0000::","str":"\u0000::"}, +{"flavor":"windows","left":"UNC\\\\x,y","right":"a\\unc\u0000","str":"UNC\\x,y\\a\\unc\u0000"}, +{"flavor":"windows","left":"","right":" ","str":" "}, +{"flavor":"windows","left":"..\\\\/.unc","right":"\\\\..C;","str":"\\\\..C;"}, +{"flavor":"windows","left":"","right":":a?Cc","str":":a?Cc"}, +{"flavor":"windows","left":".:","right":"\u00e9?","str":".:\u00e9?"}, +{"flavor":"windows","left":"","right":"aCx,y","str":"aCx,y"}, +{"flavor":"windows","left":"aunc","right":"","str":"aunc"}, +{"flavor":"windows","left":"unc\u00e9;unc","right":"..\u0000\u00e9","str":"unc\u00e9;unc\\..\u0000\u00e9"}, +{"flavor":"windows","left":"a. ","right":".b../b","str":"a. \\.b..\\b"}, +{"flavor":"windows","left":"bc\\\\\u0000","right":"..\u0000 .\u0000","str":"bc\\\u0000\\..\u0000 .\u0000"}, +{"flavor":"windows","left":"x,y","right":"b\\?/","str":"x,y\\b\\?"}, +{"flavor":"windows","left":":x,ya","right":"?UNC..unc","str":":x,ya\\?UNC..unc"}, +{"flavor":"windows","left":"...UNCunc","right":"\u0000\u00e9\\a","str":"...UNCunc\\\u0000\u00e9\\a"}, +{"flavor":"windows","left":"\\\\:UNC\u0000?b","right":"","str":"\\\\:UNC\u0000?b"}, +{"flavor":"windows","left":"cx,y:a: ","right":" UNCUNC\u0000","str":"cx,y:a: \\ UNCUNC\u0000"}, +{"flavor":"windows","left":"a bx,y\\\\","right":"b:","str":"b:"}, +{"flavor":"windows","left":":","right":"C\u0000unc;c","str":":\\C\u0000unc;c"}, +{"flavor":"windows","left":"\u00e9UNC","right":" C..a\\\\\u00e9","str":"\u00e9UNC\\ C..a\\\u00e9"}, +{"flavor":"windows","left":"C..?//","right":"c:c\u00e9","str":"c:c\u00e9"}, +{"flavor":"windows","left":"\\:c?\\\\","right":"..;;c\u0000 ","str":"\\:c?\\..;;c\u0000 "}, +{"flavor":"windows","left":"\\x,y. ; ","right":"unc","str":"\\x,y. ; \\unc"}, +{"flavor":"windows","left":"\\\\\\..","right":"","str":"\\\\\\.."}, +{"flavor":"windows","left":"c.\u0000","right":"cx,yC","str":"c.\u0000\\cx,yC"}, +{"flavor":"windows","left":"uncb","right":"b//UNC .;","str":"uncb\\b\\UNC .;"}, +{"flavor":"windows","left":"?/","right":"\\","str":"\\"}, +{"flavor":"windows","left":"\\\u0000b","right":";UNC\\//","str":"\\\u0000b\\;UNC"}, +{"flavor":"windows","left":"x,yuncUNC","right":"\u00e9\\\\UNC","str":"x,yuncUNC\\\u00e9\\UNC"}, +{"flavor":"windows","left":"\u0000","right":"?\u0000a?","str":"\u0000\\?\u0000a?"}, +{"flavor":"windows","left":"","right":";C\\\\UNC","str":";C\\UNC"}, +{"flavor":"windows","left":"?/x,y","right":"a//","str":"?\\x,y\\a"}, +{"flavor":"windows","left":".\u00e9.;unc","right":"\u0000","str":".\u00e9.;unc\\\u0000"}, +{"flavor":"windows","left":"unccunc \\UNC","right":"","str":"unccunc \\UNC"}, +{"flavor":"windows","left":"caUNC;..UNC","right":"UNCx,y:.\u0000","str":"caUNC;..UNC\\UNCx,y:.\u0000"}, +{"flavor":"windows","left":"","right":"UNC;\\:\u00e9","str":"UNC;\\:\u00e9"}, +{"flavor":"windows","left":"b?c","right":"\u0000","str":"b?c\\\u0000"}, +{"flavor":"windows","left":"a","right":"\\\\\\b\\\\?","str":"\\\\\\b\\?"}, +{"flavor":"windows","left":":\u0000..c","right":"....","str":":\u0000..c\\...."}, +{"flavor":"windows","left":"?\u0000\\\\unc","right":":\\a\u00e9unc","str":"?\u0000\\unc\\:\\a\u00e9unc"}, +{"flavor":"windows","left":"?UNC.","right":"","str":"?UNC."}, +{"flavor":"windows","left":"x,y\u00e9","right":"x,y","str":"x,y\u00e9\\x,y"}, +{"flavor":"windows","left":"//;b\\b","right":"\u0000\u0000\\\\x,y.c","str":"\\\\;b\\b\\\u0000\u0000\\x,y.c"}, +{"flavor":"windows","left":"UNC\\","right":" x,y:?","str":"UNC\\ x,y:?"}, +{"flavor":"windows","left":"","right":" x,y?\\","str":" x,y?"}, +{"flavor":"windows","left":";/\\//","right":"x,y","str":";\\x,y"}, +{"flavor":"windows","left":";\u0000\\/;","right":"b/a","str":";\u0000\\;\\b\\a"}, +{"flavor":"windows","left":"aac/","right":"...cUNC","str":"aac\\...cUNC"}, +{"flavor":"windows","left":"b\u0000","right":"c\u00e9","str":"b\u0000\\c\u00e9"}, +{"flavor":"windows","left":"","right":"\\C\u00e9\\\\CC","str":"\\C\u00e9\\CC"}, +{"flavor":"windows","left":"\u00e9","right":" UNC/uncc","str":"\u00e9\\ UNC\\uncc"}, +{"flavor":"windows","left":"","right":" \\\\;","str":" \\;"}, +{"flavor":"windows","left":"C\\UNC","right":"","str":"C\\UNC"}, +{"flavor":"windows","left":" //\\:unc","right":"UNCb;..;","str":" \\:unc\\UNCb;..;"}, +{"flavor":"windows","left":":","right":"unc\\uncunc","str":":\\unc\\uncunc"}, +{"flavor":"windows","left":"/UNC\\\\b:","right":"?x,ybc\\\\UNC","str":"\\UNC\\b:\\?x,ybc\\UNC"}, +{"flavor":"windows","left":"ac\u00e9/c","right":"","str":"ac\u00e9\\c"}, +{"flavor":"windows","left":"","right":"","str":"."}, +{"flavor":"windows","left":"x,yunc","right":"\\\\//////","str":"\\\\\\\\"}, +{"flavor":"windows","left":"///","right":"x,y////UNCb","str":"\\\\\\x,y\\UNCb"}, +{"flavor":"windows","left":"b","right":"\\\\Cunc\u0000","str":"\\\\Cunc\u0000"}, +{"flavor":"windows","left":"\\\\:a","right":"/b..\\/","str":"\\\\:a\\b..\\"}, +{"flavor":"windows","left":"..a..x,yunc","right":"..\\\\;//\u00e9unc","str":"..a..x,yunc\\..\\;\\\u00e9unc"}, +{"flavor":"windows","left":"","right":";\u0000","str":";\u0000"}, +{"flavor":"windows","left":"c/\\","right":":","str":"c\\:"}, +{"flavor":"windows","left":"x,y","right":"x,y:","str":"x,y\\x,y:"}, +{"flavor":"windows","left":"","right":"x,y:.c","str":"x,y:.c"}, +{"flavor":"windows","left":"","right":"..?b","str":"..?b"}, +{"flavor":"windows","left":"..;uncaa","right":"\\\\uncuncb\u00e9","str":"\\\\uncuncb\u00e9"}, +{"flavor":"windows","left":"","right":"\\\\..x,yc","str":"\\\\..x,yc"}, +{"flavor":"windows","left":"\u00e9unc\\??","right":"unc","str":"\u00e9unc\\??\\unc"}, +{"flavor":"windows","left":"UNCC\u00e9","right":"://\\UNCUNC","str":"UNCC\u00e9\\:\\UNCUNC"}, +{"flavor":"windows","left":"\\ ","right":" \u0000a..","str":"\\ \\ \u0000a.."}, +{"flavor":"windows","left":"unc\\?","right":"\\\\?b ","str":"\\\\?b "}, +{"flavor":"windows","left":" ///.unc","right":"/x,y..","str":"\\x,y.."}, +{"flavor":"windows","left":":aC","right":"://a//\\\\","str":":aC\\:\\a"}, +{"flavor":"windows","left":" ::","right":"","str":" ::"}, +{"flavor":"windows","left":" UNC:\u00e9","right":"","str":" UNC:\u00e9"}, +{"flavor":"windows","left":"\\x,y..","right":"\\ .:UNC","str":"\\ .:UNC"}, +{"flavor":"windows","left":" \u0000x,y;;unc","right":"unc\\","str":" \u0000x,y;;unc\\unc"}, +{"flavor":"windows","left":"","right":"\\\\;uncCx,y.","str":"\\\\;uncCx,y."}, +{"flavor":"windows","left":"","right":";bx,y\u0000//UNC","str":";bx,y\u0000\\UNC"}, +{"flavor":"windows","left":"","right":"?.C..","str":"?.C.."}, +{"flavor":"windows","left":"","right":"bC\\\\/\\\\","str":"bC"}, +{"flavor":"windows","left":";C","right":":","str":";C\\:"}, +{"flavor":"windows","left":"//a\\\\uncb?","right":".\u00e9b\u00e9?\u0000","str":"\\\\a\\\\uncb?\\.\u00e9b\u00e9?\u0000"}, +{"flavor":"windows","left":"\\\u00e9b","right":"\\b","str":"\\b"}, +{"flavor":"windows","left":";:;:","right":"","str":";:;:"}, +{"flavor":"windows","left":"\u00e9UNC","right":"\\\\:\\\\\u00e9\u00e9","str":"\\\\:\\\\\u00e9\u00e9"}, +{"flavor":"windows","left":"\\/\\","right":"","str":"\\\\\\"}, +{"flavor":"windows","left":"a","right":" ","str":"a\\ "}, +{"flavor":"windows","left":".","right":"?\\UNCUNC\\","str":"?\\UNCUNC"}, +{"flavor":"windows","left":"b\u00e9","right":"","str":"b\u00e9"}, +{"flavor":"windows","left":"//C","right":"C","str":"\\\\C\\C\\"}, +{"flavor":"windows","left":"unc// ","right":"C","str":"unc\\ \\C"}, +{"flavor":"windows","left":".c\u0000","right":"C","str":".c\u0000\\C"}, +{"flavor":"windows","left":"/","right":"...C\u0000a","str":"\\...C\u0000a"}, +{"flavor":"windows","left":"x,yC","right":"b;cccc","str":"x,yC\\b;cccc"}, +{"flavor":"windows","left":"?.","right":"//\\x,y;x,y","str":"\\\\\\x,y;x,y"}, +{"flavor":"windows","left":"//C ?cx,y","right":"?","str":"\\\\C ?cx,y\\?\\"}, +{"flavor":"windows","left":" \\\\","right":"\u00e9\\\\\u0000C\\..","str":" \\\u00e9\\\u0000C\\.."}, +{"flavor":"windows","left":"\u0000:b","right":"unc","str":"\u0000:b\\unc"}, +{"flavor":"windows","left":";a;;?","right":"uncx,yb:","str":";a;;?\\uncx,yb:"}, +{"flavor":"windows","left":"\u0000.","right":"c\\","str":"\u0000.\\c"}, +{"flavor":"windows","left":" ?//","right":"\\x,y/","str":"\\x,y"}, +{"flavor":"windows","left":" ","right":"\\\\b://\u0000\\\\","str":"\\\\b:\\\\\u0000"}, +{"flavor":"windows","left":";","right":"x,y\\\\","str":";\\x,y"}, +{"flavor":"windows","left":"\\\\x,y/c.","right":"c\\\\\\","str":"\\\\x,y\\c.\\c"}, +{"flavor":"windows","left":"\u0000c?;","right":"\u00e9unc/\u00e9x,y","str":"\u0000c?;\\\u00e9unc\\\u00e9x,y"}, +{"flavor":"windows","left":" C","right":"","str":" C"}, +{"flavor":"windows","left":"","right":";;a","str":";;a"}, +{"flavor":"windows","left":".","right":"b..","str":"b.."}, +{"flavor":"windows","left":"","right":"\u00e9/unc","str":"\u00e9\\unc"}, +{"flavor":"windows","left":"x,yUNC","right":"c\u00e9\u0000","str":"x,yUNC\\c\u00e9\u0000"}, +{"flavor":"windows","left":"; \\\\b","right":"","str":"; \\b"}, +{"flavor":"windows","left":"","right":":b;","str":":b;"}, +{"flavor":"windows","left":"\u00e9\\\\..","right":"///","str":"\\\\\\"}, +{"flavor":"windows","left":"\\x,y\u0000ax,y","right":"unc.\\","str":"\\x,y\u0000ax,y\\unc."}, +{"flavor":"windows","left":"","right":"","str":"."}, +{"flavor":"windows","left":"unc","right":"UNCbUNCb:\u0000","str":"unc\\UNCbUNCb:\u0000"}, +{"flavor":"windows","left":"..UNC..UNCb","right":"\\;C/","str":"\\;C"}, +{"flavor":"windows","left":"\u0000..\\;///","right":"/a?","str":"\\a?"}, +{"flavor":"windows","left":"..UNC?","right":"","str":"..UNC?"}, +{"flavor":"windows","left":"/ x,y\\","right":"\u00e9..a","str":"\\ x,y\\\u00e9..a"}, +{"flavor":"windows","left":"\\ab","right":";x,yb/","str":"\\ab\\;x,yb"}, +{"flavor":"windows","left":";UNC//","right":"\u0000 ","str":";UNC\\\u0000 "}, +{"flavor":"windows","left":"UNC?UNCca;","right":"UNC","str":"UNC?UNCca;\\UNC"}, +{"flavor":"windows","left":"b","right":"//C","str":"\\\\C"}, +{"flavor":"windows","left":"uncc /UNC:","right":" Cc","str":"uncc \\UNC:\\ Cc"}, +{"flavor":"windows","left":".;?","right":":C\\\\;/","str":".;?\\:C\\;"}, +{"flavor":"windows","left":"x,yc;//","right":"\u00e9\u00e9x,y","str":"x,yc;\\\u00e9\u00e9x,y"}, +{"flavor":"windows","left":"b?a.\u0000","right":"//\u00e9..\\;?","str":"\\\\\u00e9..\\;?\\"}, +{"flavor":"windows","left":"?:///.\\\\","right":"\u0000:a/","str":"\u0000:a"}, +{"flavor":"windows","left":"","right":"..Cunc..","str":"..Cunc.."}, +{"flavor":"windows","left":";","right":"UNCc","str":";\\UNCc"}, +{"flavor":"windows","left":"","right":"C.;unc\\","str":"C.;unc"}, +{"flavor":"windows","left":";.x,y..","right":"//\u00e9UNC;?","str":"\\\\\u00e9UNC;?"} +], +"equality": [ +{"left":"C:\\A","right":"c:/a","equal":true}, +{"left":"C:\\a","right":"C:\\b","equal":false}, +{"left":"\\\\S\\x\\Y","right":"//s/X/y","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"?","right":"?","equal":true}, +{"left":" ;","right":" ;","equal":true}, +{"left":"aa//B/","right":"aa//B/","equal":true}, +{"left":"a\u0000c","right":"a\u0000c","equal":true}, +{"left":"\\c","right":"\\c","equal":true}, +{"left":"\\\\/","right":"\\\\/","equal":true}, +{"left":"Cc","right":"cc","equal":true}, +{"left":".x,yaUNC;","right":".x,yaUNC;","equal":true}, +{"left":"CUNC","right":"CUNC","equal":true}, +{"left":"?:","right":"?:","equal":true}, +{"left":"aUNC.","right":"AUNC.","equal":true}, +{"left":"UNCB\\\\\\\u0000","right":"uncb\\\\\\\u0000","equal":true}, +{"left":".B","right":".B","equal":true}, +{"left":"\u00e9//;;\u0000","right":"\u00c9//;;\u0000","equal":true}, +{"left":"..\u00e9C","right":"..\u00e9c","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"BB","right":"BB","equal":true}, +{"left":"//","right":"//","equal":true}, +{"left":"a","right":"a","equal":true}, +{"left":"\\\\/;.:","right":"\\\\/;.:","equal":true}, +{"left":" ://","right":" ://","equal":true}, +{"left":";.;.unc","right":";.;.unc","equal":true}, +{"left":"b\u00e9/","right":"b\u00e9/","equal":true}, +{"left":" /x,y/","right":" /x,y/","equal":true}, +{"left":";A","right":";A","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"ax,y","right":"ax,y","equal":true}, +{"left":" .","right":" .","equal":true}, +{"left":"B","right":"B","equal":true}, +{"left":"x,y","right":"X,Y","equal":true}, +{"left":"c\u0000?","right":"C\u0000?","equal":true}, +{"left":"UNC/\u00e9","right":"UNC/\u00e9","equal":true}, +{"left":"c:A\\","right":"C:A\\","equal":true}, +{"left":"\u00e9ba/?","right":"\u00e9ba/?","equal":true}, +{"left":"//CB","right":"//cb","equal":true}, +{"left":"C\u00e9","right":"c\u00e9","equal":true}, +{"left":"..","right":"..","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"C","right":"c","equal":true}, +{"left":"//A\u0000//\\\\","right":"//A\u0000//\\\\","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"CCc","right":"CCc","equal":true}, +{"left":"b.","right":"b.","equal":true}, +{"left":"C;","right":"c;","equal":true}, +{"left":" ///\\\\UNC","right":" ///\\\\unc","equal":true}, +{"left":"x,y..:.","right":"x,y..:.","equal":true}, +{"left":".","right":".","equal":true}, +{"left":"\u00e9\\\\A/?","right":"\u00e9\\\\A/?","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"\u00e9unc?A","right":"\u00e9unc?A","equal":true}, +{"left":"\\\\","right":"\\\\","equal":true}, +{"left":"unc","right":"UNC","equal":true}, +{"left":"\u0000;//","right":"\u0000;//","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"\u00e9\u00e9","right":"\u00e9\u00e9","equal":true}, +{"left":"\u0000\u0000","right":"\u0000\u0000","equal":true}, +{"left":"\u00e9","right":"\u00e9","equal":true}, +{"left":"..\\\\//.. ","right":"..\\\\//.. ","equal":true}, +{"left":"unc","right":"unc","equal":true}, +{"left":"\\","right":"\\","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"Aa\u0000;","right":"Aa\u0000;","equal":true}, +{"left":"","right":"","equal":true}, +{"left":";c//b\u0000","right":";c//b\u0000","equal":true}, +{"left":"\u00e9\\\\unc","right":"\u00e9\\\\unc","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"uncaC","right":"uncaC","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"A","right":"a","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"x,ya","right":"x,ya","equal":true}, +{"left":"a\u00e9//","right":"a\u00e9//","equal":true}, +{"left":"/c","right":"/c","equal":true}, +{"left":" ;;\u0000","right":" ;;\u0000","equal":true}, +{"left":"A\\ ","right":"A\\ ","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"C","right":"c","equal":true}, +{"left":"../unc\\\\\\\\","right":"../UNC\\\\\\\\","equal":true}, +{"left":"?..x,ya\u0000","right":"?..X,YA\u0000","equal":true}, +{"left":"UNC","right":"UNC","equal":true}, +{"left":"C/;bc","right":"C/;BC","equal":true}, +{"left":"\\\\","right":"\\\\","equal":true}, +{"left":"..a?","right":"..A?","equal":true}, +{"left":"?x,y","right":"?X,Y","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"//c","right":"//C","equal":true}, +{"left":"///aA","right":"///aa","equal":true}, +{"left":"acB","right":"ACB","equal":true}, +{"left":"","right":"","equal":true}, +{"left":"x,yb","right":"x,yb","equal":true}, +{"left":":A","right":":A","equal":true}, +{"left":"/x,y","right":"/X,Y","equal":true}, +{"left":"c","right":"C","equal":true}, +{"left":"","right":"","equal":true} +], +"normpath": [ +{"text":"","normpath":"."}, +{"text":".","normpath":"."}, +{"text":"a","normpath":"a"}, +{"text":"a\\","normpath":"a"}, +{"text":"A//B","normpath":"A\\B"}, +{"text":"A/./B","normpath":"A\\B"}, +{"text":"A/foo/../B","normpath":"A\\B"}, +{"text":"C:\\","normpath":"C:\\"}, +{"text":"C:","normpath":"C:"}, +{"text":"C:a\\..\\..","normpath":"C:.."}, +{"text":"C:\\..\\a","normpath":"C:\\a"}, +{"text":"..\\..\\a","normpath":"..\\..\\a"}, +{"text":"a\\..\\..\\b","normpath":"..\\b"}, +{"text":"\\\\server\\share\\","normpath":"\\\\server\\share\\"}, +{"text":"\\\\server\\share\\a\\..","normpath":"\\\\server\\share\\"}, +{"text":".\\a\\.\\b\\","normpath":"a\\b"}, +{"text":"\\","normpath":"\\"}, +{"text":"./..","normpath":".."}, +{"text":"C:./x","normpath":"C:x"}, +{"text":"\\Cb ","normpath":"\\Cb "}, +{"text":"\u00e9\u00e9:/?x,yunc\\;C","normpath":"\u00e9\u00e9:\\?x,yunc\\;C"}, +{"text":"unc.:\u00e9;bunc\\","normpath":"unc.:\u00e9;bunc"}, +{"text":"C;c\u00e9b\u0000","normpath":"C;c\u00e9b\u0000"}, +{"text":";.x,y\\/abCbunc","normpath":";.x,y\\abCbunc"}, +{"text":"UNC?//C","normpath":"UNC?\\C"}, +{"text":"/UNC;ba","normpath":"\\UNC;ba"}, +{"text":"unc//","normpath":"unc"}, +{"text":"C \\\\ unc//\\\\x,yunc","normpath":"C \\ unc\\x,yunc"}, +{"text":"\\;UNC","normpath":"\\;UNC"}, +{"text":"Cb","normpath":"Cb"}, +{"text":"..aunc\u0000","normpath":"..aunc\u0000"}, +{"text":"b ;/ ","normpath":"b ;\\ "}, +{"text":"//UNC:\\\\/.","normpath":"\\\\UNC:\\\\"}, +{"text":"","normpath":"."}, +{"text":"a","normpath":"a"}, +{"text":"aCbb?;\\unc\u00e9","normpath":"aCbb?;\\unc\u00e9"}, +{"text":"unc\u0000;x,y/c?\\\\","normpath":"unc\u0000;x,y\\c?"}, +{"text":"\\;","normpath":"\\;"}, +{"text":"..?UNC","normpath":"..?UNC"}, +{"text":" :\u0000\\ :\u0000?:","normpath":" :\u0000\\ :\u0000?:"}, +{"text":"\\\\/cunc\\:","normpath":"\\\\\\cunc\\:"}, +{"text":"\u00e9c","normpath":"\u00e9c"}, +{"text":"/bunc\\?","normpath":"\\bunc\\?"}, +{"text":"a","normpath":"a"}, +{"text":"b","normpath":"b"}, +{"text":" C...aUNCc\\","normpath":" C...aUNCc"}, +{"text":"x,y/ b","normpath":"x,y\\ b"}, +{"text":"cx,y?.:","normpath":"cx,y?.:"}, +{"text":" UNC\u00e9///C./; ","normpath":" UNC\u00e9\\C.\\; "}, +{"text":"//....;\u0000a","normpath":"\\\\....;\u0000a"}, +{"text":"/x,y\\.;\\UNC ","normpath":"\\x,y\\.;\\UNC "}, +{"text":"/b;C\\\\x,y","normpath":"\\b;C\\x,y"}, +{"text":"\u00e9ca:\u00e9C\u00e9\\\\\\","normpath":"\u00e9ca:\u00e9C\u00e9"}, +{"text":"ba/;:x,yUNCUNC//","normpath":"ba\\;:x,yUNCUNC"}, +{"text":"a","normpath":"a"}, +{"text":"c.C/bC","normpath":"c.C\\bC"}, +{"text":"uncunc?;.\\\\","normpath":"uncunc?;."}, +{"text":"unc;/;\u00e9","normpath":"unc;\\;\u00e9"}, +{"text":"uncUNC bc /UNCunc\\","normpath":"uncUNC bc \\UNCunc"}, +{"text":" unc/","normpath":" unc"}, +{"text":" unc","normpath":" unc"}, +{"text":"\\\\UNC","normpath":"\\\\UNC"}, +{"text":"aUNCCx,y\\\\\\\\\u00e9bUNC","normpath":"aUNCCx,y\\\u00e9bUNC"}, +{"text":"","normpath":"."}, +{"text":"c\\b\u00e9\u00e9","normpath":"c\\b\u00e9\u00e9"}, +{"text":"C;.UNCunc","normpath":"C;.UNCunc"}, +{"text":" ?UNCunc/unc ","normpath":" ?UNCunc\\unc "}, +{"text":"UNCUNC:b\u00e9\\C.c","normpath":"UNCUNC:b\u00e9\\C.c"}, +{"text":"Cunc//UNC ///....","normpath":"Cunc\\UNC \\...."}, +{"text":"b\\c\\","normpath":"b\\c"}, +{"text":"","normpath":"."}, +{"text":"//","normpath":"\\\\"}, +{"text":"\u00e9","normpath":"\u00e9"}, +{"text":"? x,y","normpath":"? x,y"}, +{"text":"...","normpath":"..."}, +{"text":"b/..\u0000x,y\\","normpath":"b\\..\u0000x,y"}, +{"text":"\\?","normpath":"\\?"}, +{"text":"UNC////aC?.?:","normpath":"UNC\\aC?.?:"}, +{"text":"b:.\\.;unc\\\\unc","normpath":"b:.;unc\\unc"}, +{"text":":; ///","normpath":":; "}, +{"text":"\u0000b\u0000:\u0000x,y","normpath":"\u0000b\u0000:\u0000x,y"}, +{"text":"c\\\\;.?\\b?UNC;","normpath":"c\\;.?\\b?UNC;"}, +{"text":"bc\u00e9?? \u0000","normpath":"bc\u00e9?? \u0000"}, +{"text":"/auncb","normpath":"\\auncb"}, +{"text":" \\/a","normpath":" \\a"}, +{"text":"","normpath":"."}, +{"text":"\\unc\u00e9??","normpath":"\\unc\u00e9??"}, +{"text":":unc?b\u0000a","normpath":":unc?b\u0000a"}, +{"text":";?\\c\\\\ ","normpath":";?\\c\\ "}, +{"text":"\\:\\//uncx,y\u0000:\\//","normpath":"\\:\\uncx,y\u0000:"}, +{"text":"b //.","normpath":"b "}, +{"text":"\u0000//C:\u00e9:b","normpath":"\u0000\\C:\u00e9:b"}, +{"text":"","normpath":"."}, +{"text":"/unc\\\\.;","normpath":"\\unc\\.;"}, +{"text":"\\\\b\\\\a?unc?.\\","normpath":"\\\\b\\\\a?unc?."}, +{"text":"b\\\\UNC://?\\\\..x,y","normpath":"b\\UNC:\\?\\..x,y"}, +{"text":"bUNC ../.","normpath":"bUNC .."}, +{"text":"c//c\u0000 bb/","normpath":"c\\c\u0000 bb"}, +{"text":"Cx,y?\u0000uncUNC..","normpath":"Cx,y?\u0000uncUNC.."}, +{"text":".a;b x,y\u00e9//CUNC","normpath":".a;b x,y\u00e9\\CUNC"}, +{"text":" ","normpath":" "}, +{"text":"\\;","normpath":"\\;"}, +{"text":"\u00e9cC\\\\?","normpath":"\u00e9cC\\?"}, +{"text":"?C\u0000","normpath":"?C\u0000"}, +{"text":"","normpath":"."}, +{"text":"b:UNC","normpath":"b:UNC"}, +{"text":"","normpath":"."}, +{"text":"//c//?bx,y;;a","normpath":"\\\\c\\\\?bx,y;;a"}, +{"text":".;","normpath":".;"}, +{"text":" ?..C.","normpath":" ?..C."}, +{"text":";UNCCc;Ca..?C","normpath":";UNCCc;Ca..?C"}, +{"text":"x,yb","normpath":"x,yb"}, +{"text":"","normpath":"."}, +{"text":"\\\\unc\\\\\u00e9c/","normpath":"\\\\unc\\\\\u00e9c"}, +{"text":" //bb","normpath":" \\bb"}, +{"text":"C/:unc/","normpath":"C\\:unc"}, +{"text":"x,y\\\\","normpath":"x,y"}, +{"text":"x,y..unc//unc","normpath":"x,y..unc\\unc"}, +{"text":"UNC..uncbx,y\\\\b","normpath":"UNC..uncbx,y\\b"}, +{"text":"\\x,y// .","normpath":"\\x,y\\ ."}, +{"text":"\u0000//.a C","normpath":"\u0000\\.a C"}, +{"text":"aUNC","normpath":"aUNC"}, +{"text":"\\\\c. ////\\\\","normpath":"\\\\c. \\\\"}, +{"text":"../.\\\\\u00e9;","normpath":"..\\\u00e9;"}, +{"text":"","normpath":"."}, +{"text":"a\\/UNC.cc","normpath":"a\\UNC.cc"}, +{"text":"\\/// ;","normpath":"\\\\\\\\ ;"}, +{"text":"C","normpath":"C"}, +{"text":"..\u0000:","normpath":"..\u0000:"}, +{"text":"C;\u00e9b...?x,y;UNC","normpath":"C;\u00e9b...?x,y;UNC"}, +{"text":"/;\u0000.UNC..","normpath":"\\;\u0000.UNC.."}, +{"text":"//b////:;:Cb..","normpath":"\\\\b\\\\:;:Cb.."}, +{"text":"\\\u0000UNC/ccuncunc","normpath":"\\\u0000UNC\\ccuncunc"}, +{"text":" \\//\\\u00e9","normpath":" \\\u00e9"}, +{"text":"\u00e9UNCb;UNCUNC.//c.","normpath":"\u00e9UNCb;UNCUNC.\\c."}, +{"text":":\\\\\\\\C\\c","normpath":":\\C\\c"}, +{"text":"C..\u00e9","normpath":"C..\u00e9"}, +{"text":"","normpath":"."}, +{"text":".cuncb","normpath":".cuncb"}, +{"text":" \u00e9 x,y?unc","normpath":" \u00e9 x,y?unc"}, +{"text":"..;c ..x,y\u0000unc\\\\","normpath":"..;c ..x,y\u0000unc"}, +{"text":"ax,y:..x,yCunc: ","normpath":"ax,y:..x,yCunc: "}, +{"text":"...","normpath":"..."}, +{"text":"..c..\\\\//x,yb","normpath":"..c..\\x,yb"}, +{"text":"","normpath":"."}, +{"text":"//;C/./:","normpath":"\\\\;C\\.\\:"}, +{"text":"UNC\\:x,ybc/a.C","normpath":"UNC\\:x,ybc\\a.C"}, +{"text":"c\u00e9;C","normpath":"c\u00e9;C"}, +{"text":"\\\\:unc..; ..//","normpath":"\\\\:unc..; ..\\\\"}, +{"text":"x,y\u00e9\u0000.UNC..","normpath":"x,y\u00e9\u0000.UNC.."}, +{"text":"","normpath":"."}, +{"text":"/","normpath":"\\"}, +{"text":"aca.bC\\\u00e9\u0000\\\\","normpath":"aca.bC\\\u00e9\u0000"}, +{"text":"a//unc\\\\/;c","normpath":"a\\unc\\;c"}, +{"text":":uncC///?","normpath":":uncC\\?"}, +{"text":"// x,y\u00e9c","normpath":"\\\\ x,y\u00e9c"}, +{"text":"\\\u0000C","normpath":"\\\u0000C"}, +{"text":"//;?","normpath":"\\\\;?"}, +{"text":".\\\u0000C:a ","normpath":"\u0000C:a "}, +{"text":"\\\\\\\\:x,yc :a","normpath":"\\\\\\\\:x,yc :a"}, +{"text":"/\u0000x,yx,y/","normpath":"\\\u0000x,yx,y"}, +{"text":"","normpath":"."}, +{"text":"\\;.\u0000","normpath":"\\;.\u0000"}, +{"text":"\\// :c?x,y\u0000/","normpath":"\\\\\\ :c?x,y\u0000\\"}, +{"text":"\\\\\\://\u00e9\\\\\\a","normpath":"\\\\\\:\\\u00e9\\a"}, +{"text":"/C?C.\u0000UNCunc","normpath":"\\C?C.\u0000UNCunc"}, +{"text":"?\u00e9//?cx,yUNC;\\\\","normpath":"?\u00e9\\?cx,yUNC;"}, +{"text":"?:\u0000\\..\u0000:x,y..","normpath":"?:\u0000\\..\u0000:x,y.."}, +{"text":":c;//a\\;\\\\bc","normpath":":c;\\a\\;\\bc"}, +{"text":"uncUNC///b..unc","normpath":"uncUNC\\b..unc"}, +{"text":"?c;c//","normpath":"?c;c"}, +{"text":" Ca;.?/","normpath":" Ca;.?"}, +{"text":"\\","normpath":"\\"}, +{"text":"a\\.c","normpath":"a\\.c"}, +{"text":"x,yc\u0000bunc","normpath":"x,yc\u0000bunc"}, +{"text":"../\u0000x,y?C;\u0000..","normpath":"..\\\u0000x,y?C;\u0000.."}, +{"text":"// x,yC\u0000UNC////","normpath":"\\\\ x,yC\u0000UNC\\\\"}, +{"text":"..C?/..c","normpath":"..C?\\..c"}, +{"text":".?Caa:/","normpath":".?Caa:"}, +{"text":" ","normpath":" "}, +{"text":"\u0000\\;\u00e9.x,y.","normpath":"\u0000\\;\u00e9.x,y."}, +{"text":".//UNC;c;.unc","normpath":"UNC;c;.unc"}, +{"text":"bx,y: /c?Cbb","normpath":"bx,y: \\c?Cbb"}, +{"text":"unc:?\u0000//c\u0000//?\\","normpath":"unc:?\u0000\\c\u0000\\?"}, +{"text":"\u00e9\u00e9\u0000ba","normpath":"\u00e9\u00e9\u0000ba"}, +{"text":"UNC\\\\..aUNC;","normpath":"UNC\\..aUNC;"}, +{"text":"C\\?\\;\u00e9c","normpath":"C\\?\\;\u00e9c"}, +{"text":"\\/ . //.C","normpath":"\\\\ . \\\\.C"}, +{"text":"x,y","normpath":"x,y"}, +{"text":"//\\\\\u00e9?\\","normpath":"\\\\\\\\\u00e9?"}, +{"text":"/\\ \\C","normpath":"\\\\ \\C"}, +{"text":" x,yCc?.x,yc","normpath":" x,yCc?.x,yc"}, +{"text":":CUNC://x,yC\\","normpath":":CUNC:\\x,yC"}, +{"text":"/ x,y\u0000??c b/","normpath":"\\ x,y\u0000??c b"}, +{"text":"\u00e9\u0000..\\","normpath":"\u00e9\u0000.."}, +{"text":"UNC\u0000CbUNC\\\\:.C","normpath":"UNC\u0000CbUNC\\:.C"}, +{"text":"?","normpath":"?"}, +{"text":"..;\u0000C:.\u0000..\\","normpath":"..;\u0000C:.\u0000.."}, +{"text":"","normpath":"."}, +{"text":"","normpath":"."}, +{"text":"\u00e9","normpath":"\u00e9"}, +{"text":"","normpath":"."}, +{"text":"\u0000x,yx,yb??b?x,y\u0000","normpath":"\u0000x,yx,yb??b?x,y\u0000"}, +{"text":"\\\\a\\\\b a\u00e9x,yCunc","normpath":"\\\\a\\\\b a\u00e9x,yCunc"}, +{"text":":\u0000..c.;c\\unc\\","normpath":":\u0000..c.;c\\unc"}, +{"text":" c.unc\\\\? ","normpath":" c.unc\\? "}, +{"text":"...b//.x,y //","normpath":"...b\\.x,y "}, +{"text":";b\\/\u00e9a...:","normpath":";b\\\u00e9a...:"}, +{"text":"\\","normpath":"\\"}, +{"text":"a\u0000","normpath":"a\u0000"}, +{"text":"b","normpath":"b"}, +{"text":"\\\\\u00e9\\\\ x,y\\\\/\\\\","normpath":"\\\\\u00e9\\\\ x,y"}, +{"text":"x,y\u00e9C?C","normpath":"x,y\u00e9C?C"}, +{"text":"?\\","normpath":"?"}, +{"text":" a\u0000Cb","normpath":" a\u0000Cb"}, +{"text":"/? \\b..","normpath":"\\? \\b.."}, +{"text":"\u00e9\u0000.///Cc.x,y","normpath":"\u00e9\u0000.\\Cc.x,y"}, +{"text":"//x,y\u00e9\u00e9\\\\..a","normpath":"\\\\x,y\u00e9\u00e9\\\\..a"}, +{"text":"x,yUNCunc\\\\\\ \\unc ","normpath":"x,yUNCunc\\ \\unc "}, +{"text":"","normpath":"."}, +{"text":"","normpath":"."}, +{"text":"","normpath":"."}, +{"text":"cUNC:\\\\?//","normpath":"cUNC:\\?"}, +{"text":"\u00e9a","normpath":"\u00e9a"}, +{"text":"?c","normpath":"?c"}, +{"text":"// uncc\u00e9;","normpath":"\\\\ uncc\u00e9;"}, +{"text":"/x,yx,y","normpath":"\\x,yx,y"}, +{"text":"","normpath":"."}, +{"text":"\\\u0000\\\\b./uncx,y","normpath":"\\\u0000\\b.\\uncx,y"}, +{"text":"// bb. : ","normpath":"\\\\ bb. : "}, +{"text":"unc;.;","normpath":"unc;.;"}, +{"text":"b\u00e9UNC;;.b?","normpath":"b\u00e9UNC;;.b?"}, +{"text":"","normpath":"."}, +{"text":"C\\\\\\\\\\\\UNCC..\\","normpath":"C\\UNCC.."}, +{"text":"x,yC//x,yUNC?C","normpath":"x,yC\\x,yUNC?C"}, +{"text":"","normpath":"."}, +{"text":"\u0000//\\\\.UNCunc\u00e9","normpath":"\u0000\\.UNCunc\u00e9"}, +{"text":"a..:","normpath":"a..:"}, +{"text":"a.;b\u0000","normpath":"a.;b\u0000"}, +{"text":";\u0000Cx,y..;..//\u0000:","normpath":";\u0000Cx,y..;..\\\u0000:"}, +{"text":"\u0000\\\\x,ybb..;","normpath":"\u0000\\x,ybb..;"}, +{"text":"","normpath":"."}, +{"text":"","normpath":"."}, +{"text":"/ \u00e9UNC.?..ca","normpath":"\\ \u00e9UNC.?..ca"}, +{"text":"unc////.","normpath":"unc"}, +{"text":"\\unc..\u00e9C\u00e9","normpath":"\\unc..\u00e9C\u00e9"}, +{"text":"/c//","normpath":"\\c"}, +{"text":"","normpath":"."}, +{"text":";\u0000\\\\:x,y\\b?","normpath":";\u0000\\:x,y\\b?"}, +{"text":"??/C;UNCCaUNC ","normpath":"??\\C;UNCCaUNC "}, +{"text":"b\\\\\u0000\u00e9?","normpath":"b\\\u0000\u00e9?"}, +{"text":"?/ ?","normpath":"?\\ ?"}, +{"text":":","normpath":":"}, +{"text":"?\\\\\\","normpath":"?"}, +{"text":"","normpath":"."}, +{"text":"C\u0000x,y","normpath":"C\u0000x,y"}, +{"text":"b\u0000../////","normpath":"b\u0000.."}, +{"text":"\\\\//x,yb","normpath":"\\\\\\\\x,yb"}, +{"text":"\u0000","normpath":"\u0000"}, +{"text":"\u00e9a.//Cc :?a","normpath":"\u00e9a.\\Cc :?a"}, +{"text":"","normpath":"."}, +{"text":"\u0000","normpath":"\u0000"}, +{"text":"UNCb\\\\//;:?:a","normpath":"UNCb\\;:?:a"}, +{"text":"?","normpath":"?"}, +{"text":"\u0000bc ","normpath":"\u0000bc "}, +{"text":"//x,yC/\\\\\u0000?x,yC?","normpath":"\\\\x,yC\\\\\u0000?x,yC?"}, +{"text":"\u00e9","normpath":"\u00e9"}, +{"text":"UNC;","normpath":"UNC;"}, +{"text":"/;c?unc/ ","normpath":"\\;c?unc\\ "} +], +"json": [ +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"[]","value":["list",[]]}, +{"text":"{}","value":["dict",[]]}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"[1","decode_error":"Expecting ',' delimiter: line 1 column 3 (char 2)"}, +{"text":"[1,]","decode_error":"Illegal trailing comma before end of array: line 1 column 3 (char 2)"}, +{"text":"{\"a\":1,}","decode_error":"Illegal trailing comma before end of object: line 1 column 7 (char 6)"}, +{"text":"{1:2}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"{\"a\" 1}","decode_error":"Expecting ':' delimiter: line 1 column 6 (char 5)"}, +{"text":"\"abc","decode_error":"Unterminated string starting at: line 1 column 1 (char 0)"}, +{"text":"\"a\tb\"","decode_error":"Invalid control character at: line 1 column 3 (char 2)"}, +{"text":"\"\\x\"","decode_error":"Invalid \\escape: line 1 column 2 (char 1)"}, +{"text":"\"\\u12\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"\"\\u12x4\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"\"\\ud800\\u12x4\"","decode_error":"Invalid \\uXXXX escape: line 1 column 9 (char 8)"}, +{"text":"[]\n x","decode_error":"Extra data: line 2 column 2 (char 4)"}, +{"text":"\ufeff[]","decode_error":"Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)"}, +{"text":"-","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"01","decode_error":"Extra data: line 1 column 2 (char 1)"}, +{"text":"1.","decode_error":"Extra data: line 1 column 2 (char 1)"}, +{"text":"1e","decode_error":"Extra data: line 1 column 2 (char 1)"}, +{"text":"1e+","decode_error":"Extra data: line 1 column 2 (char 1)"}, +{"text":"-Infinityx","decode_error":"Extra data: line 1 column 10 (char 9)"}, +{"text":"nul","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]","value":["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[["list",[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}, +{"text":"11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111","value_error":"Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit"}, +{"text":"[1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111]","value":["list",[["int","1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"]]]}, +{"text":"1.11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111","value":["float","3ff1c71c71c71c72"]}, +{"text":"{\"a\": [true, false, nul0], \"a\":}{\"b\": \"\\n\\t\\\\\\\"\\/\"}}true","decode_error":"Expecting value: line 1 column 21 (char 20)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", }\"C\u00e9\\ud3d\\ude00\"]","decode_error":"Expecting value: line 1 column 21 (char 20)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"{\"a\": [utrue, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 8 (char 7)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"[\"A=1\", \"B=\\]00e9\", 1C=\\ud83d\\ude00\"]","decode_error":"Invalid \\escape: line 1 column 12 (char 11)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"\"d800{}","decode_error":"Unterminated string starting at: line 1 column 1 (char 0)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"{\"a\": [true, false, null]true\"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting ',' delimiter: line 1 column 26 (char 25)"}, +{"text":"[\\]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"{[]","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"\"\\ud800\\udc00\\ud:c00\\ud80\u001fx\"","decode_error":"Invalid \\uXXXX escape: line 1 column 15 (char 14)"}, +{"text":"{}","value":["dict",[]]}, +{"text":" \t\r\n[1 , 2 ]Infinity","decode_error":"Extra data: line 2 column 9 (char 12)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"{}","value":["dict",[]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3 [","decode_error":"Expecting ',' delimiter: line 1 column 54 (char 53)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[NaN, Infinity, -:Infin\u0000ity, 1e400, -0.0, 1E-5, 10 -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":" \t\r\n[1 , 2 ","decode_error":"Expecting ',' delimiter: line 2 column 9 (char 12)"}, +{"text":"{\"a\": [rue, false, null], \"a\": {\"b\":\t\"\n\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 8 (char 7)"}, +{"text":"[NaN, Infinity, 1Infinity, 1e400, -0.0, 1E-5, 10, -3]","decode_error":"Expecting ',' delimiter: line 1 column 18 (char 17)"}, +{"text":"{\"a\": [true, fa.se, null.], \"a\"\u0000: {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 14 (char 13)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"{","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[]","value":["list",[]]}, +{"text":"\"\\ud800\\udc00\\udc00\\u800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 21 (char 20)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[NaN, Infinity, -Infixnity,\"1e400, -0.0, 1E-true, 10, -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"[\"A=1\"\n \"Bu=\\u00e9\", \"C=\\ud83d]\\ude00\"]","decode_error":"Expecting ',' delimiter: line 2 column 2 (char 8)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"][","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":" \t\r\n[1 ,x\ufeff ]","decode_error":"Expecting value: line 2 column 5 (char 8)"}, +{"text":"\"\\ud800\\truedc00\\ud[00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 18 (char 17)"}, +{"text":" \r{]\n[1 , 2 ] ","decode_error":"Expecting property name enclosed in double quotes: line 1 column 4 (char 3)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"\"\\ud80+0\\udc00 udc00\\ud8-0x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"[NaN, Infinity, -In.inity, 1e400, -0.0, 1E-5, 10, -]\ufeff","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"\"\\ud8-00\\udc00NaN\\udc00\\u800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"\"\\ud800\\u}dc00\\udc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 9 (char 8)"}, +{"text":"{NaN}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"\r[","decode_error":"Expecting value: line 1 column 3 (char 2)"}, +{"text":"[]","value":["list",[]]}, +{"text":" \r\n[1 , 2 ]","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[]","value":["list",[]]}, +{"text":"{\"a\":null [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting ',' delimiter: line 1 column 11 (char 10)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"pack\nges\": [{\"name\": \"systemd\"}]}","decode_error":"Invalid control character at: line 1 column 43 (char 42)"}, +{"text":"{\"format\": NaN1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting ',' delimiter: line 1 column 15 (char 14)"}, +{"text":"1}","decode_error":"Extra data: line 1 column 2 (char 1)"}, +{"text":"\t\r[1 , 2] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[}","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":" \t\r\n[1 , 2 ]d800","decode_error":"Extra data: line 2 column 9 (char 12)"}, +{"text":"{\"format\": 1 \"artifaxt\": \"a.erofs\" \"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting ',' delimiter: line 1 column 15 (char 14)"}, +{"text":" \r\n[1 , 2 +","decode_error":"Expecting ',' delimiter: line 2 column 9 (char 11)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, x3]","decode_error":"Expecting value: line 1 column 51 (char 50)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud}00x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 21 (char 20)"}, +{"text":"{\t\r\n[1 , 2 ] ","decode_error":"Expecting property name enclosed in double quotes: line 2 column 1 (char 4)"}, +{"text":"{e0}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"{\u00e9\n","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"\"ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[117,100,56,48,48,56320,56320,55296,120]]}, +{"text":"[N0aN, Infinity, -Infinity, 1e4000, -0.0, 1E-5, 10, -3]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"\"\\ud800\\udc00\\udc00\\ude800x\"","value":["code_points",[65536,56320,56960,48,120]]}, +{"text":" \u00e9\r\n[1 , 2 ]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":" \t\r\n[1 , 2] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"]\u001f","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":" \t\r[Infinity, 2 ] ","value":["list",[["float","7ff0000000000000"],["int","2"]]]}, +{"text":"[","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"\"\\ud800\\udc00\\udc00\\ud8\u000000x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 21 (char 20)"}, +{"text":"\"\\ud800\\\u00e9udc00\nInfinitydc00\\ud800x\"","decode_error":"Invalid \\escape: line 1 column 8 (char 7)"}, +{"text":"[","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":" \t\r\n[e , ] ","decode_error":"Expecting value: line 2 column 2 (char 5)"}, +{"text":"[\u0000]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"[NaN,Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"\"ud800\\udc00\\udc00ud800x\"","value":["code_points",[117,100,56,48,48,56320,56320,117,100,56,48,48,120]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -+.0, 1E-5, 10, -3]","decode_error":"Expecting value: line 1 column 35 (char 34)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"{}x","decode_error":"Extra data: line 1 column 3 (char 2)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"\t\r\n[1 , 2true ] ","decode_error":"Expecting ',' delimiter: line 2 column 7 (char 9)"}, +{"text":"{\"format\": 1, \"artifact\": \"Infinity.erofs\", \"packages\": [{\"nam\u0000\": \"systemd\"}]}","decode_error":"Invalid control character at: line 1 column 63 (char 62)"}, +{"text":"[\"d800A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","d800A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"\t}u","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"true}","decode_error":"Extra data: line 1 column 5 (char 4)"}, +{"text":"[]","value":["list",[]]}, +{"text":"{}","value":["dict",[]]}, +{"text":"\ufeff0","decode_error":"Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)"}, +{"text":"}:","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"[n\\t\\d800\\\\\"\\/\"}}","decode_error":"Invalid \\escape: line 1 column 44 (char 43)"}, +{"text":"\"\\ud\\800\\udc00\\udc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":" \"A=1\", \"B=u00e9\", \"C=\\ud83d\\ude00\"]","decode_error":"Extra data: line 1 column 7 (char 6)"}, +{"text":"\u001f{\"\normat\": 1, \"artifact\": \"a.eofs\", \"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[\"A=1\", \"B=\\00e9\", \"C=\\ud83d\\ude00\"]","decode_error":"Invalid \\escape: line 1 column 12 (char 11)"}, +{"text":"Infinity}","decode_error":"Extra data: line 1 column 9 (char 8)"}, +{"text":"{\"a\": [true, false, null], \"a[\": {\"b\": \"\\n\\t\\\\\"\\/\"}}","decode_error":"Expecting ',' delimiter: line 1 column 48 (char 47)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"[]","value":["list",[]]}, +{"text":"\u0000[]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"\"\\ud800\\udc00+\\uuc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 16 (char 15)"}, +{"text":"[\"A=1\", \"B=\\u,00e9\",\u00e9\"C=\\ud83d\\ude00\"","decode_error":"Invalid \\uXXXX escape: line 1 column 13 (char 12)"}, +{"text":" \t\r[1 2 ] ","decode_error":"Expecting ',' delimiter: line 1 column 8 (char 7)"}, +{"text":"[NaN,\u001f Infinity, -Infinity, 1e400, -0.0, E-5, 10, -3]","decode_error":"Expecting value: line 1 column 6 (char 5)"}, +{"text":"]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"}+","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"\"\ufeff\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65279,65536,56320,55296,120]]}, +{"text":"\"\\udx800\\uc00\\udc00\\u800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"}","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":".\r","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"\"{\\ud800\\udc00eudc00\\ud800x\"","value":["code_points",[123,65536,101,117,100,99,48,48,55296,120]]}, +{"text":"[NaN, Infinity, -I finity, 1e400, -0.0, 1E-5, 10, -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"{\"forat\": 1, \"artifact\": \"aerofs\"\n, \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","forat"],["int","1"]],[["str","artifact"],["str","aerofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"\t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[NaN, Infinity, -Infinity\n 1e400, -0.0, 1E-5, 10,-3]","decode_error":"Expecting ',' delimiter: line 2 column 2 (char 27)"}, +{"text":"[\"A[1\", \"B=\\u001e9\", \"C=\\d83d\\ude00\"]","decode_error":"Invalid \\escape: line 1 column 25 (char 24)"}, +{"text":" \t\r\n[1\u00e9, 2 ] ","decode_error":"Expecting ',' delimiter: line 2 column 3 (char 6)"}, +{"text":"[:x]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erof\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erof"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"}\\]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{\"a\": [true, false, null], \"a}\": {\"bInfinity: \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting ':' delimiter: line 1 column 48 (char 47)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[NaN, Infinity, -Inf\ninity, 1e400, -0.0, 1E-5, ]10, -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"\"\\ud800\\udc00\\udtrue00\\ud80d8000x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 15 (char 14)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"\"\\ud800\\u}dc00 udc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 9 (char 8)"}, +{"text":"{\"a: [true, false, null], \"a\": {\"b\": \"\\\\t\\\\\\\"\\/\ufeff}}","decode_error":"Expecting ':' delimiter: line 1 column 28 (char 27)"}, +{"text":"[NaN, Infinity, -Infinity,1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":" \t\r\n[1}, 2 ] ","decode_error":"Expecting ',' delimiter: line 2 column 3 (char 6)"}, +{"text":"\t\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":" \r\n[1 ,] 2 ] ","decode_error":"Illegal trailing comma before end of array: line 2 column 4 (char 6)"}, +{"text":"[],NaN","decode_error":"Extra data: line 1 column 3 (char 2)"}, +{"text":" \t\r\n[1 \u0000, 2 [] :","decode_error":"Expecting ',' delimiter: line 2 column 4 (char 7)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"\"[]","decode_error":"Unterminated string starting at: line 1 column 1 (char 0)"}, +{"text":"{\"format\": \"1,0\"artifact\": \"a.erofs\", \"packages\": [{\"na+e\": \"systemd\"}]}","decode_error":"Expecting ',' delimiter: line 1 column 17 (char 16)"}, +{"text":"[NaN, Infinity, -[nfinity, 1e40u, -0.0, 1E-5, 10, -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"[\"A=1\",\"B=\\u00e9\", \"Cud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["code_points",[67,117,100,56,51,100,56832]]]]}, +{"text":" \t\r\n[1 , 2 ]","value":["list",[["int","1"],["int","2"]]]}, +{"text":"\n\t\r\n[1 true 2 ] ","decode_error":"Expecting ',' delimiter: line 3 column 4 (char 7)"}, +{"text":"[\"A=1\", \"B[\\u00e9\", \"C=\\d83d\\ue00\"]","decode_error":"Invalid \\escape: line 1 column 24 (char 23)"}, +{"text":"{\"a\": [true, false,Infinitynul{], \"a\": \"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting ',' delimiter: line 1 column 28 (char 27)"}, +{"text":"[NaN, Infinity, -Infinity\t, 1e4\"0, -0.0, 1E-5, 10, -3]","decode_error":"Expecting ',' delimiter: line 1 column 32 (char 31)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.eros\", \"packas\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.eros"]],[["str","packas"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"1 \t\r\n[1null ], 2 ] ","decode_error":"Extra data: line 2 column 1 (char 5)"}, +{"text":"[]\r","value":["list",[]]}, +{"text":"\"\\ud800\\udc00\\udc00\\ud8NaN00\"","decode_error":"Invalid \\uXXXX escape: line 1 column 21 (char 20)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\udu00\"]","decode_error":"Invalid \\uXXXX escape: line 1 column 31 (char 30)"}, +{"text":" \t\r\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"\u00e9[]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":" \t\r\n1 , NaN] ","decode_error":"Extra data: line 2 column 3 (char 6)"}, +{"text":"{\"a\": [trd800ue, false, null], \"\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 8 (char 7)"}, +{"text":" \t\r\n[\u00e9 , 2 ] ","decode_error":"Expecting value: line 2 column 2 (char 5)"}, +{"text":"{NaNa\": [true, false, null], \"a\": {\"b\": \\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"{}","value":["dict",[]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}}","decode_error":"Expecting ',' delimiter: line 1 column 70 (char 69)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud8true\\ude00\"]","decode_error":"Invalid \\uXXXX escape: line 1 column 25 (char 24)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\1de00\"]","decode_error":"Invalid \\escape: line 1 column 30 (char 29)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\,\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\,\"/"]]]]]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"{\"a\" [t]rue, false[, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting ':' delimiter: line 1 column 7 (char 6)"}, +{"text":" \t\r\n[1 , 2Infinity ] ","decode_error":"Expecting ',' delimiter: line 2 column 7 (char 10)"}, +{"text":"[]","value":["list",[]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"{\"forma{t\": 1, \"artifact\" \"a.erofs\", \"packag.es\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting ':' delimiter: line 1 column 27 (char 26)"}, +{"text":" \t\r\n[1,2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[\"A\u001f1\", \"B=\\u00e9\", \"C=\\ud83d.ude00\"]","decode_error":"Invalid control character at: line 1 column 4 (char 3)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"puackages\": [{\"name\"[ \"systemd\"}]}","decode_error":"Expecting ':' delimiter: line 1 column 59 (char 58)"}, +{"text":"-\t\r\n[1 , 2 ] ","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"{\"a\": [true, false,null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"{\"format\" 1, \"artifact\": \"a.er.ofs\", \"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting ':' delimiter: line 1 column 11 (char 10)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"{\"a\": [true, alse, null], \"a\": 0{\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 14 (char 13)"}, +{"text":"{\"for\"mat\": 1, \"artifact\": \"a.erofs\", \u0000\"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting ':' delimiter: line 1 column 7 (char 6)"}, +{"text":"\"A=1\", \"B=\\u00e9\", \"C=\ufeff\\ud83d\\ude00\"]","decode_error":"Extra data: line 1 column 6 (char 5)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud8\rx\"","decode_error":"Invalid \\uXXXX escape: line 1 column 21 (char 20)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[\"A=1\", \"B=\\\ufeffu00e9\", \"C=\\u{d83d\\ude00\"]","decode_error":"Invalid \\escape: line 1 column 12 (char 11)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"[\"A=null\"\", \"B=\\u00e9\", \"C=ud83d\\ude00\"]","decode_error":"Expecting ',' delimiter: line 1 column 10 (char 9)"}, +{"text":"[NaN, Infinity, -Infinit, 1e400, -0.0, 1E5, 10, -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83dnull\\udex0\"]","decode_error":"Invalid \\uXXXX escape: line 1 column 35 (char 34)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"{format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, d8000, -3]","decode_error":"Expecting value: line 1 column 47 (char 46)"}, +{"text":"[[","decode_error":"Expecting value: line 1 column 3 (char 2)"}, +{"text":"{} ","value":["dict",[]]}, +{"text":"{\"a\": [,rue, flse, null], \"a\": {\"b\": \"\\n\\t\t\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 8 (char 7)"}, +{"text":"Infinity}","decode_error":"Extra data: line 1 column 9 (char 8)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"[\"A1\"], \"B=\\u00e9\", \"C=\\ud83d\\ude00 ]","decode_error":"Extra data: line 1 column 7 (char 6)"}, +{"text":" \t\t1x\n[1 , 2 ] ","decode_error":"Extra data: line 1 column 5 (char 4)"}, +{"text":"{\"]Infinityformat\"::1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting value: line 1 column 20 (char 19)"}, +{"text":"\"}","decode_error":"Unterminated string starting at: line 1 column 1 (char 0)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"e=\\ud83d\"ude00\"]","decode_error":"Expecting ',' delimiter: line 1 column 31 (char 30)"}, +{"text":"} \t\r\n,1 2 ] ","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[]","value":["list",[]]}, +{"text":"NaN[]","decode_error":"Extra data: line 1 column 4 (char 3)"}, +{"text":"{nulla\": [true, false, null], \"a\": {\"b : \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":" NaN\n[1 , 2 ]","decode_error":"Extra data: line 2 column 1 (char 5)"}, +{"text":" \t\r\n[1 , NaN [ ","decode_error":"Expecting ',' delimiter: line 2 column 10 (char 13)"}, +{"text":"[\"Au=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","Au=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"\"a\": [true, false, null], \"e\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Extra data: line 1 column 4 (char 3)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"NaN\r\\n\\t\\\\+\"\\/\"}}","decode_error":"Invalid control character at: line 1 column 43 (char 42)"}, +{"text":",]]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"\"\\ud800udc00\\udc00\\ud800xe\"","value":["code_points",[55296,117,100,99,48,48,56320,55296,120,101]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"[.NaN, Infinity, -Infinity, ,e400, -0.0, 1E-5, 10, -3]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":" d800\r\n[1 ,\"2 ] ","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, 0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","0000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", e\"C=\\ud83d\\ude00\"]","decode_error":"Expecting value: line 1 column 21 (char 20)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \u0000\"C=\\ud83d\\unull\ufeff00\"]","decode_error":"Expecting value: line 1 column 21 (char 20)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":" \t\n[1NaN, 2 ] ","decode_error":"Expecting ',' delimiter: line 2 column 3 (char 5)"}, +{"text":"{\"format\": 1, \"atifact\": \"x.erofs\", \"packages\": [{\"nuame\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","atifact"],["str","x.erofs"]],[["str","packages"],["list",[["dict",[[["str","nuame"],["str","systemd"]]]]]]]]]}, +{"text":"{\"a\": [tr\"ue, f\\alse, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 8 (char 7)"}, +{"text":"[\"A=1\", \"B=\\NaNu00e\", \"\tC=\\ud83d\\ude00\"]","decode_error":"Invalid \\escape: line 1 column 12 (char 11)"}, +{"text":"{true}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"\"\\ud800\\udc00\\unulldc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 15 (char 14)"}, +{"text":"[NaN","decode_error":"Expecting ',' delimiter: line 1 column 5 (char 4)"}, +{"text":"{\\","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"[\"A=1\", B=\\u00e9\", \"C=\\uud83d\\ude00\"]","decode_error":"Expecting value: line 1 column 9 (char 8)"}, +{"text":"\"\\ud800\\udc0\\udc00\\u800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 9 (char 8)"}, +{"text":"[NaN, Infinity, -Infinit\u0000, 1e4\ufeff00, -0.0, 1E-5, 10, -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"\t\r\n[1\\ , 2 ]- ","decode_error":"Expecting ',' delimiter: line 2 column 3 (char 5)"}, +{"text":"{,\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"{},","decode_error":"Extra data: line 1 column 3 (char 2)"}, +{"text":"{\"a\": [true, false, nul], \"a\": {\"b\\: \"\\n\t\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 21 (char 20)"}, +{"text":"","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"-}\\ud800\\u c00\\udc00\\ud800x\"","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[]","value":["list",[]]}, +{"text":"[A=1\", \"B=\\u0[e9\", \"C=\\ud83d\\ude00\"]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"{\"a\": [true, fals\ufeffe,null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 14 (char 13)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":" {\r\n[1 ,2 ] ","decode_error":"Expecting property name enclosed in double quotes: line 2 column 1 (char 4)"}, +{"text":"{\\}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":" \t\r\n[1 , 2 ] ","value":["list",[["int","1"],["int","2"]]]}, +{"text":"[\"A+=1\", \"B=\\u0\u00e9e9\", \"C=\\ud83d\\ude00\"]","decode_error":"Invalid \\uXXXX escape: line 1 column 14 (char 13)"}, +{"text":"]]\n","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{\"a\": [\rrue, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 9 (char 8)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\-\"\\/\"}}","decode_error":"Expecting ',' delimiter: line 1 column 48 (char 47)"}, +{"text":"\"\\ud800\\u\u0000dc00\\udc00\\ud80,0x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 9 (char 8)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"{eNaN","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"[\"A=1\", \"=\\u00e9\", \"C=\\ud83true\\ude00\"]","decode_error":"Invalid \\uXXXX escape: line 1 column 24 (char 23)"}, +{"text":"[ \t\n[1 2 ] ","decode_error":"Expecting ',' delimiter: line 2 column 5 (char 8)"}, +{"text":"[]","value":["list",[]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"{\"a\": truetr:ue, false, null], \"a\"null {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting ',' delimiter: line 1 column 11 (char 10)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00{]","decode_error":"Unterminated string starting at: line 1 column 21 (char 20)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400,x -0.0, 1E-5, 10,-3]","decode_error":"Expecting value: line 1 column 34 (char 33)"}, +{"text":"+]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"\"\\ud800d800\\udc00\\udc00\\ud800x\"","value":["code_points",[55296,100,56,48,48,56320,56320,55296,120]]}, +{"text":"\"\\ud800\\dc00\\udc0\\ud800x\"","decode_error":"Invalid \\escape: line 1 column 8 (char 7)"}, +{"text":"","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[]","value":["list",[]]}, +{"text":"\u0000","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[\"A=1\",\r\"B=\\u00e9\", \"C=\\ud83d\\ud\u000000\"]","decode_error":"Invalid \\uXXXX escape: line 1 column 31 (char 30)"}, +{"text":"\"\\d800\\udc00\\udc00\\ud8e00x","decode_error":"Invalid \\escape: line 1 column 2 (char 1)"}, +{"text":"[\"A=1\", \"d800B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","d800B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[\"A=1\"Infinity, \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","decode_error":"Expecting ',' delimiter: line 1 column 7 (char 6)"}, +{"text":"[]\u00e9","decode_error":"Extra data: line 1 column 3 (char 2)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"{}","value":["dict",[]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1 -5:, 10, -3]","decode_error":"Expecting ',' delimiter: line 1 column 43 (char 42)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"{\"a\": true, false, n0ll], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 13 (char 12)"}, +{"text":"{\"\normat\": 1, \"arttrueifact\": \"a.erofs\", \"packages\": [{\"name\": \"systmd\"}]}","decode_error":"Invalid control character at: line 1 column 3 (char 2)"}, +{"text":"[]","value":["list",[]]}, +{"text":"\"\\ud800\\+udc00\\udc\t0\\ud800x\"","decode_error":"Invalid \\escape: line 1 column 8 (char 7)"}, +{"text":" \t\r\n1 , ]\ufeff","decode_error":"Extra data: line 2 column 3 (char 6)"}, +{"text":"\"\\ud800\\u+dc00\\udc00\\ud00xnull\"","decode_error":"Invalid \\uXXXX escape: line 1 column 9 (char 8)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"{\"a\": [true, false, null], \"a\"+:{\"b\"NaN \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting ':' delimiter: line 1 column 31 (char 30)"}, +{"text":"\"\\ud800\\udc00\\udc00\\u800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 21 (char 20)"}, +{"text":"[NaN, Infinity, -Infinity, 1e4\u001f00, -0.0, 1E-5, 10, -3]","decode_error":"Expecting ',' delimiter: line 1 column 31 (char 30)"}, +{"text":"\"\\ud800\\udc00\\udc0u0\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 15 (char 14)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erof\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erof"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"\"\\ud800\\udc00\\udc00\\u800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 21 (char 20)"}, +{"text":"{\"a\": [true, d800false, null], \"a\": {\"b\": \"\\nt:\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 14 (char 13)"}, +{"text":"","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[.e","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"[]","value":["list",[]]}, +{"text":"[\"A=1u\", \"B=\\u00e9\",\"C=\\ud83dInfinity\\ude00\"]","value":["list",[["str","A=1u"],["str","B=\u00e9"],["code_points",[67,61,55357,73,110,102,105,110,105,116,121,56832]]]]}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str","\n\t\\\"/"]]]]]]]}, +{"text":"{}","value":["dict",[]]}, +{"text":"{\"format\"0 1, \"artifact\": \"a.erofs\", \"packages\"\t [{\"name\": \"systemd\"}]}","decode_error":"Expecting ':' delimiter: line 1 column 10 (char 9)"}, +{"text":"{\n","decode_error":"Expecting property name enclosed in double quotes: line 2 column 1 (char 2)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3:","decode_error":"Expecting ',' delimiter: line 1 column 53 (char 52)"}, +{"text":"[NaN, Infinity, Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","7ff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud8-d\\ude00\"]","decode_error":"Invalid \\uXXXX escape: line 1 column 25 (char 24)"}, +{"text":"]\\1","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\",\u001f\"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting property name enclosed in double quotes: line 1 column 37 (char 36)"}, +{"text":"[","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"{\"a\": [true,, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 13 (char 12)"}, +{"text":"\"\\ud80\\udc00\\uInfinityc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"{e","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"{\"a\": [true, fa.l0se,null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 14 (char 13)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"\"\\ud800\\udc00\\udc00null\\ud800x\"","value":["code_points",[65536,56320,110,117,108,108,55296,120]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs1, \"packages\": [{\"name\": \"systemd\"}]}\u001f","decode_error":"Expecting ',' delimiter: line 1 column 39 (char 38)"}, +{"text":"Infinity][,]","decode_error":"Extra data: line 1 column 9 (char 8)"}, +{"text":"[]","value":["list",[]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"[]","value":["list",[]]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":" \t\r\n[1[ true 2 ] ","decode_error":"Expecting ',' delimiter: line 2 column 3 (char 6)"}, +{"text":"\u0000}","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[NaN, Infinity, -InfNaNnity, 1e400, -0.0 1E-5,Infinity10, -3]","decode_error":"Expecting value: line 1 column 17 (char 16)"}, +{"text":"[]","value":["list",[]]}, +{"text":"\"\\ud8}\\udc00\\udc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"[NaN, Infinit, -Infinity, 1e400, -0.0, 1E-5, 1, -3]","decode_error":"Expecting value: line 1 column 7 (char 6)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=\u00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"{}","value":["dict",[]]}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\true\r\\\\\\\"\\/\"}}","decode_error":"Invalid control character at: line 1 column 47 (char 46)"}, +{"text":" true\t\r\n[1\t , 2 ] ","decode_error":"Extra data: line 2 column 1 (char 8)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"[Na, Infinity, -Infinity, 1e400, -0.0, 1E-5, 010, -3]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":" \"\r\n[1 . 2 ] ","decode_error":"Invalid control character at: line 1 column 3 (char 2)"}, +{"text":"\n}","decode_error":"Expecting value: line 2 column 1 (char 1)"}, +{"text":"[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","value":["list",[["float","7ff8000000000000"],["float","7ff0000000000000"],["float","fff0000000000000"],["float","7ff0000000000000"],["float","8000000000000000"],["float","3ee4f8b588e368f1"],["int","10"],["int","-3"]]]}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\\t/\"}}","decode_error":"Invalid \\escape: line 1 column 48 (char 47)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"[NaN Infinity, -Infinity, 1e0400+ -0.0, 1E-5, 10, -3]","decode_error":"Expecting ',' delimiter: line 1 column 6 (char 5)"}, +{"text":"{\"a\": +true, false, +ull], \"a\": {\"b\":\"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 7 (char 6)"}, +{"text":"","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \\C=\\ud83d\\ude0[0\"]","decode_error":"Expecting value: line 1 column 21 (char 20)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"[NauN, In{inity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"\"0\\ud800\\udc0\t\\udc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 10 (char 9)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"package+\": [{\"name\": \"systemd}]}","decode_error":"Unterminated string starting at: line 1 column 60 (char 59)"}, +{"text":"{}}","decode_error":"Extra data: line 1 column 3 (char 2)"}, +{"text":"{}","value":["dict",[]]}, +{"text":"[","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"{\"format\": 1null, \"artifact\": \"a.erofs\", \"pd800ack\ufeffages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting ',' delimiter: line 1 column 13 (char 12)"}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \" n\\t\\\\\\\"\\/\"}}","value":["dict",[[["str","a"],["dict",[[["str","b"],["str"," n\t\\\"/"]]]]]]]}, +{"text":"{\"a\": [true, false, null], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\t\"}}","decode_error":"Invalid control character at: line 1 column 50 (char 49)"}, +{"text":"true]","decode_error":"Extra data: line 1 column 5 (char 4)"}, +{"text":"\"\\ud8:0\\udc00\\xdc00\\ud800x\"","decode_error":"Invalid \\uXXXX escape: line 1 column 3 (char 2)"}, +{"text":"\r]","decode_error":"Expecting value: line 1 column 2 (char 1)"}, +{"text":"}","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"\"\\ud800\\ud00\\udc00\\ud80truex\"","decode_error":"Invalid \\uXXXX escape: line 1 column 9 (char 8)"}, +{"text":"{\"format\": 1 \"artifact\": \"a.erofs\",\"\"packages\": [{\\\"name\": \"systemd\"}]}","decode_error":"Expecting ',' delimiter: line 1 column 14 (char 13)"}, +{"text":"\"\\ud800\\udc00\\udc00\\ud800x\"","value":["code_points",[65536,56320,55296,120]]}, +{"text":"[\"A=1\", \"B=u[00e9\", \"C=\\ud83d\\ude00\"]","value":["list",[["str","A=1"],["str","B=u[00e9"],["str","C=\ud83d\ude00"]]]}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"ptrueckages: 0[{\"name\": \"systemd\"}]}","decode_error":"Expecting ':' delimiter: line 1 column 56 (char 55)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.\u00e9rofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.\u00e9rofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"{}","value":["dict",[]]}, +{"text":"{.}e","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":" \r\r\n[11 , 2 ]\r","value":["list",[["int","11"],["int","2"]]]}, +{"text":"[\"A=1\",]\"B=\\u00e9\", \"C=\\u\"83,d\\ude00\"]","decode_error":"Illegal trailing comma before end of array: line 1 column 7 (char 6)"}, +{"text":"{","decode_error":"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"text":"[]","value":["list",[]]}, +{"text":"{\nforNaNat\":. 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","decode_error":"Expecting property name enclosed in double quotes: line 2 column 1 (char 2)"}, +{"text":"{\"A=1\", \"B=\\u00e9\"e \"nullC=\\ud83d\\ude00\"]","decode_error":"Expecting ':' delimiter: line 1 column 7 (char 6)"}, +{"text":"[\taN, InfiniInfinityy, -Infinity, 1e400, -0.0, 1E-5, 10 -3]","decode_error":"Expecting value: line 1 column 3 (char 2)"}, +{"text":"{\"format\": 1, \"artifact\":\"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"]","decode_error":"Expecting value: line 1 column 1 (char 0)"}, +{"text":"{\"a\": [tr.e, false, nu[l], \"a\": {\"b\": \"\\n\\t\\\\\\\"\\/\"}}","decode_error":"Expecting value: line 1 column 8 (char 7)"}, +{"text":" \t\r\n[1]. 2 ] ","decode_error":"Extra data: line 2 column 4 (char 7)"}, +{"text":"{\"format\": 1, \"artifact\": \"a.erofs\", \"packages\": [{\"name\": \"systemd\"}]}","value":["dict",[[["str","format"],["int","1"]],[["str","artifact"],["str","a.erofs"]],[["str","packages"],["list",[["dict",[[["str","name"],["str","systemd"]]]]]]]]]}, +{"text":"{\"","decode_error":"Unterminated string starting at: line 1 column 2 (char 1)"} +], +"utf8": [ +{"bytes":"6f6b","error":null}, +{"bytes":"ff","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"61e282","error":"'utf-8' codec can't decode bytes in position 1-2: unexpected end of data"}, +{"bytes":"e228","error":"'utf-8' codec can't decode byte 0xe2 in position 0: invalid continuation byte"}, +{"bytes":"f09028","error":"'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte"}, +{"bytes":"eda080","error":"'utf-8' codec can't decode byte 0xed in position 0: invalid continuation byte"}, +{"bytes":"c080","error":"'utf-8' codec can't decode byte 0xc0 in position 0: invalid start byte"}, +{"bytes":"f4908080","error":"'utf-8' codec can't decode byte 0xf4 in position 0: invalid continuation byte"}, +{"bytes":"e08080","error":"'utf-8' codec can't decode byte 0xe0 in position 0: invalid continuation byte"}, +{"bytes":"f0808080","error":"'utf-8' codec can't decode byte 0xf0 in position 0: invalid continuation byte"}, +{"bytes":"f09f98","error":"'utf-8' codec can't decode bytes in position 0-2: unexpected end of data"}, +{"bytes":"8061f09f","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"c3a9c3e282ac6161","error":"'utf-8' codec can't decode byte 0xc3 in position 2: invalid continuation byte"}, +{"bytes":"ff61c3a980e282ac","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"80","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"","error":null}, +{"bytes":"6180e261","error":"'utf-8' codec can't decode byte 0x80 in position 1: invalid start byte"}, +{"bytes":"f09f988061e282acff","error":"'utf-8' codec can't decode byte 0xff in position 8: invalid start byte"}, +{"bytes":"80e282ace282acffff","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"f09f9880e282ac80","error":"'utf-8' codec can't decode byte 0x80 in position 7: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"616161","error":null}, +{"bytes":"e282ace282acf09f9880e282ace282acc3a9","error":null}, +{"bytes":"e282ac9f9880e282acff","error":"'utf-8' codec can't decode byte 0x9f in position 3: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"8080c3a9ffc3a961","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"c3a961","error":null}, +{"bytes":"e282ac","error":null}, +{"bytes":"","error":null}, +{"bytes":"","error":null}, +{"bytes":"e282","error":"'utf-8' codec can't decode bytes in position 0-1: unexpected end of data"}, +{"bytes":"c3a9e282ac","error":null}, +{"bytes":"c361e282ac","error":"'utf-8' codec can't decode byte 0xc3 in position 0: invalid continuation byte"}, +{"bytes":"c3a980f09f9880","error":"'utf-8' codec can't decode byte 0x80 in position 2: invalid start byte"}, +{"bytes":"e282acf09f988080e2","error":"'utf-8' codec can't decode byte 0x80 in position 7: invalid start byte"}, +{"bytes":"e282ace2acff80c3a9","error":"'utf-8' codec can't decode bytes in position 3-4: invalid continuation byte"}, +{"bytes":"","error":null}, +{"bytes":"","error":null}, +{"bytes":"e282ac","error":null}, +{"bytes":"80c3a9c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"f09f9880c3a9e282ac61ff","error":"'utf-8' codec can't decode byte 0xff in position 10: invalid start byte"}, +{"bytes":"e282acf09f","error":"'utf-8' codec can't decode bytes in position 3-4: unexpected end of data"}, +{"bytes":"ff6161ff","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"f09f9880e282e282acc3a9","error":"'utf-8' codec can't decode bytes in position 4-5: invalid continuation byte"}, +{"bytes":"e282acfff09f9880ff","error":"'utf-8' codec can't decode byte 0xff in position 3: invalid start byte"}, +{"bytes":"ffffc3a9f09f9880c3a9f09f9880","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a9e282ac61e2e282ac","error":"'utf-8' codec can't decode byte 0xe2 in position 6: invalid continuation byte"}, +{"bytes":"8061e282ac","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"ffff80","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"80c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"61ff","error":"'utf-8' codec can't decode byte 0xff in position 1: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"61ffff","error":"'utf-8' codec can't decode byte 0xff in position 1: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"c3a961ff","error":"'utf-8' codec can't decode byte 0xff in position 3: invalid start byte"}, +{"bytes":"61806180","error":"'utf-8' codec can't decode byte 0x80 in position 1: invalid start byte"}, +{"bytes":"acf09f9880e282ac6180","error":"'utf-8' codec can't decode byte 0xac in position 0: invalid start byte"}, +{"bytes":"e282acf09f9880","error":null}, +{"bytes":"8061","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"c3a9f09f9880e282ac","error":null}, +{"bytes":"61e282ac","error":null}, +{"bytes":"e282c3a9f09f9880c3a9e282acc3a9","error":"'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte"}, +{"bytes":"80f09f9880e2ac61c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"f09f9880e282acc3a9e282ac61","error":null}, +{"bytes":"61ac80f09f9880","error":"'utf-8' codec can't decode byte 0xac in position 1: invalid start byte"}, +{"bytes":"618061a9","error":"'utf-8' codec can't decode byte 0x80 in position 1: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"","error":null}, +{"bytes":"ffe282ac","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"80ff80","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"f09f9880","error":null}, +{"bytes":"c3a980c3a9c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 2: invalid start byte"}, +{"bytes":"ffa980","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"e282ac","error":null}, +{"bytes":"80ff80ff","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"e282acf09f9880e282ac","error":null}, +{"bytes":"6161","error":null}, +{"bytes":"e282ace282acc3a9c3a9c3a9","error":null}, +{"bytes":"82ac8061e282ac","error":"'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte"}, +{"bytes":"f09880e282ac","error":"'utf-8' codec can't decode bytes in position 0-2: invalid continuation byte"}, +{"bytes":"f09f9880f09f988061618061","error":"'utf-8' codec can't decode byte 0x80 in position 10: invalid start byte"}, +{"bytes":"619880f09f9880","error":"'utf-8' codec can't decode byte 0x98 in position 1: invalid start byte"}, +{"bytes":"e282acc3a9f09f9880fff09880","error":"'utf-8' codec can't decode byte 0xff in position 9: invalid start byte"}, +{"bytes":"61e282ac809880c3a9f09f9880","error":"'utf-8' codec can't decode byte 0x80 in position 4: invalid start byte"}, +{"bytes":"fff09f988061f09f9880f09f98","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"9f9880e282ac","error":"'utf-8' codec can't decode byte 0x9f in position 0: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"c3a9fff09f9880c3a9c3a9f09f9880","error":"'utf-8' codec can't decode byte 0xff in position 2: invalid start byte"}, +{"bytes":"c3a98061","error":"'utf-8' codec can't decode byte 0x80 in position 2: invalid start byte"}, +{"bytes":"ff80f080","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"","error":null}, +{"bytes":"ff6180ffff","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a9","error":null}, +{"bytes":"8061f09f9880618080","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"c3a961c3a961","error":null}, +{"bytes":"61f09f988080c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 5: invalid start byte"}, +{"bytes":"ff","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"e282ac80c3a9c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 3: invalid start byte"}, +{"bytes":"f0988061","error":"'utf-8' codec can't decode bytes in position 0-2: invalid continuation byte"}, +{"bytes":"ff8061f09f9880c3a9a9","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a96180fff09f98","error":"'utf-8' codec can't decode byte 0x80 in position 3: invalid start byte"}, +{"bytes":"f09f9880f09f9880f080c3a9c3a9","error":"'utf-8' codec can't decode byte 0xf0 in position 8: invalid continuation byte"}, +{"bytes":"82acc3a9ff","error":"'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte"}, +{"bytes":"ff61ace282ac","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"e282ace282ac98808061e282ac","error":"'utf-8' codec can't decode byte 0x98 in position 6: invalid start byte"}, +{"bytes":"c3a980e2ace282ac","error":"'utf-8' codec can't decode byte 0x80 in position 2: invalid start byte"}, +{"bytes":"6180","error":"'utf-8' codec can't decode byte 0x80 in position 1: invalid start byte"}, +{"bytes":"ffc3a980e282ac80c3a9","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"80c3a9e282acf09f9880f09f988061","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"61f09f98808061ff","error":"'utf-8' codec can't decode byte 0x80 in position 5: invalid start byte"}, +{"bytes":"e2acf09f9880","error":"'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte"}, +{"bytes":"ff","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"80","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"82ac","error":"'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte"}, +{"bytes":"ff6161f09f9880c3a9","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"e282acfff09f9880","error":"'utf-8' codec can't decode byte 0xff in position 3: invalid start byte"}, +{"bytes":"ffc3a980","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"80c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"f0988080ff","error":"'utf-8' codec can't decode byte 0xff in position 4: invalid start byte"}, +{"bytes":"80c3a9c3a9","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"6182ac","error":"'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"61c3a9fff09f9880ff","error":"'utf-8' codec can't decode byte 0xff in position 3: invalid start byte"}, +{"bytes":"8080","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"616180","error":"'utf-8' codec can't decode byte 0x80 in position 2: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"e282ac61","error":null}, +{"bytes":"","error":null}, +{"bytes":"e282ac8080","error":"'utf-8' codec can't decode byte 0x80 in position 3: invalid start byte"}, +{"bytes":"f09f988080ffe282acc3ff","error":"'utf-8' codec can't decode byte 0x80 in position 4: invalid start byte"}, +{"bytes":"80fff09f98","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"61f09f988061","error":null}, +{"bytes":"e282acc3a9e282ac80ffc3","error":"'utf-8' codec can't decode byte 0x80 in position 8: invalid start byte"}, +{"bytes":"fff09f988061","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"ff61","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a9e282acc3a961fff09f9880","error":"'utf-8' codec can't decode byte 0xff in position 8: invalid start byte"}, +{"bytes":"","error":null}, +{"bytes":"f09f988061e282ac8061ff","error":"'utf-8' codec can't decode byte 0x80 in position 8: invalid start byte"}, +{"bytes":"c3a9e282ace2ac8061","error":null}, +{"bytes":"","error":null}, +{"bytes":"618080","error":"'utf-8' codec can't decode byte 0x80 in position 1: invalid start byte"}, +{"bytes":"c3a96161e2ac","error":"'utf-8' codec can't decode bytes in position 4-5: unexpected end of data"}, +{"bytes":"","error":null}, +{"bytes":"ffe282ace282e282ac6161","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"e282acc3a9f09f988061f09f9880e282ac","error":null}, +{"bytes":"f09f9880","error":null}, +{"bytes":"f09f9880e282acffc3a9","error":"'utf-8' codec can't decode byte 0xff in position 7: invalid start byte"}, +{"bytes":"61e282","error":"'utf-8' codec can't decode bytes in position 1-2: unexpected end of data"}, +{"bytes":"f09f98","error":"'utf-8' codec can't decode bytes in position 0-2: unexpected end of data"}, +{"bytes":"8061ff","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}, +{"bytes":"ffa9f09f9880f09f9880","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"ffc3a9f09f9880","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"ff","error":"'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"}, +{"bytes":"c3a9ff80f09f98","error":"'utf-8' codec can't decode byte 0xff in position 2: invalid start byte"}, +{"bytes":"80c3a9f09f9880","error":"'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"} +] +} diff --git a/nvx-rs/tests/fixtures/sandbox.json b/nvx-rs/tests/fixtures/sandbox.json new file mode 100644 index 000000000..f46a8ff59 --- /dev/null +++ b/nvx-rs/tests/fixtures/sandbox.json @@ -0,0 +1,1431 @@ +{ +"generator": "scripts/nvx_rs_fixtures.py", +"python": "3.14", +"cwds": {"posix": "/fixture/cwd", "windows": "C:\\fixture\\cwd"}, +"identities": [ +{"value":"1000:1001","identity":[1000,1001]}, +{"value":"0:0","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"1000","error":"--workload-user must be UID:GID"}, +{"value":"1:2:3","error":"--workload-user must be UID:GID"}, +{"value":"a:1","error":"--workload-user must contain numeric UID and GID"}, +{"value":"1:","error":"--workload-user must contain numeric UID and GID"}, +{"value":" +1_000 :\uff11\uff10","identity":[1000,10]}, +{"value":"4294967295:1","identity":[4294967295,1]}, +{"value":"4294967296:1","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"-1:1","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"1:0","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"1:0x10","error":"--workload-user must contain numeric UID and GID"}, +{"value":"11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111:1","error":"--workload-user must contain numeric UID and GID"}, +{"value":"_9:","error":"--workload-user must contain numeric UID and GID"}, +{"value":"9","error":"--workload-user must be UID:GID"}, +{"value":"5\u30005191x1+ ","error":"--workload-user must be UID:GID"}, +{"value":"\u3000\u0661","error":"--workload-user must be UID:GID"}, +{"value":"- _:5_+_9","error":"--workload-user must contain numeric UID and GID"}, +{"value":" 1:x9","error":"--workload-user must contain numeric UID and GID"}, +{"value":"-_1\u0661\u30005","error":"--workload-user must be UID:GID"}, +{"value":"\u30000++_5: 1","error":"--workload-user must contain numeric UID and GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"\u0661115+1","error":"--workload-user must be UID:GID"}, +{"value":"+91:-59 1: 1","error":"--workload-user must be UID:GID"}, +{"value":"_\u06610x\u0661\u3000 _ 9x9","error":"--workload-user must be UID:GID"}, +{"value":"15:0x-\u06611_0","error":"--workload-user must contain numeric UID and GID"}, +{"value":"+0\u06615\u300015_","error":"--workload-user must be UID:GID"}, +{"value":" \u06615_00+:","error":"--workload-user must contain numeric UID and GID"}, +{"value":"5:\u0661","identity":[5,1]}, +{"value":" x__--","error":"--workload-user must be UID:GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"1-\u06611_0:\u066199","error":"--workload-user must contain numeric UID and GID"}, +{"value":":1","error":"--workload-user must contain numeric UID and GID"}, +{"value":"\u0661+\u30001-_9_","error":"--workload-user must be UID:GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"_\u0661 :\u3000","error":"--workload-user must contain numeric UID and GID"}, +{"value":":1:-:\u30001_","error":"--workload-user must be UID:GID"}, +{"value":"5_","error":"--workload-user must be UID:GID"}, +{"value":"0:\u0661001x","error":"--workload-user must contain numeric UID and GID"}, +{"value":"0-9","error":"--workload-user must be UID:GID"}, +{"value":":5++1-1: 5__","error":"--workload-user must be UID:GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"5_x","error":"--workload-user must be UID:GID"}, +{"value":"5-_\u3000-\u0661","error":"--workload-user must be UID:GID"}, +{"value":" 0\u3000 ","error":"--workload-user must be UID:GID"}, +{"value":"_\u300011+\u0661 5 x \u3000","error":"--workload-user must be UID:GID"}, +{"value":"_\u0661","error":"--workload-user must be UID:GID"}, +{"value":"-x","error":"--workload-user must be UID:GID"}, +{"value":"\u0661\u300011 -5-","error":"--workload-user must be UID:GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"5","error":"--workload-user must be UID:GID"}, +{"value":" :5\u3000_\u06615 0x59","error":"--workload-user must contain numeric UID and GID"}, +{"value":"1","error":"--workload-user must be UID:GID"}, +{"value":"+-_90:+ x","error":"--workload-user must contain numeric UID and GID"}, +{"value":"_5-0\u3000:","error":"--workload-user must contain numeric UID and GID"}, +{"value":"++0x\u0661\u3000-_:\u0661","error":"--workload-user must contain numeric UID and GID"}, +{"value":"91:00-","error":"--workload-user must contain numeric UID and GID"}, +{"value":"\u3000 \u06619:+","error":"--workload-user must contain numeric UID and GID"}, +{"value":"0\u0661_","error":"--workload-user must be UID:GID"}, +{"value":"1x\u0661\u0661::","error":"--workload-user must be UID:GID"}, +{"value":"+_\u3000","error":"--workload-user must be UID:GID"}, +{"value":"__-\u0661- _-x_ ","error":"--workload-user must be UID:GID"}, +{"value":"\u0661_15\u0661:x_\u3000_5","error":"--workload-user must contain numeric UID and GID"}, +{"value":"x9_\u3000::","error":"--workload-user must be UID:GID"}, +{"value":" 99","error":"--workload-user must be UID:GID"}, +{"value":"x055:++","error":"--workload-user must contain numeric UID and GID"}, +{"value":"\u0661-+ ","error":"--workload-user must be UID:GID"}, +{"value":"111-\u0661+\u0661\u3000","error":"--workload-user must be UID:GID"}, +{"value":"00\u06611","error":"--workload-user must be UID:GID"}, +{"value":" _0\u3000_ \u3000-","error":"--workload-user must be UID:GID"}, +{"value":"xxx0_-\u06610\u3000+","error":"--workload-user must be UID:GID"}, +{"value":"\u06611x -5","error":"--workload-user must be UID:GID"}, +{"value":"0 \u30001-_5 5\u3000 ","error":"--workload-user must be UID:GID"}, +{"value":"5:5-x+_5","error":"--workload-user must contain numeric UID and GID"}, +{"value":"\u0661x","error":"--workload-user must be UID:GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"-5:+\u066109\u0661-","error":"--workload-user must contain numeric UID and GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"_0\u3000_5+\u0661\u0661","error":"--workload-user must be UID:GID"}, +{"value":":-1\u0661 \u0661 ","error":"--workload-user must contain numeric UID and GID"}, +{"value":"9-5","error":"--workload-user must be UID:GID"}, +{"value":"\u0661\u3000x9\u3000\u0661 0:x","error":"--workload-user must contain numeric UID and GID"}, +{"value":"\u0661++ ","error":"--workload-user must be UID:GID"}, +{"value":" 0+_:x1x_","error":"--workload-user must contain numeric UID and GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"\u3000\u06611\u06615 +9","error":"--workload-user must be UID:GID"}, +{"value":"1\u06610","error":"--workload-user must be UID:GID"}, +{"value":"1","error":"--workload-user must be UID:GID"}, +{"value":"-","error":"--workload-user must be UID:GID"}, +{"value":"\u0661-\u3000\u0661\u3000\u3000+_\u3000x","error":"--workload-user must be UID:GID"}, +{"value":" 1055x","error":"--workload-user must be UID:GID"}, +{"value":"::0_0 +::0 :","error":"--workload-user must be UID:GID"}, +{"value":"1- 9\u3000 +x-","error":"--workload-user must be UID:GID"}, +{"value":"99__:","error":"--workload-user must contain numeric UID and GID"}, +{"value":"++","error":"--workload-user must be UID:GID"}, +{"value":" 0+:1\u3000 xx","error":"--workload-user must contain numeric UID and GID"}, +{"value":"9\u30000\u0661:_","error":"--workload-user must contain numeric UID and GID"}, +{"value":"+5+ \u06611 ","error":"--workload-user must be UID:GID"}, +{"value":"-\u0661x:+\u0661- ","error":"--workload-user must contain numeric UID and GID"}, +{"value":"0\u06611 5+x++x9","error":"--workload-user must be UID:GID"}, +{"value":"__\u3000","error":"--workload-user must be UID:GID"}, +{"value":"99 ","error":"--workload-user must be UID:GID"}, +{"value":"","error":"--workload-user must be UID:GID"}, +{"value":"9_\u0661_\u0661 9_","error":"--workload-user must be UID:GID"}, +{"value":"\u3000151","error":"--workload-user must be UID:GID"}, +{"value":"90","error":"--workload-user must be UID:GID"}, +{"value":"\u30000 +x+0\u3000x_ ","error":"--workload-user must be UID:GID"}, +{"value":"_01 \u3000+\u06619","error":"--workload-user must be UID:GID"}, +{"value":"5-9","error":"--workload-user must be UID:GID"}, +{"value":"5-10+\u0661009 0+","error":"--workload-user must be UID:GID"}, +{"value":"1+\u3000:+5+","error":"--workload-user must contain numeric UID and GID"}, +{"value":"1+:__","error":"--workload-user must contain numeric UID and GID"}, +{"value":"\t67 :\u3000-65534\t","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\u3000-65534\u3000:\t-65534\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":" +40 :\t-073 ","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\u300006_5534:\t065534\u3000","identity":[65534,65534]}, +{"value":"65534 : -65534\u3000","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":" 65534\u3000: 6553_4\t","identity":[65534,65534]}, +{"value":"\u3000027044425_37\t: -172085870 ","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\t-\uff12\uff17\uff16\uff16\uff18\uff13\uff16\uff16\uff18\uff14\u3000: -1376434866","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":" \uff10\uff12\u3000:\u3000-3315681680\u3000","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\u3000-543827860\t:06_1\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":" +4118025408:\t+\uff16\uff15\uff15\uff13_\uff14\t","identity":[4118025408,65534]}, +{"value":"\t+3062_851463\u3000:\u300002357216467\u3000","identity":[3062851463,2357216467]}, +{"value":"\u30001_3\t:053_3716954\t","identity":[13,533716954]}, +{"value":"65534 :\t65534\t","identity":[65534,65534]}, +{"value":"\t6_7\u3000: 65534\t","identity":[67,65534]}, +{"value":" 831927946\u3000:\u3000\uff16\uff15\uff15\uff13\uff14\t","identity":[831927946,65534]}, +{"value":" 3720_501321\t:-72814282 ","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\t-\uff15\uff12 :\t362_7849867\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u3000+59\u3000: 3399312181\t","identity":[59,3399312181]}, +{"value":" 65534:\t-73 ","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"065534\t: 94 ","identity":[65534,94]}, +{"value":"\u30001377235406\u3000: -\uff13\uff13\uff16\uff19\uff13\uff18\uff17\uff17\uff17\uff18 ","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":" +084:-672843280\u3000","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":" 65534\u3000:\u3000-9_0566714","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"+58\u3000: 04006110698","identity":[58,4006110698]}, +{"value":"65534\t:\t-99","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\t+9_6\t:+65534","identity":[96,65534]}, +{"value":"+3 :+26\u3000","identity":[3,26]}, +{"value":"6_5534\t:\t-65534","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\u300065534\u3000:\u3000-01652675379","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\t+4096015006\t:\u300065534 ","identity":[4096015006,65534]}, +{"value":"\t-1941165337 :\t-03586206692 ","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"+88:\uff19\uff10\uff14\uff12\uff14\uff16\uff17\uff16\uff19\t","identity":[88,904246769]}, +{"value":" 65534\u3000: +\uff16\uff15\uff15\uff13\uff14\t","identity":[65534,65534]}, +{"value":"017 :\t65534\u3000","identity":[17,65534]}, +{"value":"\t+24\u3000:2882191863 ","identity":[24,2882191863]}, +{"value":" -56:\t050\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u3000+84 : +65534 ","identity":[84,65534]}, +{"value":"\t65534: +62\t","identity":[65534,62]}, +{"value":"\t9_5\u3000: 65534\u3000","identity":[95,65534]}, +{"value":"\uff13_\uff19\u3000: 1822914502\u3000","identity":[39,1822914502]}, +{"value":" 6_5534\u3000: 65534\t","identity":[65534,65534]}, +{"value":"\u3000-65534: 7\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u300088:\t\uff18\uff17\uff16_\uff13\uff13\uff10\uff14\uff19\uff19 ","identity":[88,876330499]}, +{"value":"-65534\t:+2_1\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u3000-088\t:\u3000078 ","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u3000048\t:\u30001319632021\t","identity":[48,1319632021]}, +{"value":"\t-31:\t16\u3000","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\t64: +65534\u3000","identity":[64,65534]}, +{"value":"01182912588:\t9","identity":[1182912588,9]}, +{"value":"\t+26_7838323\t:\t065_534","identity":[267838323,65534]}, +{"value":"\t65534\u3000:\t-03064840839\t","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\u3000+65_534\u3000:\t-6_5534","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":" -2396053624\t: -228038441\u3000","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":" 42\u3000:\t65534\u3000","identity":[42,65534]}, +{"value":"065534\t: +065534","identity":[65534,65534]}, +{"value":"-4012149028\t:-123874810_0\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\t1287511420\t:1\t","identity":[1287511420,1]}, +{"value":"\u3000-\uff19\uff12 :-669413798\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\t25447_90229:\u30008_4\u3000","identity":[2544790229,84]}, +{"value":" -65534\t:\t65534\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\t-01654717838 :\t3767854402 ","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":" 29 : 28 ","identity":[29,28]}, +{"value":" +2004873741 :\u3000-056","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\t+\uff14\uff11 :\u300074\u3000","identity":[41,74]}, +{"value":"2012142218: 3746796062 ","identity":[2012142218,3746796062]}, +{"value":"\t4143267344\u3000: +065534 ","identity":[4143267344,65534]}, +{"value":" +5_31508052:\t079\t","identity":[531508052,79]}, +{"value":"065534:\u3000+65534","identity":[65534,65534]}, +{"value":"\u3000+065534\t:\t-65534 ","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\u3000-4207239492 :+194143544\u3000","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"+58: -331_481346","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"+8_8: 022\t","identity":[88,22]}, +{"value":"2955026462\u3000:\t-21021507_74 ","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\u300065534\u3000: -366775534\u3000","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"-18 :+46","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\uff10\uff12\uff16\uff13\uff12\uff10\uff12\uff10\uff19\uff14\uff14\t: 3714053507\t","identity":[2632020944,3714053507]}, +{"value":"\u30001310933740:02717772865 ","identity":[1310933740,2717772865]}, +{"value":"\u3000+65534\t: -5_9\t","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"065534\t:\t+046","identity":[65534,46]}, +{"value":" +60\u3000: 65534\t","identity":[60,65534]}, +{"value":"\t1065765648: -65534\u3000","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"-65534\t: 1861159687 ","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u3000+69 :\u300065534","identity":[69,65534]}, +{"value":"+0735370997\t: +65534 ","identity":[735370997,65534]}, +{"value":"6_5534 :\t-6_4","error":"--workload-user GID must be between 1 and 4294967295"}, +{"value":"\t4 :\t+099","identity":[4,99]}, +{"value":"+\uff16_\uff15\uff15\uff13\uff14 :\t+19\t","identity":[65534,19]}, +{"value":"\u3000+21:\t+65534\t","identity":[21,65534]}, +{"value":"\t065_534:\t65534","identity":[65534,65534]}, +{"value":" -065534\t:\u3000+2272821736\u3000","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u3000+22\u3000:\t2232960263\u3000","identity":[22,2232960263]}, +{"value":" -4_8\t:065534","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"-065534\t:\u3000-2499768029\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\u300003640810574 :\u300065534\t","identity":[3640810574,65534]}, +{"value":" +82\u3000:\t\uff16\uff15\uff15\uff13_\uff14\u3000","identity":[82,65534]}, +{"value":"\u3000-40\u3000:\t+65534\t","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"\t-71\t:\t51 ","error":"--workload-user UID must be between 1 and 4294967295"}, +{"value":"38\t: 72\t","identity":[38,72]}, +{"value":" -21_4345071\u3000:\u3000+65534\t","error":"--workload-user UID must be between 1 and 4294967295"} +], +"layers": [ +{"flavor":"posix","value":"distro,distro.erofs,11111111-1111-1111-1111-111111111111","role":"distro","path":"distro.erofs","uuid":"11111111-1111-1111-1111-111111111111"}, +{"flavor":"posix","value":"custom,layers//custom.erofs,{22222222-2222-2222-2222-22222222222A}","role":"custom","path":"layers/custom.erofs","uuid":"22222222-2222-2222-2222-22222222222a"}, +{"flavor":"posix","value":"distro,C:/l/d.erofs,urn:uuid:11111111111111111111111111111111","role":"distro","path":"C:/l/d.erofs","uuid":"11111111-1111-1111-1111-111111111111"}, +{"flavor":"posix","value":"unknown,layer.erofs,11111111-1111-1111-1111-111111111111","error":"unsupported layer role 'unknown'; choose distro, runtime, custom"}, +{"flavor":"posix","value":"distro,layer.erofs,not-a-uuid","error":"distro layer UUID is invalid: 'not-a-uuid'"}, +{"flavor":"posix","value":"distro,,1","error":"distro layer path is empty"}, +{"flavor":"posix","value":"distro","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"distro,a","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"distro,a,b,c","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"runtime,a\\b/./c,11111111111111111111111111111111","role":"runtime","path":"a\\b/c","uuid":"11111111-1111-1111-1111-111111111111"}, +{"flavor":"posix","value":"distro,\u00e9\\\\\u00e9\u0000;,d47370eae5cbA75C863be3F8793fa3c475","error":"distro layer UUID is invalid: 'd47370eae5cbA75C863be3F8793fa3c475'"}, +{"flavor":"posix","value":"custom,l.erofs,urn:u9id:be784076-4505-74e5-8157-10ede7f6ae309","error":"custom layer UUID is invalid: 'urn:u9id:be784076-4505-74e5-8157-10ede7f6ae309'"}, +{"flavor":"posix","value":"distro,l.erofs,09c3919ad790d44963d356c2b8caff37","role":"distro","path":"l.erofs","uuid":"09c3919a-d790-d449-63d3-56c2b8caff37"}, +{"flavor":"posix","value":"runtime,l.erofs,059cad0b9662c2f15313cdd92c8a467b","role":"runtime","path":"l.erofs","uuid":"059cad0b-9662-c2f1-5313-cdd92c8a467b"}, +{"flavor":"posix","value":"custom,l.erofs,urn:uuid:ce910a31-87b3-d154-8786-e88b5197ca97","role":"custom","path":"l.erofs","uuid":"ce910a31-87b3-d154-8786-e88b5197ca97"}, +{"flavor":"posix","value":"runtime,l.erofs,{688670ec-11af-4ecc-227c-2b8fb80b46e5}","role":"runtime","path":"l.erofs","uuid":"688670ec-11af-4ecc-227c-2b8fb80b46e5"}, +{"flavor":"posix","value":"distro,l.erofs,ce3c4752bdde2f319f46da597f9f1dd","error":"distro layer UUID is invalid: 'ce3c4752bdde2f319f46da597f9f1dd'"}, +{"flavor":"posix","value":"distro,l.erofs,CDDC6C8C-280D-C429-581C-6EB1818605BD","role":"distro","path":"l.erofs","uuid":"cddc6c8c-280d-c429-581c-6eb1818605bd"}, +{"flavor":"posix","value":"distro,l.erofs,un:uuid:4d19dc82-d6a2f-3d39-390e-c575209d4642","error":"distro layer UUID is invalid: 'un:uuid:4d19dc82-d6a2f-3d39-390e-c575209d4642'"}, +{"flavor":"posix","value":"distro,\\.UNC ,A2C08FB6-B847-57F5-E521-BE7BB7B8CAC1","role":"distro","path":"\\.UNC ","uuid":"a2c08fb6-b847-57f5-e521-be7bb7b8cac1"}, +{"flavor":"posix","value":"runtime,l.erofs,{aca5b0e7-2b23-36aa-b0d1-c358ade55f90}","role":"runtime","path":"l.erofs","uuid":"aca5b0e7-2b23-36aa-b0d1-c358ade55f90"}, +{"flavor":"posix","value":"distro,l.erofs,{e31ccd09-a38d-6b01-a567-18d353583623}","role":"distro","path":"l.erofs","uuid":"e31ccd09-a38d-6b01-a567-18d353583623"}, +{"flavor":"posix","value":"custom,\\:,df3dbf88-8e54-b194-bc16-f6d0a60cc0b0","role":"custom","path":"\\:","uuid":"df3dbf88-8e54-b194-bc16-f6d0a60cc0b0"}, +{"flavor":"posix","value":"custom,l.erofs,2c98e43b-97d0-9883-4ca0-5268c800e130","role":"custom","path":"l.erofs","uuid":"2c98e43b-97d0-9883-4ca0-5268c800e130"}, +{"flavor":"posix","value":"runtime,l.erofs,771B1D8B-8AEE-67C9-8375-24C24FABCA5C","role":"runtime","path":"l.erofs","uuid":"771b1d8b-8aee-67c9-8375-24c24fabca5c"}, +{"flavor":"posix","value":"runtime,l.erofs,urn:uuid:2a36bc7f-8009-2884-8cac-6f04ec9098d77","error":"runtime layer UUID is invalid: 'urn:uuid:2a36bc7f-8009-2884-8cac-6f04ec9098d77'"}, +{"flavor":"posix","value":"custom,l.erofs,fe517c55-3205-b18d-a793-552d9476ccab","role":"custom","path":"l.erofs","uuid":"fe517c55-3205-b18d-a793-552d9476ccab"}, +{"flavor":"posix","value":"distro,l.erofs,urn:uuid:08adf337-b699-488b-db394ee894ce6bfd","role":"distro","path":"l.erofs","uuid":"08adf337-b699-488b-db39-4ee894ce6bfd"}, +{"flavor":"posix","value":"runtime,l.erofs,00862bd0-83cc-6f70-456c-9eb}05cc8f258","error":"runtime layer UUID is invalid: '00862bd0-83cc-6f70-456c-9eb}05cc8f258'"}, +{"flavor":"posix","value":"custom,l.erofs,6a021be8-23cc-a543-4b02-649acf462468","role":"custom","path":"l.erofs","uuid":"6a021be8-23cc-a543-4b02-649acf462468"}, +{"flavor":"posix","value":"runtime,l.erofs,31d4082d-965c-b9ad-72f9-279472d4ea6d","role":"runtime","path":"l.erofs","uuid":"31d4082d-965c-b9ad-72f9-279472d4ea6d"}, +{"flavor":"posix","value":"distro,l.erofs,b400187b5f62a2f5790b46db686356656","error":"distro layer UUID is invalid: 'b400187b5f62a2f5790b46db686356656'"}, +{"flavor":"posix","value":"runtime, \u0000,c511e4b9-b92a-2a47-9616-d0549f9ab320","role":"runtime","path":" \u0000","uuid":"c511e4b9-b92a-2a47-9616-d0549f9ab320"}, +{"flavor":"posix","value":",l.erofs,urn:uuid:08ea33c3-151 5-6fad-9054-dd4193856a4","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"posix","value":"runtime,ac,{f3ec549b-58ee-1773-8fe5-dae60ceef11e}","role":"runtime","path":"ac","uuid":"f3ec549b-58ee-1773-8fe5-dae60ceef11e"}, +{"flavor":"posix","value":"distro,l.erofs,{a24de61c-c46-beb7-37239-ce87b43f0ebd}","role":"distro","path":"l.erofs","uuid":"a24de61c-c46b-eb73-7239-ce87b43f0ebd"}, +{"flavor":"posix","value":"runtime,l.erofs,DC5891E0-CE44-CD84-500B-0750EC9F4A92","role":"runtime","path":"l.erofs","uuid":"dc5891e0-ce44-cd84-500b-0750ec9f4a92"}, +{"flavor":"posix","value":"runtime,unc\\\\../,548f4264ae4ee06aad6c0006a3b9e092","role":"runtime","path":"unc\\\\..","uuid":"548f4264-ae4e-e06a-ad6c-0006a3b9e092"}, +{"flavor":"posix","value":"custom,l.erofs,{4c9badba-2f47-59db-12b0-e199df1a9608}","role":"custom","path":"l.erofs","uuid":"4c9badba-2f47-59db-12b0-e199df1a9608"}, +{"flavor":"posix","value":"distro,l.erofs,urn:uuid:b36fe728-b5af-5551-b421-018b3a4de53a","role":"distro","path":"l.erofs","uuid":"b36fe728-b5af-5551-b421-018b3a4de53a"}, +{"flavor":"posix","value":"runtime,l.erofs,{a110fcca-a69e-5161-53c2-bc0ca0e94e09}","role":"runtime","path":"l.erofs","uuid":"a110fcca-a69e-5161-53c2-bc0ca0e94e09"}, +{"flavor":"posix","value":"runtime,l.erofs,4fc70d2cc{4dbff4abb5658f7b4722deb","error":"runtime layer UUID is invalid: '4fc70d2cc{4dbff4abb5658f7b4722deb'"}, +{"flavor":"posix","value":"runtime,l.erofs,D2E71CE1-30E5-BF2A-8903-75506A9F274F","role":"runtime","path":"l.erofs","uuid":"d2e71ce1-30e5-bf2a-8903-75506a9f274f"}, +{"flavor":"posix","value":"runtime,l.erofs,urn:uuid:83439e1b-2664-4a3f-276e-0f9e75587d3b","role":"runtime","path":"l.erofs","uuid":"83439e1b-2664-4a3f-276e-0f9e75587d3b"}, +{"flavor":"posix","value":"distro,l.erofs,d5be81ce-6e0e-53b9-f5d0-6a9b9bceb265","role":"distro","path":"l.erofs","uuid":"d5be81ce-6e0e-53b9-f5d0-6a9b9bceb265"}, +{"flavor":"posix","value":"distro,l.erofs,95cc2409-bbca-2a82-de91-7632852d212c","role":"distro","path":"l.erofs","uuid":"95cc2409-bbca-2a82-de91-7632852d212c"}, +{"flavor":"posix","value":"runtime,l.erofs,92A33C76-9BC5-5182bBA0B-7ED7406EC40","role":"runtime","path":"l.erofs","uuid":"92a33c76-9bc5-5182-bba0-b7ed7406ec40"}, +{"flavor":"posix","value":"custom,l.erofs,{2a882789-795e-5c58-9680-67e62f3e13f6}","role":"custom","path":"l.erofs","uuid":"2a882789-795e-5c58-9680-67e62f3e13f6"}, +{"flavor":"posix","value":"distro,l.erofs,ccafea27-8d70-dabf-4951-3e2dadf7c698","role":"distro","path":"l.erofs","uuid":"ccafea27-8d70-dabf-4951-3e2dadf7c698"}, +{"flavor":"posix","value":"distro,l.erofs,532C3ADE-D6A5-E14-CF21-14B9798B420A","error":"distro layer UUID is invalid: '532C3ADE-D6A5-E14-CF21-14B9798B420A'"}, +{"flavor":"posix","value":"custom,l.erofs,6C9CBB32-DE23-D3F5-D660-D3AB216169C1","role":"custom","path":"l.erofs","uuid":"6c9cbb32-de23-d3f5-d660-d3ab216169c1"}, +{"flavor":"posix","value":"custom,l.erofs,0960b882-3da7-6b8e-3a06-e525149dc739","role":"custom","path":"l.erofs","uuid":"0960b882-3da7-6b8e-3a06-e525149dc739"}, +{"flavor":"posix","value":"runtime,l.erofs,37b3c3f6-5d11-b3f7-9326-7a57ac15d0fd","role":"runtime","path":"l.erofs","uuid":"37b3c3f6-5d11-b3f7-9326-7a57ac15d0fd"}, +{"flavor":"posix","value":"custom,;: x,y,{3a551b21-d4e9-146e-7603-67059ab821fe}","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":",l.erofs,urn:uuid:cda20c9b-83c7-98da-d1b2-003c3aea0b7e","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"posix","value":"distro,l.erofs,{aecbe287-14eb-56a4-af8a-2e3745acd76d}","role":"distro","path":"l.erofs","uuid":"aecbe287-14eb-56a4-af8a-2e3745acd76d"}, +{"flavor":"posix","value":"custom,l.erofs,88bf5b5cb00a47a137d2fa6aafd047cf","role":"custom","path":"l.erofs","uuid":"88bf5b5c-b00a-47a1-37d2-fa6aafd047cf"}, +{"flavor":"posix","value":"runtime,l.erofs,urn:uuid:dc3d63b-190Cd-dca1-7683-2ea1b6e5364c","role":"runtime","path":"l.erofs","uuid":"dc3d63b1-90cd-dca1-7683-2ea1b6e5364c"}, +{"flavor":"posix","value":"runtime,\u00e9\\\\\\unc:,a02af7acae132fbaccbddfc4f090167dd8","error":"runtime layer UUID is invalid: 'a02af7acae132fbaccbddfc4f090167dd8'"}, +{"flavor":"posix","value":"distro,l.erofs,{6515471f-2973-ae1e-8d7f-fb7b51df5998}","role":"distro","path":"l.erofs","uuid":"6515471f-2973-ae1e-8d7f-fb7b51df5998"}, +{"flavor":"posix","value":"distro,;\u00e9\u00e9\\\\UNC,b55261e86c80c039671e2b6884b446f3","role":"distro","path":";\u00e9\u00e9\\\\UNC","uuid":"b55261e8-6c80-c039-671e-2b6884b446f3"}, +{"flavor":"posix","value":"other,l.erofs,{9a0d92b2-47da-59a0-f9c3-78e5d4078715}","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"posix","value":"custom,\u0000,292dd182-f2ee-70a7-aa01-45edda6d9076","role":"custom","path":"\u0000","uuid":"292dd182-f2ee-70a7-aa01-45edda6d9076"}, +{"flavor":"posix","value":"custom,l.erofs,d34dc48b7b0e451b774c5412c547a1f3","role":"custom","path":"l.erofs","uuid":"d34dc48b-7b0e-451b-774c-5412c547a1f3"}, +{"flavor":"posix","value":"distro,l.erofs,{2c354ec2-09cb-3185-72b4-5c3c57b2288a}","role":"distro","path":"l.erofs","uuid":"2c354ec2-09cb-3185-72b4-5c3c57b2288a"}, +{"flavor":"posix","value":"distro,l.erofs,{0fc1d4b1-c50a-5882-4869-495975691dad}","role":"distro","path":"l.erofs","uuid":"0fc1d4b1-c50a-5882-4869-495975691dad"}, +{"flavor":"posix","value":"distro,l.erofs,{2c6d75df-348c-7ede-46f5-f9680a4475d0}","role":"distro","path":"l.erofs","uuid":"2c6d75df-348c-7ede-46f5-f9680a4475d0"}, +{"flavor":"posix","value":"custom,l.erofs,urn:uuid:5b3d3301-7ae3-39bd-0593-11c1ffc3e1e8","role":"custom","path":"l.erofs","uuid":"5b3d3301-7ae3-39bd-0593-11c1ffc3e1e8"}, +{"flavor":"posix","value":"runtime,x,y,222e5703-5db7-9b5d-fa80-0cfdfea56a27","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"runtime,;,{24eb406e-8f63-1ab2-d6d5-55607c0bc6b3}","role":"runtime","path":";","uuid":"24eb406e-8f63-1ab2-d6d5-55607c0bc6b3"}, +{"flavor":"posix","value":"custom,b,ee04d4f8-683a-571a-bf6f-b4a044dc79b3","role":"custom","path":"b","uuid":"ee04d4f8-683a-571a-bf6f-b4a044dc79b3"}, +{"flavor":"posix","value":"custom,l.erofs,1b1b0886-7c07-7f9e-7570-a6631c0154ab","role":"custom","path":"l.erofs","uuid":"1b1b0886-7c07-7f9e-7570-a6631c0154ab"}, +{"flavor":"posix","value":"runtime,l.erofs,{e1a7ad9b-ac25-d8c2-f9b3-45d7fd8b23b7}","role":"runtime","path":"l.erofs","uuid":"e1a7ad9b-ac25-d8c2-f9b3-45d7fd8b23b7"}, +{"flavor":"posix","value":"Distro,c\u00e9?,{bcdadb4-d20-dd18-5e4-b59dd2055185}","error":"unsupported layer role 'Distro'; choose distro, runtime, custom"}, +{"flavor":"posix","value":"runtime,l.erofs,37bf70eb-eb85-8145-6327-592a25c4f115","role":"runtime","path":"l.erofs","uuid":"37bf70eb-eb85-8145-6327-592a25c4f115"}, +{"flavor":"posix","value":"custom,l.erofs,32378E3C-73C8-EF04-1DA6-DF849450B1A9","role":"custom","path":"l.erofs","uuid":"32378e3c-73c8-ef04-1da6-df849450b1a9"}, +{"flavor":"posix","value":"distro,l.erofs,urn:uuid:0b03c9dd-401f-7bfb-e3e3-b332640a35d5","role":"distro","path":"l.erofs","uuid":"0b03c9dd-401f-7bfb-e3e3-b332640a35d5"}, +{"flavor":"posix","value":"runtime,l.erofs,6230FAF7-410A-2FF7-9CD9-D7362210D4B6","role":"runtime","path":"l.erofs","uuid":"6230faf7-410a-2ff7-9cd9-d7362210d4b6"}, +{"flavor":"posix","value":"runtime,/\u0000?,0ca2a217497f43dd10aa3a290eb115f","error":"runtime layer UUID is invalid: '0ca2a217497f43dd10aa3a290eb115f'"}, +{"flavor":"posix","value":"distro,l.erofs,b316badf-2319-c480-c888-02f1bd605338","role":"distro","path":"l.erofs","uuid":"b316badf-2319-c480-c888-02f1bd605338"}, +{"flavor":"posix","value":"distro,.C/ ,25ca1a68-1ab9-902a-e7b6-4273181c79cb","role":"distro","path":".C/ ","uuid":"25ca1a68-1ab9-902a-e7b6-4273181c79cb"}, +{"flavor":"posix","value":"distro,l.erofs,3688118-C599-1453-3290-C4A7C34B7CD","error":"distro layer UUID is invalid: '3688118-C599-1453-3290-C4A7C34B7CD'"}, +{"flavor":"posix","value":"distro,uncx,yb\u00e9:,{1ffead7c-9a77-85b1-7a68-33c85b062c65}","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"other,l.erofs,urn:uuid:bf546440-63c6-cbc5-7ff2-5e82b9fbfa19","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"posix","value":"distro,l.erofs,dabedd032c6f656a12c6f617fc6e3007","role":"distro","path":"l.erofs","uuid":"dabedd03-2c6f-656a-12c6-f617fc6e3007"}, +{"flavor":"posix","value":",l.erofs,urn:uuid:499f619b-9bc1-b3c5-bb3c-eda7afb2700b","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"posix","value":"distro,UNCx,yCuncc,AB614B36-E4EA-9EB3-C0D1-A65F1C50518D","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"custom,l.erofs,6e0eb82a1495c6a6c4c7881e9f692a65","role":"custom","path":"l.erofs","uuid":"6e0eb82a-1495-c6a6-c4c7-881e9f692a65"}, +{"flavor":"posix","value":"distro,.b,05478E4-95B9-8391-B322-65B77F1F4A513","role":"distro","path":".b","uuid":"05478e49-5b98-391b-3226-5b77f1f4a513"}, +{"flavor":"posix","value":"other,l.erofs,6EFB9A12-7711-5D46-4A71-2AAF3151BB08","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"posix","value":"custom,l.erofs,urn:uuid:672efe58-44dc-82e3-070e-01970a44155e","role":"custom","path":"l.erofs","uuid":"672efe58-44dc-82e3-070e-01970a44155e"}, +{"flavor":"posix","value":"distro,l.erofs,b1399c0c-cc0e-ed26-d7a8-59f0f85ba50e","role":"distro","path":"l.erofs","uuid":"b1399c0c-cc0e-ed26-d7a8-59f0f85ba50e"}, +{"flavor":"posix","value":"custom,;,2b8c983b48fe117a9c752b6ae4beba0","error":"custom layer UUID is invalid: '2b8c983b48fe117a9c752b6ae4beba0'"}, +{"flavor":"posix","value":"runtime,l.erofs,ad196266f44e84b43ca4cd55774d7149","role":"runtime","path":"l.erofs","uuid":"ad196266-f44e-84b4-3ca4-cd55774d7149"}, +{"flavor":"posix","value":"distro,l.erofs,EAD3B99C-9987-7DE9-A7EF-82F85E07AF86","role":"distro","path":"l.erofs","uuid":"ead3b99c-9987-7de9-a7ef-82f85e07af86"}, +{"flavor":"posix","value":"runtime,\\\\c\\\u00e9,urn:uuid:4092f28d-dc50-8b06-9ecc-a48ed0ad6507","role":"runtime","path":"\\\\c\\\u00e9","uuid":"4092f28d-dc50-8b06-9ecc-a48ed0ad6507"}, +{"flavor":"posix","value":"custom,\u0000unc\\\\;..,825f732fd850a36d6c4c597220227130","role":"custom","path":"\u0000unc\\\\;..","uuid":"825f732f-d850-a36d-6c4c-597220227130"}, +{"flavor":"posix","value":"custom,l.erofs,F6731AAF-D316-01D8-4BF1-68CC72AF9909","role":"custom","path":"l.erofs","uuid":"f6731aaf-d316-01d8-4bf1-68cc72af9909"}, +{"flavor":"posix","value":"distro,l.erofs,77CA0BFCE-0314-EC44E-3C4C-E3BAAD63E9A","error":"distro layer UUID is invalid: '77CA0BFCE-0314-EC44E-3C4C-E3BAAD63E9A'"}, +{"flavor":"posix","value":"custom,l.erofs,{c68d0969-de02-5426-06d3-0be6e6d1dabf}","role":"custom","path":"l.erofs","uuid":"c68d0969-de02-5426-06d3-0be6e6d1dabf"}, +{"flavor":"posix","value":"distro,l.erofs,ab32e5242d8e30fE35381179bf9ceD2d","role":"distro","path":"l.erofs","uuid":"ab32e524-2d8e-30fe-3538-1179bf9ced2d"}, +{"flavor":"posix","value":"runtime,l.erofs,{2af58845-a26d-8c0f-b4bb-e39fccfc3b4c}","role":"runtime","path":"l.erofs","uuid":"2af58845-a26d-8c0f-b4bb-e39fccfc3b4c"}, +{"flavor":"posix","value":"distro,l.erofs,bfb97fd5-3a84-a7e8-b479-5a0ddf6db5cc","role":"distro","path":"l.erofs","uuid":"bfb97fd5-3a84-a7e8-b479-5a0ddf6db5cc"}, +{"flavor":"posix","value":"runtime,l.erofs,6d049}f8d-829d-c9a6-73b7-09bc366dc5b42","error":"runtime layer UUID is invalid: '6d049}f8d-829d-c9a6-73b7-09bc366dc5b42'"}, +{"flavor":"posix","value":"custom,l.erofs,4F4CF90B-B81F-A867-93B5-8E73C239F190","role":"custom","path":"l.erofs","uuid":"4f4cf90b-b81f-a867-93b5-8e73c239f190"}, +{"flavor":"posix","value":"distro,l.erofs,{522bca4d-8403-77d2-0839-e91d7ad9e24c}","role":"distro","path":"l.erofs","uuid":"522bca4d-8403-77d2-0839-e91d7ad9e24c"}, +{"flavor":"posix","value":"distro,l.erofs,urn:uuid:0c40a365-6158-b360-fca2-35d13fe588bd","role":"distro","path":"l.erofs","uuid":"0c40a365-6158-b360-fca2-35d13fe588bd"}, +{"flavor":"posix","value":"runtime,.. ?\\\\,c57eaeca-2945-0e90-b1d3-b2a40d0f30bd","role":"runtime","path":".. ?\\\\","uuid":"c57eaeca-2945-0e90-b1d3-b2a40d0f30bd"}, +{"flavor":"posix","value":"distro,l.erofs,e7daea43-ef9c-113b-0418-37eba9114810","role":"distro","path":"l.erofs","uuid":"e7daea43-ef9c-113b-0418-37eba9114810"}, +{"flavor":"posix","value":"custom,unc,{32427f34-de49-89d4-8ded-5dcddb5345c2}","role":"custom","path":"unc","uuid":"32427f34-de49-89d4-8ded-5dcddb5345c2"}, +{"flavor":"posix","value":"custom,:,4c2bbdf8-22c8-7cb4-bd0a-3035f04eec23","role":"custom","path":":","uuid":"4c2bbdf8-22c8-7cb4-bd0a-3035f04eec23"}, +{"flavor":"posix","value":"runtime,l.erofs,78143d74-3b93-2adb-dfd4-507da43913bc","role":"runtime","path":"l.erofs","uuid":"78143d74-3b93-2adb-dfd4-507da43913bc"}, +{"flavor":"posix","value":"custom,x,yCa:\\\\,30f2a9c6-854c-7403-c4e2-1856023f0be6","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"runtime,..C ,f1680b112-ad0c-ef25-53dd-3dc0701f1a3","role":"runtime","path":"..C ","uuid":"f1680b11-2ad0-cef2-553d-d3dc0701f1a3"}, +{"flavor":"posix","value":"distro,l.erofs,{63a4ce9d-86d5-2050-c34e-2b6ee0a06565}","role":"distro","path":"l.erofs","uuid":"63a4ce9d-86d5-2050-c34e-2b6ee0a06565"}, +{"flavor":"posix","value":",.bC;b,EBD3B66D-5AC5-33C5-90EB-7329A3D01D3F","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"posix","value":"runtime,l.erofs,bd0fee6d-efa4-9a7d-1d10-b7e2cc5682b0","role":"runtime","path":"l.erofs","uuid":"bd0fee6d-efa4-9a7d-1d10-b7e2cc5682b0"}, +{"flavor":"posix","value":"custom,l.erofs,5ecf5c78-cb7c-dfb4-334d-234d4721b4c9","role":"custom","path":"l.erofs","uuid":"5ecf5c78-cb7c-dfb4-334d-234d4721b4c9"}, +{"flavor":"posix","value":"custom,l.erofs,8b0d15bcaf5d90669c821b1112b7e431","role":"custom","path":"l.erofs","uuid":"8b0d15bc-af5d-9066-9c82-1b1112b7e431"}, +{"flavor":"posix","value":"custom,l.erofs,d17d90e3-0bb6-226b-7a3b-1ab6a8533fe6","role":"custom","path":"l.erofs","uuid":"d17d90e3-0bb6-226b-7a3b-1ab6a8533fe6"}, +{"flavor":"posix","value":"distro,l.erofs,2F077696-A4D8-2E70-86E7-EB6B0C85650F","role":"distro","path":"l.erofs","uuid":"2f077696-a4d8-2e70-86e7-eb6b0c85650f"}, +{"flavor":"posix","value":"custom,l.erofs,f3728c33-b0ea-7764-d116-71642b05765d","role":"custom","path":"l.erofs","uuid":"f3728c33-b0ea-7764-d116-71642b05765d"}, +{"flavor":"posix","value":",l.erofs,76e7ca06b12f0b32fecde12e68fd412f","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"posix","value":"other,l.erofs,{ee87b998-5e87-5c51-c238-028bc639a714}","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"posix","value":"distro,./b,8bc4605cf9e5e49eaadb9e18ee1782b5","role":"distro","path":"b","uuid":"8bc4605c-f9e5-e49e-aadb-9e18ee1782b5"}, +{"flavor":"posix","value":"distro,l.erofs,urn:uuid:79f3ddae-9c07-800f-45cf-54097d035ef8","role":"distro","path":"l.erofs","uuid":"79f3ddae-9c07-800f-45cf-54097d035ef8"}, +{"flavor":"posix","value":"custom,C,47823a6dff65489fcd5be72972fee2cb","role":"custom","path":"C","uuid":"47823a6d-ff65-489f-cd5b-e72972fee2cb"}, +{"flavor":"posix","value":"custom,l.erofs,urn:uuid:854c5327-f769-381a-4a98-d966a4ba29e6","role":"custom","path":"l.erofs","uuid":"854c5327-f769-381a-4a98-d966a4ba29e6"}, +{"flavor":"posix","value":"runtime,b.//:\\\\,476740ac-bafe-c2f2-3ebf-e49cef11b409","role":"runtime","path":"b./:\\\\","uuid":"476740ac-bafe-c2f2-3ebf-e49cef11b409"}, +{"flavor":"posix","value":"custom,l.erofs,B277A3B5-87A2-51A4-8EB7-A2691C95D135","role":"custom","path":"l.erofs","uuid":"b277a3b5-87a2-51a4-8eb7-a2691c95d135"}, +{"flavor":"posix","value":"custom,x,y?ba,869ebbba-6f4c-95c4-1860-dd8f486b24f4","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"other,\\/;//a,37D0039E-DC2B-80B2-EFA1-333BA95B39FC","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"posix","value":"runtime,:;x,y,1df499fbac7ecdb056a5a31922e139b0","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"posix","value":"custom,l.erofs,{85ba55b0-696e-26c9-fd7e-9f7eead7fe21}","role":"custom","path":"l.erofs","uuid":"85ba55b0-696e-26c9-fd7e-9f7eead7fe21"}, +{"flavor":"posix","value":"distro,l.erofs,{91d6acca-835b-23a6-c0b4-4983a1b3b045}","role":"distro","path":"l.erofs","uuid":"91d6acca-835b-23a6-c0b4-4983a1b3b045"}, +{"flavor":"posix","value":"distro,a.UNC\\//,urn:uuid:4e03e884-6e1a-872e-1414-2e8c94f02eb0","role":"distro","path":"a.UNC\\","uuid":"4e03e884-6e1a-872e-1414-2e8c94f02eb0"}, +{"flavor":"posix","value":"custom,l.erofs,urn:uuid:02e32568-fcca-f123-5353-e9b83cef4368","role":"custom","path":"l.erofs","uuid":"02e32568-fcca-f123-5353-e9b83cef4368"}, +{"flavor":"posix","value":",l.erofs,77643A3D-73B4-7A44-4995-17CBD01064761","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"posix","value":"runtime,\u00e9\\\\,42e8eb08-f5cd-09c0-b983-a385a69e51db","role":"runtime","path":"\u00e9\\\\","uuid":"42e8eb08-f5cd-09c0-b983-a385a69e51db"}, +{"flavor":"posix","value":"distro,l.erofs,b563db2fe17d97300341346b7fb8a733","role":"distro","path":"l.erofs","uuid":"b563db2f-e17d-9730-0341-346b7fb8a733"}, +{"flavor":"posix","value":"custom,l.erofs,urn:uuid:5ebe3631-3b0a-9e22-7361-c4418f9be13c","role":"custom","path":"l.erofs","uuid":"5ebe3631-3b0a-9e22-7361-c4418f9be13c"}, +{"flavor":"posix","value":"custom,l.erofs,{2da3af6-33d4-386c-fea5-0146f2803a6f}","error":"custom layer UUID is invalid: '{2da3af6-33d4-386c-fea5-0146f2803a6f}'"}, +{"flavor":"posix","value":"custom,l.erofs,{96064721-c861-cf7c-286d-34728bb8ca13}","role":"custom","path":"l.erofs","uuid":"96064721-c861-cf7c-286d-34728bb8ca13"}, +{"flavor":"posix","value":"runtime,l.erofs,65b24dea-7418-c086-3c04-3cd799cff062","role":"runtime","path":"l.erofs","uuid":"65b24dea-7418-c086-3c04-3cd799cff062"}, +{"flavor":"posix","value":"distro,\u00e9 ,37a1897c6df95069935c4ead59c96a33","role":"distro","path":"\u00e9 ","uuid":"37a1897c-6df9-5069-935c-4ead59c96a33"}, +{"flavor":"posix","value":"custom,l.erofs,2CE7F754-351A-DB60258f0-E8E032441C57","error":"custom layer UUID is invalid: '2CE7F754-351A-DB60258f0-E8E032441C57'"}, +{"flavor":"posix","value":"runtime,l.erofs,urn:uuid:6ac1a990-d7a6-5989-e871-c7412b190192","role":"runtime","path":"l.erofs","uuid":"6ac1a990-d7a6-5989-e871-c7412b190192"}, +{"flavor":"posix","value":"custom,l.erofs,{5f40518e-0d79-60fd-55c2-da069ddc0ab0}","role":"custom","path":"l.erofs","uuid":"5f40518e-0d79-60fd-55c2-da069ddc0ab0"}, +{"flavor":"posix","value":"custom,l.erofs,d055ce0d-8e03-e2ff-f256-ee19a8413fe8","role":"custom","path":"l.erofs","uuid":"d055ce0d-8e03-e2ff-f256-ee19a8413fe8"}, +{"flavor":"posix","value":"runtime,l.erofs,ae127445-baf6-03b0-d399-e6a9f509e85c","role":"runtime","path":"l.erofs","uuid":"ae127445-baf6-03b0-d399-e6a9f509e85c"}, +{"flavor":"posix","value":"custom,l.erofs,{fb9ed6a3-cd7e-94f2-cdfb-fb579e78e411}","role":"custom","path":"l.erofs","uuid":"fb9ed6a3-cd7e-94f2-cdfb-fb579e78e411"}, +{"flavor":"posix","value":"runtime,l.erofs,6a7691af-Dafb9-3750c-bff2-baf5274a52c","error":"runtime layer UUID is invalid: '6a7691af-Dafb9-3750c-bff2-baf5274a52c'"}, +{"flavor":"posix","value":"distro,l.erofs,urn:uuid:a0e9b18a-1bd9-32f6-d5d6-ec04d2fc6239","role":"distro","path":"l.erofs","uuid":"a0e9b18a-1bd9-32f6-d5d6-ec04d2fc6239"}, +{"flavor":"posix","value":"distro,UNCUNC;..,caf75663-6c92-8e7a-ecc0-34269711b7a5","role":"distro","path":"UNCUNC;..","uuid":"caf75663-6c92-8e7a-ecc0-34269711b7a5"}, +{"flavor":"posix","value":",\\\\\\\\.a\\,9AF458AB-F246D-B12B-8E8C-3C1F316A616","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"posix","value":"distro,\\,50e889b-aec2-298acb99-e98235296bab","error":"distro layer UUID is invalid: '50e889b-aec2-298acb99-e98235296bab'"}, +{"flavor":"posix","value":"distro,l.erofs,a462541d-e859-9321-fb91-8728b6556688","role":"distro","path":"l.erofs","uuid":"a462541d-e859-9321-fb91-8728b6556688"}, +{"flavor":"posix","value":"distro,l.erofs,0f0c1ada-0f7f-793a-94b7-11c02b5dac00","role":"distro","path":"l.erofs","uuid":"0f0c1ada-0f7f-793a-94b7-11c02b5dac00"}, +{"flavor":"posix","value":"runtime,l.erofs,5F35AA7D-2A62-4B24-B9F4-ED94935B156F","role":"runtime","path":"l.erofs","uuid":"5f35aa7d-2a62-4b24-b9f4-ed94935b156f"}, +{"flavor":"posix","value":"custom,l.erofs,e3de5cd9dfe45cafe1759b71cb1a3bfa","role":"custom","path":"l.erofs","uuid":"e3de5cd9-dfe4-5caf-e175-9b71cb1a3bfa"}, +{"flavor":"windows","value":"distro,distro.erofs,11111111-1111-1111-1111-111111111111","role":"distro","path":"distro.erofs","uuid":"11111111-1111-1111-1111-111111111111"}, +{"flavor":"windows","value":"custom,layers//custom.erofs,{22222222-2222-2222-2222-22222222222A}","role":"custom","path":"layers\\custom.erofs","uuid":"22222222-2222-2222-2222-22222222222a"}, +{"flavor":"windows","value":"distro,C:/l/d.erofs,urn:uuid:11111111111111111111111111111111","role":"distro","path":"C:\\l\\d.erofs","uuid":"11111111-1111-1111-1111-111111111111"}, +{"flavor":"windows","value":"unknown,layer.erofs,11111111-1111-1111-1111-111111111111","error":"unsupported layer role 'unknown'; choose distro, runtime, custom"}, +{"flavor":"windows","value":"distro,layer.erofs,not-a-uuid","error":"distro layer UUID is invalid: 'not-a-uuid'"}, +{"flavor":"windows","value":"distro,,1","error":"distro layer path is empty"}, +{"flavor":"windows","value":"distro","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"distro,a","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"distro,a,b,c","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"runtime,a\\b/./c,11111111111111111111111111111111","role":"runtime","path":"a\\b\\c","uuid":"11111111-1111-1111-1111-111111111111"}, +{"flavor":"windows","value":"distro,\u00e9\\\\\u00e9\u0000;,d47370eae5cbA75C863be3F8793fa3c475","error":"distro layer UUID is invalid: 'd47370eae5cbA75C863be3F8793fa3c475'"}, +{"flavor":"windows","value":"custom,l.erofs,urn:u9id:be784076-4505-74e5-8157-10ede7f6ae309","error":"custom layer UUID is invalid: 'urn:u9id:be784076-4505-74e5-8157-10ede7f6ae309'"}, +{"flavor":"windows","value":"distro,l.erofs,09c3919ad790d44963d356c2b8caff37","role":"distro","path":"l.erofs","uuid":"09c3919a-d790-d449-63d3-56c2b8caff37"}, +{"flavor":"windows","value":"runtime,l.erofs,059cad0b9662c2f15313cdd92c8a467b","role":"runtime","path":"l.erofs","uuid":"059cad0b-9662-c2f1-5313-cdd92c8a467b"}, +{"flavor":"windows","value":"custom,l.erofs,urn:uuid:ce910a31-87b3-d154-8786-e88b5197ca97","role":"custom","path":"l.erofs","uuid":"ce910a31-87b3-d154-8786-e88b5197ca97"}, +{"flavor":"windows","value":"runtime,l.erofs,{688670ec-11af-4ecc-227c-2b8fb80b46e5}","role":"runtime","path":"l.erofs","uuid":"688670ec-11af-4ecc-227c-2b8fb80b46e5"}, +{"flavor":"windows","value":"distro,l.erofs,ce3c4752bdde2f319f46da597f9f1dd","error":"distro layer UUID is invalid: 'ce3c4752bdde2f319f46da597f9f1dd'"}, +{"flavor":"windows","value":"distro,l.erofs,CDDC6C8C-280D-C429-581C-6EB1818605BD","role":"distro","path":"l.erofs","uuid":"cddc6c8c-280d-c429-581c-6eb1818605bd"}, +{"flavor":"windows","value":"distro,l.erofs,un:uuid:4d19dc82-d6a2f-3d39-390e-c575209d4642","error":"distro layer UUID is invalid: 'un:uuid:4d19dc82-d6a2f-3d39-390e-c575209d4642'"}, +{"flavor":"windows","value":"distro,\\.UNC ,A2C08FB6-B847-57F5-E521-BE7BB7B8CAC1","role":"distro","path":"\\.UNC ","uuid":"a2c08fb6-b847-57f5-e521-be7bb7b8cac1"}, +{"flavor":"windows","value":"runtime,l.erofs,{aca5b0e7-2b23-36aa-b0d1-c358ade55f90}","role":"runtime","path":"l.erofs","uuid":"aca5b0e7-2b23-36aa-b0d1-c358ade55f90"}, +{"flavor":"windows","value":"distro,l.erofs,{e31ccd09-a38d-6b01-a567-18d353583623}","role":"distro","path":"l.erofs","uuid":"e31ccd09-a38d-6b01-a567-18d353583623"}, +{"flavor":"windows","value":"custom,\\:,df3dbf88-8e54-b194-bc16-f6d0a60cc0b0","role":"custom","path":"\\:","uuid":"df3dbf88-8e54-b194-bc16-f6d0a60cc0b0"}, +{"flavor":"windows","value":"custom,l.erofs,2c98e43b-97d0-9883-4ca0-5268c800e130","role":"custom","path":"l.erofs","uuid":"2c98e43b-97d0-9883-4ca0-5268c800e130"}, +{"flavor":"windows","value":"runtime,l.erofs,771B1D8B-8AEE-67C9-8375-24C24FABCA5C","role":"runtime","path":"l.erofs","uuid":"771b1d8b-8aee-67c9-8375-24c24fabca5c"}, +{"flavor":"windows","value":"runtime,l.erofs,urn:uuid:2a36bc7f-8009-2884-8cac-6f04ec9098d77","error":"runtime layer UUID is invalid: 'urn:uuid:2a36bc7f-8009-2884-8cac-6f04ec9098d77'"}, +{"flavor":"windows","value":"custom,l.erofs,fe517c55-3205-b18d-a793-552d9476ccab","role":"custom","path":"l.erofs","uuid":"fe517c55-3205-b18d-a793-552d9476ccab"}, +{"flavor":"windows","value":"distro,l.erofs,urn:uuid:08adf337-b699-488b-db394ee894ce6bfd","role":"distro","path":"l.erofs","uuid":"08adf337-b699-488b-db39-4ee894ce6bfd"}, +{"flavor":"windows","value":"runtime,l.erofs,00862bd0-83cc-6f70-456c-9eb}05cc8f258","error":"runtime layer UUID is invalid: '00862bd0-83cc-6f70-456c-9eb}05cc8f258'"}, +{"flavor":"windows","value":"custom,l.erofs,6a021be8-23cc-a543-4b02-649acf462468","role":"custom","path":"l.erofs","uuid":"6a021be8-23cc-a543-4b02-649acf462468"}, +{"flavor":"windows","value":"runtime,l.erofs,31d4082d-965c-b9ad-72f9-279472d4ea6d","role":"runtime","path":"l.erofs","uuid":"31d4082d-965c-b9ad-72f9-279472d4ea6d"}, +{"flavor":"windows","value":"distro,l.erofs,b400187b5f62a2f5790b46db686356656","error":"distro layer UUID is invalid: 'b400187b5f62a2f5790b46db686356656'"}, +{"flavor":"windows","value":"runtime, \u0000,c511e4b9-b92a-2a47-9616-d0549f9ab320","role":"runtime","path":" \u0000","uuid":"c511e4b9-b92a-2a47-9616-d0549f9ab320"}, +{"flavor":"windows","value":",l.erofs,urn:uuid:08ea33c3-151 5-6fad-9054-dd4193856a4","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"windows","value":"runtime,ac,{f3ec549b-58ee-1773-8fe5-dae60ceef11e}","role":"runtime","path":"ac","uuid":"f3ec549b-58ee-1773-8fe5-dae60ceef11e"}, +{"flavor":"windows","value":"distro,l.erofs,{a24de61c-c46-beb7-37239-ce87b43f0ebd}","role":"distro","path":"l.erofs","uuid":"a24de61c-c46b-eb73-7239-ce87b43f0ebd"}, +{"flavor":"windows","value":"runtime,l.erofs,DC5891E0-CE44-CD84-500B-0750EC9F4A92","role":"runtime","path":"l.erofs","uuid":"dc5891e0-ce44-cd84-500b-0750ec9f4a92"}, +{"flavor":"windows","value":"runtime,unc\\\\../,548f4264ae4ee06aad6c0006a3b9e092","role":"runtime","path":"unc\\..","uuid":"548f4264-ae4e-e06a-ad6c-0006a3b9e092"}, +{"flavor":"windows","value":"custom,l.erofs,{4c9badba-2f47-59db-12b0-e199df1a9608}","role":"custom","path":"l.erofs","uuid":"4c9badba-2f47-59db-12b0-e199df1a9608"}, +{"flavor":"windows","value":"distro,l.erofs,urn:uuid:b36fe728-b5af-5551-b421-018b3a4de53a","role":"distro","path":"l.erofs","uuid":"b36fe728-b5af-5551-b421-018b3a4de53a"}, +{"flavor":"windows","value":"runtime,l.erofs,{a110fcca-a69e-5161-53c2-bc0ca0e94e09}","role":"runtime","path":"l.erofs","uuid":"a110fcca-a69e-5161-53c2-bc0ca0e94e09"}, +{"flavor":"windows","value":"runtime,l.erofs,4fc70d2cc{4dbff4abb5658f7b4722deb","error":"runtime layer UUID is invalid: '4fc70d2cc{4dbff4abb5658f7b4722deb'"}, +{"flavor":"windows","value":"runtime,l.erofs,D2E71CE1-30E5-BF2A-8903-75506A9F274F","role":"runtime","path":"l.erofs","uuid":"d2e71ce1-30e5-bf2a-8903-75506a9f274f"}, +{"flavor":"windows","value":"runtime,l.erofs,urn:uuid:83439e1b-2664-4a3f-276e-0f9e75587d3b","role":"runtime","path":"l.erofs","uuid":"83439e1b-2664-4a3f-276e-0f9e75587d3b"}, +{"flavor":"windows","value":"distro,l.erofs,d5be81ce-6e0e-53b9-f5d0-6a9b9bceb265","role":"distro","path":"l.erofs","uuid":"d5be81ce-6e0e-53b9-f5d0-6a9b9bceb265"}, +{"flavor":"windows","value":"distro,l.erofs,95cc2409-bbca-2a82-de91-7632852d212c","role":"distro","path":"l.erofs","uuid":"95cc2409-bbca-2a82-de91-7632852d212c"}, +{"flavor":"windows","value":"runtime,l.erofs,92A33C76-9BC5-5182bBA0B-7ED7406EC40","role":"runtime","path":"l.erofs","uuid":"92a33c76-9bc5-5182-bba0-b7ed7406ec40"}, +{"flavor":"windows","value":"custom,l.erofs,{2a882789-795e-5c58-9680-67e62f3e13f6}","role":"custom","path":"l.erofs","uuid":"2a882789-795e-5c58-9680-67e62f3e13f6"}, +{"flavor":"windows","value":"distro,l.erofs,ccafea27-8d70-dabf-4951-3e2dadf7c698","role":"distro","path":"l.erofs","uuid":"ccafea27-8d70-dabf-4951-3e2dadf7c698"}, +{"flavor":"windows","value":"distro,l.erofs,532C3ADE-D6A5-E14-CF21-14B9798B420A","error":"distro layer UUID is invalid: '532C3ADE-D6A5-E14-CF21-14B9798B420A'"}, +{"flavor":"windows","value":"custom,l.erofs,6C9CBB32-DE23-D3F5-D660-D3AB216169C1","role":"custom","path":"l.erofs","uuid":"6c9cbb32-de23-d3f5-d660-d3ab216169c1"}, +{"flavor":"windows","value":"custom,l.erofs,0960b882-3da7-6b8e-3a06-e525149dc739","role":"custom","path":"l.erofs","uuid":"0960b882-3da7-6b8e-3a06-e525149dc739"}, +{"flavor":"windows","value":"runtime,l.erofs,37b3c3f6-5d11-b3f7-9326-7a57ac15d0fd","role":"runtime","path":"l.erofs","uuid":"37b3c3f6-5d11-b3f7-9326-7a57ac15d0fd"}, +{"flavor":"windows","value":"custom,;: x,y,{3a551b21-d4e9-146e-7603-67059ab821fe}","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":",l.erofs,urn:uuid:cda20c9b-83c7-98da-d1b2-003c3aea0b7e","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"windows","value":"distro,l.erofs,{aecbe287-14eb-56a4-af8a-2e3745acd76d}","role":"distro","path":"l.erofs","uuid":"aecbe287-14eb-56a4-af8a-2e3745acd76d"}, +{"flavor":"windows","value":"custom,l.erofs,88bf5b5cb00a47a137d2fa6aafd047cf","role":"custom","path":"l.erofs","uuid":"88bf5b5c-b00a-47a1-37d2-fa6aafd047cf"}, +{"flavor":"windows","value":"runtime,l.erofs,urn:uuid:dc3d63b-190Cd-dca1-7683-2ea1b6e5364c","role":"runtime","path":"l.erofs","uuid":"dc3d63b1-90cd-dca1-7683-2ea1b6e5364c"}, +{"flavor":"windows","value":"runtime,\u00e9\\\\\\unc:,a02af7acae132fbaccbddfc4f090167dd8","error":"runtime layer UUID is invalid: 'a02af7acae132fbaccbddfc4f090167dd8'"}, +{"flavor":"windows","value":"distro,l.erofs,{6515471f-2973-ae1e-8d7f-fb7b51df5998}","role":"distro","path":"l.erofs","uuid":"6515471f-2973-ae1e-8d7f-fb7b51df5998"}, +{"flavor":"windows","value":"distro,;\u00e9\u00e9\\\\UNC,b55261e86c80c039671e2b6884b446f3","role":"distro","path":";\u00e9\u00e9\\UNC","uuid":"b55261e8-6c80-c039-671e-2b6884b446f3"}, +{"flavor":"windows","value":"other,l.erofs,{9a0d92b2-47da-59a0-f9c3-78e5d4078715}","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"windows","value":"custom,\u0000,292dd182-f2ee-70a7-aa01-45edda6d9076","role":"custom","path":"\u0000","uuid":"292dd182-f2ee-70a7-aa01-45edda6d9076"}, +{"flavor":"windows","value":"custom,l.erofs,d34dc48b7b0e451b774c5412c547a1f3","role":"custom","path":"l.erofs","uuid":"d34dc48b-7b0e-451b-774c-5412c547a1f3"}, +{"flavor":"windows","value":"distro,l.erofs,{2c354ec2-09cb-3185-72b4-5c3c57b2288a}","role":"distro","path":"l.erofs","uuid":"2c354ec2-09cb-3185-72b4-5c3c57b2288a"}, +{"flavor":"windows","value":"distro,l.erofs,{0fc1d4b1-c50a-5882-4869-495975691dad}","role":"distro","path":"l.erofs","uuid":"0fc1d4b1-c50a-5882-4869-495975691dad"}, +{"flavor":"windows","value":"distro,l.erofs,{2c6d75df-348c-7ede-46f5-f9680a4475d0}","role":"distro","path":"l.erofs","uuid":"2c6d75df-348c-7ede-46f5-f9680a4475d0"}, +{"flavor":"windows","value":"custom,l.erofs,urn:uuid:5b3d3301-7ae3-39bd-0593-11c1ffc3e1e8","role":"custom","path":"l.erofs","uuid":"5b3d3301-7ae3-39bd-0593-11c1ffc3e1e8"}, +{"flavor":"windows","value":"runtime,x,y,222e5703-5db7-9b5d-fa80-0cfdfea56a27","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"runtime,;,{24eb406e-8f63-1ab2-d6d5-55607c0bc6b3}","role":"runtime","path":";","uuid":"24eb406e-8f63-1ab2-d6d5-55607c0bc6b3"}, +{"flavor":"windows","value":"custom,b,ee04d4f8-683a-571a-bf6f-b4a044dc79b3","role":"custom","path":"b","uuid":"ee04d4f8-683a-571a-bf6f-b4a044dc79b3"}, +{"flavor":"windows","value":"custom,l.erofs,1b1b0886-7c07-7f9e-7570-a6631c0154ab","role":"custom","path":"l.erofs","uuid":"1b1b0886-7c07-7f9e-7570-a6631c0154ab"}, +{"flavor":"windows","value":"runtime,l.erofs,{e1a7ad9b-ac25-d8c2-f9b3-45d7fd8b23b7}","role":"runtime","path":"l.erofs","uuid":"e1a7ad9b-ac25-d8c2-f9b3-45d7fd8b23b7"}, +{"flavor":"windows","value":"Distro,c\u00e9?,{bcdadb4-d20-dd18-5e4-b59dd2055185}","error":"unsupported layer role 'Distro'; choose distro, runtime, custom"}, +{"flavor":"windows","value":"runtime,l.erofs,37bf70eb-eb85-8145-6327-592a25c4f115","role":"runtime","path":"l.erofs","uuid":"37bf70eb-eb85-8145-6327-592a25c4f115"}, +{"flavor":"windows","value":"custom,l.erofs,32378E3C-73C8-EF04-1DA6-DF849450B1A9","role":"custom","path":"l.erofs","uuid":"32378e3c-73c8-ef04-1da6-df849450b1a9"}, +{"flavor":"windows","value":"distro,l.erofs,urn:uuid:0b03c9dd-401f-7bfb-e3e3-b332640a35d5","role":"distro","path":"l.erofs","uuid":"0b03c9dd-401f-7bfb-e3e3-b332640a35d5"}, +{"flavor":"windows","value":"runtime,l.erofs,6230FAF7-410A-2FF7-9CD9-D7362210D4B6","role":"runtime","path":"l.erofs","uuid":"6230faf7-410a-2ff7-9cd9-d7362210d4b6"}, +{"flavor":"windows","value":"runtime,/\u0000?,0ca2a217497f43dd10aa3a290eb115f","error":"runtime layer UUID is invalid: '0ca2a217497f43dd10aa3a290eb115f'"}, +{"flavor":"windows","value":"distro,l.erofs,b316badf-2319-c480-c888-02f1bd605338","role":"distro","path":"l.erofs","uuid":"b316badf-2319-c480-c888-02f1bd605338"}, +{"flavor":"windows","value":"distro,.C/ ,25ca1a68-1ab9-902a-e7b6-4273181c79cb","role":"distro","path":".C\\ ","uuid":"25ca1a68-1ab9-902a-e7b6-4273181c79cb"}, +{"flavor":"windows","value":"distro,l.erofs,3688118-C599-1453-3290-C4A7C34B7CD","error":"distro layer UUID is invalid: '3688118-C599-1453-3290-C4A7C34B7CD'"}, +{"flavor":"windows","value":"distro,uncx,yb\u00e9:,{1ffead7c-9a77-85b1-7a68-33c85b062c65}","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"other,l.erofs,urn:uuid:bf546440-63c6-cbc5-7ff2-5e82b9fbfa19","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"windows","value":"distro,l.erofs,dabedd032c6f656a12c6f617fc6e3007","role":"distro","path":"l.erofs","uuid":"dabedd03-2c6f-656a-12c6-f617fc6e3007"}, +{"flavor":"windows","value":",l.erofs,urn:uuid:499f619b-9bc1-b3c5-bb3c-eda7afb2700b","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"windows","value":"distro,UNCx,yCuncc,AB614B36-E4EA-9EB3-C0D1-A65F1C50518D","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"custom,l.erofs,6e0eb82a1495c6a6c4c7881e9f692a65","role":"custom","path":"l.erofs","uuid":"6e0eb82a-1495-c6a6-c4c7-881e9f692a65"}, +{"flavor":"windows","value":"distro,.b,05478E4-95B9-8391-B322-65B77F1F4A513","role":"distro","path":".b","uuid":"05478e49-5b98-391b-3226-5b77f1f4a513"}, +{"flavor":"windows","value":"other,l.erofs,6EFB9A12-7711-5D46-4A71-2AAF3151BB08","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"windows","value":"custom,l.erofs,urn:uuid:672efe58-44dc-82e3-070e-01970a44155e","role":"custom","path":"l.erofs","uuid":"672efe58-44dc-82e3-070e-01970a44155e"}, +{"flavor":"windows","value":"distro,l.erofs,b1399c0c-cc0e-ed26-d7a8-59f0f85ba50e","role":"distro","path":"l.erofs","uuid":"b1399c0c-cc0e-ed26-d7a8-59f0f85ba50e"}, +{"flavor":"windows","value":"custom,;,2b8c983b48fe117a9c752b6ae4beba0","error":"custom layer UUID is invalid: '2b8c983b48fe117a9c752b6ae4beba0'"}, +{"flavor":"windows","value":"runtime,l.erofs,ad196266f44e84b43ca4cd55774d7149","role":"runtime","path":"l.erofs","uuid":"ad196266-f44e-84b4-3ca4-cd55774d7149"}, +{"flavor":"windows","value":"distro,l.erofs,EAD3B99C-9987-7DE9-A7EF-82F85E07AF86","role":"distro","path":"l.erofs","uuid":"ead3b99c-9987-7de9-a7ef-82f85e07af86"}, +{"flavor":"windows","value":"runtime,\\\\c\\\u00e9,urn:uuid:4092f28d-dc50-8b06-9ecc-a48ed0ad6507","role":"runtime","path":"\\\\c\\\u00e9\\","uuid":"4092f28d-dc50-8b06-9ecc-a48ed0ad6507"}, +{"flavor":"windows","value":"custom,\u0000unc\\\\;..,825f732fd850a36d6c4c597220227130","role":"custom","path":"\u0000unc\\;..","uuid":"825f732f-d850-a36d-6c4c-597220227130"}, +{"flavor":"windows","value":"custom,l.erofs,F6731AAF-D316-01D8-4BF1-68CC72AF9909","role":"custom","path":"l.erofs","uuid":"f6731aaf-d316-01d8-4bf1-68cc72af9909"}, +{"flavor":"windows","value":"distro,l.erofs,77CA0BFCE-0314-EC44E-3C4C-E3BAAD63E9A","error":"distro layer UUID is invalid: '77CA0BFCE-0314-EC44E-3C4C-E3BAAD63E9A'"}, +{"flavor":"windows","value":"custom,l.erofs,{c68d0969-de02-5426-06d3-0be6e6d1dabf}","role":"custom","path":"l.erofs","uuid":"c68d0969-de02-5426-06d3-0be6e6d1dabf"}, +{"flavor":"windows","value":"distro,l.erofs,ab32e5242d8e30fE35381179bf9ceD2d","role":"distro","path":"l.erofs","uuid":"ab32e524-2d8e-30fe-3538-1179bf9ced2d"}, +{"flavor":"windows","value":"runtime,l.erofs,{2af58845-a26d-8c0f-b4bb-e39fccfc3b4c}","role":"runtime","path":"l.erofs","uuid":"2af58845-a26d-8c0f-b4bb-e39fccfc3b4c"}, +{"flavor":"windows","value":"distro,l.erofs,bfb97fd5-3a84-a7e8-b479-5a0ddf6db5cc","role":"distro","path":"l.erofs","uuid":"bfb97fd5-3a84-a7e8-b479-5a0ddf6db5cc"}, +{"flavor":"windows","value":"runtime,l.erofs,6d049}f8d-829d-c9a6-73b7-09bc366dc5b42","error":"runtime layer UUID is invalid: '6d049}f8d-829d-c9a6-73b7-09bc366dc5b42'"}, +{"flavor":"windows","value":"custom,l.erofs,4F4CF90B-B81F-A867-93B5-8E73C239F190","role":"custom","path":"l.erofs","uuid":"4f4cf90b-b81f-a867-93b5-8e73c239f190"}, +{"flavor":"windows","value":"distro,l.erofs,{522bca4d-8403-77d2-0839-e91d7ad9e24c}","role":"distro","path":"l.erofs","uuid":"522bca4d-8403-77d2-0839-e91d7ad9e24c"}, +{"flavor":"windows","value":"distro,l.erofs,urn:uuid:0c40a365-6158-b360-fca2-35d13fe588bd","role":"distro","path":"l.erofs","uuid":"0c40a365-6158-b360-fca2-35d13fe588bd"}, +{"flavor":"windows","value":"runtime,.. ?\\\\,c57eaeca-2945-0e90-b1d3-b2a40d0f30bd","role":"runtime","path":".. ?","uuid":"c57eaeca-2945-0e90-b1d3-b2a40d0f30bd"}, +{"flavor":"windows","value":"distro,l.erofs,e7daea43-ef9c-113b-0418-37eba9114810","role":"distro","path":"l.erofs","uuid":"e7daea43-ef9c-113b-0418-37eba9114810"}, +{"flavor":"windows","value":"custom,unc,{32427f34-de49-89d4-8ded-5dcddb5345c2}","role":"custom","path":"unc","uuid":"32427f34-de49-89d4-8ded-5dcddb5345c2"}, +{"flavor":"windows","value":"custom,:,4c2bbdf8-22c8-7cb4-bd0a-3035f04eec23","role":"custom","path":":","uuid":"4c2bbdf8-22c8-7cb4-bd0a-3035f04eec23"}, +{"flavor":"windows","value":"runtime,l.erofs,78143d74-3b93-2adb-dfd4-507da43913bc","role":"runtime","path":"l.erofs","uuid":"78143d74-3b93-2adb-dfd4-507da43913bc"}, +{"flavor":"windows","value":"custom,x,yCa:\\\\,30f2a9c6-854c-7403-c4e2-1856023f0be6","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"runtime,..C ,f1680b112-ad0c-ef25-53dd-3dc0701f1a3","role":"runtime","path":"..C ","uuid":"f1680b11-2ad0-cef2-553d-d3dc0701f1a3"}, +{"flavor":"windows","value":"distro,l.erofs,{63a4ce9d-86d5-2050-c34e-2b6ee0a06565}","role":"distro","path":"l.erofs","uuid":"63a4ce9d-86d5-2050-c34e-2b6ee0a06565"}, +{"flavor":"windows","value":",.bC;b,EBD3B66D-5AC5-33C5-90EB-7329A3D01D3F","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"windows","value":"runtime,l.erofs,bd0fee6d-efa4-9a7d-1d10-b7e2cc5682b0","role":"runtime","path":"l.erofs","uuid":"bd0fee6d-efa4-9a7d-1d10-b7e2cc5682b0"}, +{"flavor":"windows","value":"custom,l.erofs,5ecf5c78-cb7c-dfb4-334d-234d4721b4c9","role":"custom","path":"l.erofs","uuid":"5ecf5c78-cb7c-dfb4-334d-234d4721b4c9"}, +{"flavor":"windows","value":"custom,l.erofs,8b0d15bcaf5d90669c821b1112b7e431","role":"custom","path":"l.erofs","uuid":"8b0d15bc-af5d-9066-9c82-1b1112b7e431"}, +{"flavor":"windows","value":"custom,l.erofs,d17d90e3-0bb6-226b-7a3b-1ab6a8533fe6","role":"custom","path":"l.erofs","uuid":"d17d90e3-0bb6-226b-7a3b-1ab6a8533fe6"}, +{"flavor":"windows","value":"distro,l.erofs,2F077696-A4D8-2E70-86E7-EB6B0C85650F","role":"distro","path":"l.erofs","uuid":"2f077696-a4d8-2e70-86e7-eb6b0c85650f"}, +{"flavor":"windows","value":"custom,l.erofs,f3728c33-b0ea-7764-d116-71642b05765d","role":"custom","path":"l.erofs","uuid":"f3728c33-b0ea-7764-d116-71642b05765d"}, +{"flavor":"windows","value":",l.erofs,76e7ca06b12f0b32fecde12e68fd412f","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"windows","value":"other,l.erofs,{ee87b998-5e87-5c51-c238-028bc639a714}","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"windows","value":"distro,./b,8bc4605cf9e5e49eaadb9e18ee1782b5","role":"distro","path":"b","uuid":"8bc4605c-f9e5-e49e-aadb-9e18ee1782b5"}, +{"flavor":"windows","value":"distro,l.erofs,urn:uuid:79f3ddae-9c07-800f-45cf-54097d035ef8","role":"distro","path":"l.erofs","uuid":"79f3ddae-9c07-800f-45cf-54097d035ef8"}, +{"flavor":"windows","value":"custom,C,47823a6dff65489fcd5be72972fee2cb","role":"custom","path":"C","uuid":"47823a6d-ff65-489f-cd5b-e72972fee2cb"}, +{"flavor":"windows","value":"custom,l.erofs,urn:uuid:854c5327-f769-381a-4a98-d966a4ba29e6","role":"custom","path":"l.erofs","uuid":"854c5327-f769-381a-4a98-d966a4ba29e6"}, +{"flavor":"windows","value":"runtime,b.//:\\\\,476740ac-bafe-c2f2-3ebf-e49cef11b409","role":"runtime","path":"b.\\:","uuid":"476740ac-bafe-c2f2-3ebf-e49cef11b409"}, +{"flavor":"windows","value":"custom,l.erofs,B277A3B5-87A2-51A4-8EB7-A2691C95D135","role":"custom","path":"l.erofs","uuid":"b277a3b5-87a2-51a4-8eb7-a2691c95d135"}, +{"flavor":"windows","value":"custom,x,y?ba,869ebbba-6f4c-95c4-1860-dd8f486b24f4","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"other,\\/;//a,37D0039E-DC2B-80B2-EFA1-333BA95B39FC","error":"unsupported layer role 'other'; choose distro, runtime, custom"}, +{"flavor":"windows","value":"runtime,:;x,y,1df499fbac7ecdb056a5a31922e139b0","error":"--layer must be ROLE,PATH,EROFS_UUID"}, +{"flavor":"windows","value":"custom,l.erofs,{85ba55b0-696e-26c9-fd7e-9f7eead7fe21}","role":"custom","path":"l.erofs","uuid":"85ba55b0-696e-26c9-fd7e-9f7eead7fe21"}, +{"flavor":"windows","value":"distro,l.erofs,{91d6acca-835b-23a6-c0b4-4983a1b3b045}","role":"distro","path":"l.erofs","uuid":"91d6acca-835b-23a6-c0b4-4983a1b3b045"}, +{"flavor":"windows","value":"distro,a.UNC\\//,urn:uuid:4e03e884-6e1a-872e-1414-2e8c94f02eb0","role":"distro","path":"a.UNC","uuid":"4e03e884-6e1a-872e-1414-2e8c94f02eb0"}, +{"flavor":"windows","value":"custom,l.erofs,urn:uuid:02e32568-fcca-f123-5353-e9b83cef4368","role":"custom","path":"l.erofs","uuid":"02e32568-fcca-f123-5353-e9b83cef4368"}, +{"flavor":"windows","value":",l.erofs,77643A3D-73B4-7A44-4995-17CBD01064761","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"windows","value":"runtime,\u00e9\\\\,42e8eb08-f5cd-09c0-b983-a385a69e51db","role":"runtime","path":"\u00e9","uuid":"42e8eb08-f5cd-09c0-b983-a385a69e51db"}, +{"flavor":"windows","value":"distro,l.erofs,b563db2fe17d97300341346b7fb8a733","role":"distro","path":"l.erofs","uuid":"b563db2f-e17d-9730-0341-346b7fb8a733"}, +{"flavor":"windows","value":"custom,l.erofs,urn:uuid:5ebe3631-3b0a-9e22-7361-c4418f9be13c","role":"custom","path":"l.erofs","uuid":"5ebe3631-3b0a-9e22-7361-c4418f9be13c"}, +{"flavor":"windows","value":"custom,l.erofs,{2da3af6-33d4-386c-fea5-0146f2803a6f}","error":"custom layer UUID is invalid: '{2da3af6-33d4-386c-fea5-0146f2803a6f}'"}, +{"flavor":"windows","value":"custom,l.erofs,{96064721-c861-cf7c-286d-34728bb8ca13}","role":"custom","path":"l.erofs","uuid":"96064721-c861-cf7c-286d-34728bb8ca13"}, +{"flavor":"windows","value":"runtime,l.erofs,65b24dea-7418-c086-3c04-3cd799cff062","role":"runtime","path":"l.erofs","uuid":"65b24dea-7418-c086-3c04-3cd799cff062"}, +{"flavor":"windows","value":"distro,\u00e9 ,37a1897c6df95069935c4ead59c96a33","role":"distro","path":"\u00e9 ","uuid":"37a1897c-6df9-5069-935c-4ead59c96a33"}, +{"flavor":"windows","value":"custom,l.erofs,2CE7F754-351A-DB60258f0-E8E032441C57","error":"custom layer UUID is invalid: '2CE7F754-351A-DB60258f0-E8E032441C57'"}, +{"flavor":"windows","value":"runtime,l.erofs,urn:uuid:6ac1a990-d7a6-5989-e871-c7412b190192","role":"runtime","path":"l.erofs","uuid":"6ac1a990-d7a6-5989-e871-c7412b190192"}, +{"flavor":"windows","value":"custom,l.erofs,{5f40518e-0d79-60fd-55c2-da069ddc0ab0}","role":"custom","path":"l.erofs","uuid":"5f40518e-0d79-60fd-55c2-da069ddc0ab0"}, +{"flavor":"windows","value":"custom,l.erofs,d055ce0d-8e03-e2ff-f256-ee19a8413fe8","role":"custom","path":"l.erofs","uuid":"d055ce0d-8e03-e2ff-f256-ee19a8413fe8"}, +{"flavor":"windows","value":"runtime,l.erofs,ae127445-baf6-03b0-d399-e6a9f509e85c","role":"runtime","path":"l.erofs","uuid":"ae127445-baf6-03b0-d399-e6a9f509e85c"}, +{"flavor":"windows","value":"custom,l.erofs,{fb9ed6a3-cd7e-94f2-cdfb-fb579e78e411}","role":"custom","path":"l.erofs","uuid":"fb9ed6a3-cd7e-94f2-cdfb-fb579e78e411"}, +{"flavor":"windows","value":"runtime,l.erofs,6a7691af-Dafb9-3750c-bff2-baf5274a52c","error":"runtime layer UUID is invalid: '6a7691af-Dafb9-3750c-bff2-baf5274a52c'"}, +{"flavor":"windows","value":"distro,l.erofs,urn:uuid:a0e9b18a-1bd9-32f6-d5d6-ec04d2fc6239","role":"distro","path":"l.erofs","uuid":"a0e9b18a-1bd9-32f6-d5d6-ec04d2fc6239"}, +{"flavor":"windows","value":"distro,UNCUNC;..,caf75663-6c92-8e7a-ecc0-34269711b7a5","role":"distro","path":"UNCUNC;..","uuid":"caf75663-6c92-8e7a-ecc0-34269711b7a5"}, +{"flavor":"windows","value":",\\\\\\\\.a\\,9AF458AB-F246D-B12B-8E8C-3C1F316A616","error":"unsupported layer role ''; choose distro, runtime, custom"}, +{"flavor":"windows","value":"distro,\\,50e889b-aec2-298acb99-e98235296bab","error":"distro layer UUID is invalid: '50e889b-aec2-298acb99-e98235296bab'"}, +{"flavor":"windows","value":"distro,l.erofs,a462541d-e859-9321-fb91-8728b6556688","role":"distro","path":"l.erofs","uuid":"a462541d-e859-9321-fb91-8728b6556688"}, +{"flavor":"windows","value":"distro,l.erofs,0f0c1ada-0f7f-793a-94b7-11c02b5dac00","role":"distro","path":"l.erofs","uuid":"0f0c1ada-0f7f-793a-94b7-11c02b5dac00"}, +{"flavor":"windows","value":"runtime,l.erofs,5F35AA7D-2A62-4B24-B9F4-ED94935B156F","role":"runtime","path":"l.erofs","uuid":"5f35aa7d-2a62-4b24-b9f4-ed94935b156f"}, +{"flavor":"windows","value":"custom,l.erofs,e3de5cd9dfe45cafe1759b71cb1a3bfa","role":"custom","path":"l.erofs","uuid":"e3de5cd9-dfe4-5caf-e175-9b71cb1a3bfa"} +], +"mount_values": [ +{"value":"/workspace","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"/workspace,","error":"--mount host path is empty"}, +{"value":"/workspace,,rw","error":"--mount host path is empty"}, +{"value":"/workspace,host,rx","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/workspace,host,rw,extra","error":"unsupported --mount mode 'extra'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/workspace,alpha,beta","error":"unsupported --mount mode 'beta'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/data,/srv/data,rw,ro","target":"/data","host_path":"/srv/data,rw","access":"ro"}, +{"value":"/data,/srv/data,,rw","target":"/data","host_path":"/srv/data,","access":"rw"}, +{"value":"/data,/srv/data,rw","target":"/data","host_path":"/srv/data","access":"rw"}, +{"value":"/data,/srv/data","target":"/data","host_path":"/srv/data","access":"ro"}, +{"value":",","error":"--mount host path is empty"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"c","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"b.unc\u0000b\\\\","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":",CbUNC","target":"","host_path":"CbUNC","access":"ro"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\u00e9\u0000/Cro//","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"/ ;\u00e9a:","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"b","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"UNC //","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"/b:////","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":":unc?..C/","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"C","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"b,uncC;","target":"b","host_path":"uncC;","access":"ro"}, +{"value":" ....:ro:","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\\x,y\\\\:\u00e9","target":"\\x","host_path":"y\\\\:\u00e9","access":"ro"}, +{"value":"rw","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"/","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\\\\//uncrw","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":" \u0000\u0000 ","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"unc .\u00e9?crw ","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"c ","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":",\u00e9\\\\\\\\\u00e9//","target":"","host_path":"\u00e9\\\\\\\\\u00e9//","access":"ro"}, +{"value":"\u00e9rw;\\\\/","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":";?, ","target":";?","host_path":" ","access":"ro"}, +{"value":"?\\C.bc","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"/\\\\b","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"?\u0000rox,y","target":"?\u0000rox","host_path":"y","access":"ro"}, +{"value":"..:b?;ro\u0000..","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"C//C\\\\","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"//.rw?uncC","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"a","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"rox,y////","target":"rox","host_path":"y////","access":"ro"}, +{"value":"a ","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"Cx,y,,x,y\u00e9b\\","error":"unsupported --mount mode 'y\u00e9b\\\\'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"?\u0000x,y,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":";//aunc:x,yx,y","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"\\\\","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"unc;x,y;","target":"unc;x","host_path":"y;","access":"ro"}, +{"value":",","error":"--mount host path is empty"}, +{"value":" //\\\\rwUNC","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"b..:;//rw?","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"/;b","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"rw?a;....\u00e9","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"ax,y;x,y\\","error":"unsupported --mount mode 'y\\\\'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"\\\\//a;a.","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"uncunc..","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"a..UNCrw:\\","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"?ro:","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\\\\\\","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"b..unc\\\\","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":",\\\\UNC\u00e9\u00e9..:","target":"","host_path":"\\\\UNC\u00e9\u00e9..:","access":"ro"}, +{"value":"\u0000,\u00e9rw;\\\\.","target":"\u0000","host_path":"\u00e9rw;\\\\.","access":"ro"}, +{"value":",cUNC\u0000?..rw","target":"","host_path":"cUNC\u0000?..rw","access":"ro"}, +{"value":"rwa","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"?b?UNC..","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"C","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"C;aUNC//ro,","error":"--mount host path is empty"}, +{"value":"..\u00e9","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"b//, ..a","target":"b//","host_path":" ..a","access":"ro"}, +{"value":".\u00e9:","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"ro\\ ?//rw//","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\\\\UNC ","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":" rw..unc\u00e9/ro","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\u0000","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"rouncuncc: ","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":":","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":".x,yroUNC//\u0000\u00e9..","target":".x","host_path":"yroUNC//\u0000\u00e9..","access":"ro"}, +{"value":"x,y","target":"x","host_path":"y","access":"ro"}, +{"value":"ro//\u00e9/x,y.ro","target":"ro//\u00e9/x","host_path":"y.ro","access":"ro"}, +{"value":" \u00e9c","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\u0000","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":" C\u00e9arox,y","target":" C\u00e9arox","host_path":"y","access":"ro"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":";\u0000rwCrox,y:\\\\","target":";\u0000rwCrox","host_path":"y:\\\\","access":"ro"}, +{"value":"ro///..x,y;b","target":"ro///..x","host_path":"y;b","access":"ro"}, +{"value":"Ca","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"UNC","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\u0000;..ro?://","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\u0000,uncaunc","target":"\u0000","host_path":"uncaunc","access":"ro"}, +{"value":"/:/\\\\","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"UNCx,y:x,y","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"rw\\\\:.,// ","target":"rw\\\\:.","host_path":"// ","access":"ro"}, +{"value":"\\/","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"x,yunc","target":"x","host_path":"yunc","access":"ro"}, +{"value":"c;roC","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\u00e9?:..unc;..","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\\UNC.\u0000c ,","error":"--mount host path is empty"}, +{"value":" c:c","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"UNC\\ro","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"UNC..","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"rw","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"uncUNCa?","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"\u0000;b\u0000/","error":"--mount must be GUEST_TARGET,HOST_PATH[,ro|rw]"}, +{"value":"/\u00e9/nvx/srv,aaUNC,ro","target":"/\u00e9/nvx/srv","host_path":"aaUNC","access":"ro"}, +{"value":"/srv/\u00e9,\\\\//,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/cache/a,aunc\u0000,rw","target":"/cache/a","host_path":"aunc\u0000","access":"rw"}, +{"value":"/nvx,; a,rw","target":"/nvx","host_path":"; a","access":"rw"}, +{"value":"/a/srv/nvx,unc..C,,ro","target":"/a/srv/nvx","host_path":"unc..C,","access":"ro"}, +{"value":"/data-1,\u00e9\u0000,ro","target":"/data-1","host_path":"\u00e9\u0000","access":"ro"}, +{"value":"/\u00e9/etc,\u00e9a//c","target":"/\u00e9/etc","host_path":"\u00e9a//c","access":"ro"}, +{"value":"/nvx,\\uncx,yC,ro","target":"/nvx","host_path":"\\uncx,yC","access":"ro"}, +{"value":"/a/srv/work,a","target":"/a/srv/work","host_path":"a","access":"ro"}, +{"value":"/etc/\u00e9/data-1,.","target":"/etc/\u00e9/data-1","host_path":".","access":"ro"}, +{"value":"/run/srv/work,//\u0000c","target":"/run/srv/work","host_path":"//\u0000c","access":"ro"}, +{"value":"/data-1/srv,\u00e9,rw","target":"/data-1/srv","host_path":"\u00e9","access":"rw"}, +{"value":"/etc,C\u0000;:","target":"/etc","host_path":"C\u0000;:","access":"ro"}, +{"value":"/srv,.","target":"/srv","host_path":".","access":"ro"}, +{"value":"/srv,\u0000?.unc,ro","target":"/srv","host_path":"\u0000?.unc","access":"ro"}, +{"value":"/\u00e9/srv/cache,;b,rw","target":"/\u00e9/srv/cache","host_path":";b","access":"rw"}, +{"value":"/work/srv,unc.//","target":"/work/srv","host_path":"unc.//","access":"ro"}, +{"value":"/\u00e9/nvx,\\\\x,y,rx","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/work,cC,ro","target":"/work","host_path":"cC","access":"ro"}, +{"value":"/data-1/data-1,unc.,rw","target":"/data-1/data-1","host_path":"unc.","access":"rw"}, +{"value":"/etc/run,\\,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"//etc, ,rw","target":"//etc","host_path":" ","access":"rw"}, +{"value":"/srv/data-1,UNCUNC","target":"/srv/data-1","host_path":"UNCUNC","access":"ro"}, +{"value":"/srv/data-1/srv,/../;,ro","target":"/srv/data-1/srv","host_path":"/../;","access":"ro"}, +{"value":"/data-1/work,/,rw","target":"/data-1/work","host_path":"/","access":"rw"}, +{"value":"/sharesnvx,UNCUNC\\\\unc,rx","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/,UNCc","target":"/","host_path":"UNCc","access":"ro"}, +{"value":"/\u00e9/srv,..,ro","target":"/\u00e9/srv","host_path":"..","access":"ro"}, +{"value":"/nvx/etc,;","target":"/nvx/etc","host_path":";","access":"ro"}, +{"value":"/srv/work/a,.","target":"/srv/work/a","host_path":".","access":"ro"}, +{"value":"/work/data-1,C\u0000,rw","target":"/work/data-1","host_path":"C\u0000","access":"rw"}, +{"value":"/etc/run, uncUNC:,rw","target":"/etc/run","host_path":" uncUNC:","access":"rw"}, +{"value":"///data-1,\\\\C,rw","target":"///data-1","host_path":"\\\\C","access":"rw"}, +{"value":"/run, C,rw","target":"/run","host_path":" C","access":"rw"}, +{"value":"/cache/a/a,UNC// ,ro","target":"/cache/a/a","host_path":"UNC// ","access":"ro"}, +{"value":"/\u00e9/\u00e9,unc//\\x,y,rw","target":"/\u00e9/\u00e9","host_path":"unc//\\x,y","access":"rw"}, +{"value":"/cache/a/etc,\\,rw","target":"/cache/a/etc","host_path":"\\","access":"rw"}, +{"value":"/data-1,..\u00e9,rw","target":"/data-1","host_path":"..\u00e9","access":"rw"}, +{"value":"/\u00e9/etc,\u0000.,ro","target":"/\u00e9/etc","host_path":"\u0000.","access":"ro"}, +{"value":"/run/a,..","target":"/run/a","host_path":"..","access":"ro"}, +{"value":"/nvx,//\\.,rw","target":"/nvx","host_path":"//\\.","access":"rw"}, +{"value":"/nvx/cache/nvx,;..C,ro","target":"/nvx/cache/nvx","host_path":";..C","access":"ro"}, +{"value":"/data-1,:bunc","target":"/data-1","host_path":":bunc","access":"ro"}, +{"value":"/srv,?C,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/=,x,yb,rw","target":"/=","host_path":"x,yb","access":"rw"}, +{"value":"/nvx/srv,,UNC","error":"--mount host path is empty"}, +{"value":"/etc/run,?,ro","target":"/etc/run","host_path":"?","access":"ro"}, +{"value":"/nvx/run,C\u0000","target":"/nvx/run","host_path":"C\u0000","access":"ro"}, +{"value":"/a/data-1,,unc\\\\,rw","target":"/a/data-1","host_path":",unc\\\\","access":"rw"}, +{"value":"/a/\u00e9/run,unc","target":"/a/\u00e9/run","host_path":"unc","access":"ro"}, +{"value":"/work,//,rw","target":"/work","host_path":"//","access":"rw"}, +{"value":"/\u00e9/nvx,//a ,ro","target":"/\u00e9/nvx","host_path":"//a ","access":"ro"}, +{"value":"/etc,.:..,ro","target":"/etc","host_path":".:..","access":"ro"}, +{"value":"/run/nvx,x,yc..\\,rw","target":"/run/nvx","host_path":"x,yc..\\","access":"rw"}, +{"value":"/cache/data-1,\u0000\\\\x,y,rw","target":"/cache/data-1","host_path":"\u0000\\\\x,y","access":"rw"}, +{"value":"/etc/a,//unc\u0000unc,ro","target":"/etc/a","host_path":"//unc\u0000unc","access":"ro"}, +{"value":"/etc/\u00e9,C//,rw","target":"/etc/\u00e9","host_path":"C//","access":"rw"}, +{"value":"/etc,.uncaa","target":"/etc","host_path":".uncaa","access":"ro"}, +{"value":"/cache/cache/data-1,UNC?..C,rw","target":"/cache/cache/data-1","host_path":"UNC?..C","access":"rw"}, +{"value":"/data-1/run,\\b ,rw","target":"/data-1/run","host_path":"\\b ","access":"rw"}, +{"value":"/ /a,/ .","target":"/ /a","host_path":"/ .","access":"ro"}, +{"value":"/..a//srv, \\.,ro","target":"/..a//srv","host_path":" \\.","access":"ro"}, +{"value":"/data-1/run, \\\\;,rw","target":"/data-1/run","host_path":" \\\\;","access":"rw"}, +{"value":"/etc/a,.x,yunc,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/srv,b,rw","target":"/srv","host_path":"b","access":"rw"}, +{"value":"/a/etc,. . ,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/srv/work,b .,,rw","target":"/srv/work","host_path":"b .,","access":"rw"}, +{"value":"/\u00e9/cache/cache,?aUNC\\,rw","target":"/\u00e9/cache/cache","host_path":"?aUNC\\","access":"rw"}, +{"value":"/\u00e9,c//","target":"/\u00e9","host_path":"c//","access":"ro"}, +{"value":"/work/a/run,c?\u00e9,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/a/\u00e9/cache,x,y","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/\u00e9/cache/nvx,..,rw","target":"/\u00e9/cache/nvx","host_path":"..","access":"rw"}, +{"value":"/\u00e9/run,.","target":"/\u00e9/run","host_path":".","access":"ro"}, +{"value":"/cache/etc,\\unc\\","target":"/cache/etc","host_path":"\\unc\\","access":"ro"}, +{"value":"/cache,//uncUNC.,ro","target":"/cache","host_path":"//uncUNC.","access":"ro"}, +{"value":"/data-1/cache,c,","error":"unsupported --mount mode ''; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/data-1/srv,?c?,rw","target":"/data-1/srv","host_path":"?c?","access":"rw"}, +{"value":"/etc/data-1/etc,//..//\\,rw","target":"/etc/data-1/etc","host_path":"//..//\\","access":"rw"}, +{"value":"/data-1/\u00e9/a,.\u0000,ro","target":"/data-1/\u00e9/a","host_path":".\u0000","access":"ro"}, +{"value":"/data-1,./ ,rx","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/data-1,..\u00e9x,y.,ro","target":"/data-1","host_path":"..\u00e9x,y.","access":"ro"}, +{"value":"/nvx,unc;/UNC,ro","target":"/nvx","host_path":"unc;/UNC","access":"ro"}, +{"value":"/a/nvx,unc\\\\\\,ro","target":"/a/nvx","host_path":"unc\\\\\\","access":"ro"}, +{"value":"/nvx/data-1/work,c /,rw","target":"/nvx/data-1/work","host_path":"c /","access":"rw"}, +{"value":"/run/\u00e9/a,,b\u0000,ro","target":"/run/\u00e9/a","host_path":",b\u0000","access":"ro"}, +{"value":"/work/etc,a\u0000UNC\u00e9","target":"/work/etc","host_path":"a\u0000UNC\u00e9","access":"ro"}, +{"value":"/a/a/etc,,","error":"--mount host path is empty"}, +{"value":"/run/nvx,x,y.x,y\\\\,ro","target":"/run/nvx","host_path":"x,y.x,y\\\\","access":"ro"}, +{"value":"/work/etc,? x,y,ro","target":"/work/etc","host_path":"? x,y","access":"ro"}, +{"value":"/data-1/srv,UNC.C","target":"/data-1/srv","host_path":"UNC.C","access":"ro"}, +{"value":"/\u00e9/srv,..","target":"/\u00e9/srv","host_path":"..","access":"ro"}, +{"value":"/\u00e9/run,\\","target":"/\u00e9/run","host_path":"\\","access":"ro"}, +{"value":"/cache,a\\\\..,ro","target":"/cache","host_path":"a\\\\..","access":"ro"}, +{"value":"/\u00e9/data-1/srv,x,y//,rw","target":"/\u00e9/data-1/srv","host_path":"x,y//","access":"rw"}, +{"value":"/data-1/etc/etc,b\u00e9:b,rw","target":"/data-1/etc/etc","host_path":"b\u00e9:b","access":"rw"}, +{"value":"/data-1/run,\u0000C,RW","error":"unsupported --mount mode 'RW'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"value":"/nvx/cache,C,rw","target":"/nvx/cache","host_path":"C","access":"rw"}, +{"value":"/run/data-1/srv,:UNC\u0000","target":"/run/data-1/srv","host_path":":UNC\u0000","access":"ro"}, +{"value":"/nvx,C,ro","target":"/nvx","host_path":"C","access":"ro"}, +{"value":"/cache/run/etc,// a,rw","target":"/cache/run/etc","host_path":"// a","access":"rw"} +], +"mount_targets": [ +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/","error":"invalid sandbox mount target '/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"workspace","error":"invalid sandbox mount target 'workspace': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/workspace/","error":"invalid sandbox mount target '/workspace/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work//space","error":"invalid sandbox mount target '/work//space': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work/./space","error":"invalid sandbox mount target '/work/./space': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work/../space","error":"invalid sandbox mount target '/work/../space': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work space","error":"invalid sandbox mount target '/work space': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work=space","error":"invalid sandbox mount target '/work=space': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work\\space","error":"invalid sandbox mount target '/work\\\\space': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":"invalid sandbox mount target '/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":null}, +{"target":"/\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","error":"invalid sandbox mount target '/\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/proc","error":"sandbox mount target /proc overlaps the reserved /proc tree"}, +{"target":"/proc/self","error":"sandbox mount target /proc/self overlaps the reserved /proc tree"}, +{"target":"/sys/fs","error":"sandbox mount target /sys/fs overlaps the reserved /sys tree"}, +{"target":"/dev/shm","error":"sandbox mount target /dev/shm overlaps the reserved /dev tree"}, +{"target":"/.nvx-agent/tools","error":"sandbox mount target /.nvx-agent/tools overlaps the reserved /.nvx-agent tree"}, +{"target":"/etc","error":"sandbox mount target /etc is reserved"}, +{"target":"/etc/machine-id","error":"sandbox mount target /etc/machine-id overlaps the reserved /etc/machine-id tree"}, +{"target":"/etc/machine-id/child","error":"sandbox mount target /etc/machine-id/child overlaps the reserved /etc/machine-id tree"}, +{"target":"/workspace","error":null}, +{"target":"/procfs","error":null}, +{"target":"/etc/app","error":null}, +{"target":"/run","error":null}, +{"target":"/run/nvx","error":null}, +{"target":"/run/nvx/time","error":null}, +{"target":"/run/nvx/shares","error":null}, +{"target":"nvxproc\u3000=a\u001c\u00e9","error":"invalid sandbox mount target 'nvxproc\\u3000=a\\x1c\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"a /etc","error":"invalid sandbox mount target 'a /etc': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"=\u0000run","error":"invalid sandbox mount target '=\\x00run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"..,","error":"invalid sandbox mount target '..,': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"work./","error":"invalid sandbox mount target 'work./': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u001c","error":"invalid sandbox mount target '\\x1c': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"..machine-idetc\u001cetc ","error":"invalid sandbox mount target '..machine-idetc\\x1cetc ': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/\\\u001c\u001cetcnvx","error":"invalid sandbox mount target '/\\\\\\x1c\\x1cetcnvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":".run ","error":"invalid sandbox mount target '.run ': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u0000machine-idproc\\proc/ proc","error":"invalid sandbox mount target '\\x00machine-idproc\\\\proc/ proc': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u001c workwork","error":"invalid sandbox mount target '\\x1c workwork': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" ","error":"invalid sandbox mount target ' ': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u3000run\u001c,,,\u3000","error":"invalid sandbox mount target '\\u3000run\\x1c,,,\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"procetc,.\\\u3000nvxmachine-id","error":"invalid sandbox mount target 'procetc,.\\\\\\u3000nvxmachine-id': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":",\\../\u3000","error":"invalid sandbox mount target ',\\\\../\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"work","error":"invalid sandbox mount target 'work': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u3000etc,\u3000..run","error":"invalid sandbox mount target '\\u3000etc,\\u3000..run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":",runamachine-id\u0000 ,","error":"invalid sandbox mount target ',runamachine-id\\x00 ,': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"nvx\u001c\\a","error":"invalid sandbox mount target 'nvx\\x1c\\\\a': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\\","error":"invalid sandbox mount target '\\\\': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"worketc=work.","error":"invalid sandbox mount target 'worketc=work.': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"nvx","error":"invalid sandbox mount target 'nvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" \\","error":"invalid sandbox mount target ' \\\\': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":",machine-idnvx","error":"invalid sandbox mount target ',machine-idnvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etcshares=shares\u001c","error":"invalid sandbox mount target 'etcshares=shares\\x1c': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u3000\u001c.. shares","error":"invalid sandbox mount target '\\u3000\\x1c.. shares': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"a\u3000etcrun..\u00e9.","error":"invalid sandbox mount target 'a\\u3000etcrun..\u00e9.': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"proc.\u00e9\u3000","error":"invalid sandbox mount target 'proc.\u00e9\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":",machine-id...=","error":"invalid sandbox mount target ',machine-id...=': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"aaetc\u001ca/","error":"invalid sandbox mount target 'aaetc\\x1ca/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":".\\\u3000/","error":"invalid sandbox mount target '.\\\\\\u3000/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u0000runa","error":"invalid sandbox mount target '\\x00runa': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etc\u3000\u001c","error":"invalid sandbox mount target 'etc\\u3000\\x1c': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u3000nvx\u0000","error":"invalid sandbox mount target '\\u3000nvx\\x00': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" ==machine-id.\u0000aa","error":"invalid sandbox mount target ' ==machine-id.\\x00aa': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\\/\u00e9\u3000.\u001cmachine-id","error":"invalid sandbox mount target '\\\\/\u00e9\\u3000.\\x1cmachine-id': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"=\u00e9","error":"invalid sandbox mount target '=\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" run\u3000work\\","error":"invalid sandbox mount target ' run\\u3000work\\\\': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"=\u3000 \u001c","error":"invalid sandbox mount target '=\\u3000 \\x1c': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\\nvxprocprocwork","error":"invalid sandbox mount target '\\\\nvxprocprocwork': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"a\u001csharesnvxwork","error":"invalid sandbox mount target 'a\\x1csharesnvxwork': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u001c\\\u001c\u0000 \u00e9","error":"invalid sandbox mount target '\\x1c\\\\\\x1c\\x00 \u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"a .shares\u0000=etc","error":"invalid sandbox mount target 'a .shares\\x00=etc': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etc./","error":"invalid sandbox mount target 'etc./': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u3000","error":"invalid sandbox mount target '\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"machine-idnvx","error":"invalid sandbox mount target 'machine-idnvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\\run.procshares","error":"invalid sandbox mount target '\\\\run.procshares': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"..etc/ ","error":"invalid sandbox mount target '..etc/ ': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"machine-ida\u0000 \\arun=","error":"invalid sandbox mount target 'machine-ida\\x00 \\\\arun=': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"=procrunetc\u3000","error":"invalid sandbox mount target '=procrunetc\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u00e9nvx\u00e9\u001c\u001c\u3000nvx","error":"invalid sandbox mount target '\u00e9nvx\u00e9\\x1c\\x1c\\u3000nvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\\","error":"invalid sandbox mount target '\\\\': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etc","error":"invalid sandbox mount target 'etc': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"proc\u001c\u3000\u0000//","error":"invalid sandbox mount target 'proc\\x1c\\u3000\\x00//': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"runnvx ,","error":"invalid sandbox mount target 'runnvx ,': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"machine-idproc...,.\u0000\u3000","error":"invalid sandbox mount target 'machine-idproc...,.\\x00\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" \u0000\\procrun=\u0000\\","error":"invalid sandbox mount target ' \\x00\\\\procrun=\\x00\\\\': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"nvx..,proc/shares,run","error":"invalid sandbox mount target 'nvx..,proc/shares,run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"nvx../nvxa..","error":"invalid sandbox mount target 'nvx../nvxa..': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u001cnvx","error":"invalid sandbox mount target '\\x1cnvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u0000nvx.","error":"invalid sandbox mount target '\\x00nvx.': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"machine-ida\u3000\u3000work\u3000proc\u00e9","error":"invalid sandbox mount target 'machine-ida\\u3000\\u3000work\\u3000proc\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"sharesmachine-id\u3000a\u00e9","error":"invalid sandbox mount target 'sharesmachine-id\\u3000a\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"nvxshares ..nvx","error":"invalid sandbox mount target 'nvxshares ..nvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u0000proc,run","error":"invalid sandbox mount target '\\x00proc,run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"nvxproc/run\u001crun","error":"invalid sandbox mount target 'nvxproc/run\\x1crun': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\\\\\u3000/\u00e9=/","error":"invalid sandbox mount target '\\\\\\\\\\u3000/\u00e9=/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"machine-id","error":"invalid sandbox mount target 'machine-id': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"proc/.\u0000work \u00e9a","error":"invalid sandbox mount target 'proc/.\\x00work \u00e9a': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/=shares","error":"invalid sandbox mount target '/=shares': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" \u001c","error":"invalid sandbox mount target ' \\x1c': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":",..\u00e9,work","error":"invalid sandbox mount target ',..\u00e9,work': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"machine-id\u001c","error":"invalid sandbox mount target 'machine-id\\x1c': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u001cetc=,run proc","error":"invalid sandbox mount target '\\x1cetc=,run proc': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u3000","error":"invalid sandbox mount target '\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"..\u0000\u3000nvx","error":"invalid sandbox mount target '..\\x00\\u3000nvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etcmachine-id.\u001c\u0000sharesnvxrun","error":"invalid sandbox mount target 'etcmachine-id.\\x1c\\x00sharesnvxrun': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etcnvx\\","error":"invalid sandbox mount target 'etcnvx\\\\': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\\\u0000\\etc.,","error":"invalid sandbox mount target '\\\\\\x00\\\\etc.,': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":".. shares../run","error":"invalid sandbox mount target '.. shares../run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"work","error":"invalid sandbox mount target 'work': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u001c\u0000\u001c","error":"invalid sandbox mount target '\\x1c\\x00\\x1c': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"","error":"invalid sandbox mount target '': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"proc","error":"invalid sandbox mount target 'proc': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" .","error":"invalid sandbox mount target ' .': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" ","error":"invalid sandbox mount target ' ': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/\u3000=\\machine-id\u00e9 a","error":"invalid sandbox mount target '/\\u3000=\\\\machine-id\u00e9 a': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"\u00e9\u001c\\.\\","error":"invalid sandbox mount target '\u00e9\\x1c\\\\.\\\\': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"shares\u3000,\u3000sharesrun","error":"invalid sandbox mount target 'shares\\u3000,\\u3000sharesrun': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"run..shares,\u00e9\u00e9","error":"invalid sandbox mount target 'run..shares,\u00e9\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etcetc\u0000work\\\u0000run","error":"invalid sandbox mount target 'etcetc\\x00work\\\\\\x00run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":" asharesnvxnvx","error":"invalid sandbox mount target ' asharesnvxnvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etc\u0000etcetc..\u0000anvx","error":"invalid sandbox mount target 'etc\\x00etcetc..\\x00anvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"etc","error":"invalid sandbox mount target 'etc': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/nvx/srv/\u00e9","error":null}, +{"target":"/\u00e9/nvx/srv","error":null}, +{"target":"/nvx/cache","error":null}, +{"target":"/\u00e9/run/\u00e9","error":null}, +{"target":"/\u00e9","error":null}, +{"target":"/srv","error":null}, +{"target":"/srv","error":null}, +{"target":"/nvx","error":null}, +{"target":"/nvx","error":null}, +{"target":"//work/\u00e9","error":"invalid sandbox mount target '//work/\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work/srv","error":null}, +{"target":"/run/,etc/data-1","error":"invalid sandbox mount target '/run/,etc/data-1': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/a","error":null}, +{"target":"/etc","error":"sandbox mount target /etc is reserved"}, +{"target":"/run/\u00e9","error":null}, +{"target":"/run","error":null}, +{"target":"/nvx/work","error":null}, +{"target":"/a/cache/etc","error":null}, +{"target":"/a/\u001c=","error":"invalid sandbox mount target '/a/\\x1c=': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/srv/run/nvx","error":null}, +{"target":"/work/srv/etc","error":null}, +{"target":"/etc/etc/cache","error":null}, +{"target":"/run","error":null}, +{"target":"/srv","error":null}, +{"target":"/srv/work/","error":"invalid sandbox mount target '/srv/work/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/\u00e9/a","error":null}, +{"target":"/data-1/srv","error":null}, +{"target":"/run/\u00e9","error":null}, +{"target":"/nvx/etc","error":null}, +{"target":"/","error":"invalid sandbox mount target '/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work/work","error":null}, +{"target":"/data-1/run","error":null}, +{"target":"/work/work/cache","error":null}, +{"target":"/work/run../data-1","error":null}, +{"target":"/\u00e9/cache","error":null}, +{"target":"/etc/run","error":null}, +{"target":"/run/nvx/\u00e9","error":null}, +{"target":"/run/a/nvx","error":null}, +{"target":"/data-1/srv/data-1","error":null}, +{"target":"/data-1","error":null}, +{"target":"/run/srv/a","error":null}, +{"target":"/nvx","error":null}, +{"target":"/etc","error":"sandbox mount target /etc is reserved"}, +{"target":"/data-1","error":null}, +{"target":"/srv","error":null}, +{"target":"/cache","error":null}, +{"target":"/run/srv","error":null}, +{"target":"/etc/etc","error":null}, +{"target":"/work/work","error":null}, +{"target":"/work/etc/\u3000\u001cmachine-id","error":"invalid sandbox mount target '/work/etc/\\u3000\\x1cmachine-id': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/a/\u00e9/\u00e9","error":null}, +{"target":"/etc/a/work","error":null}, +{"target":"/etc/cache/data-1","error":null}, +{"target":"/\u00e9","error":null}, +{"target":"/nvx","error":null}, +{"target":"/data-1/data-1/work","error":null}, +{"target":"/\u3000","error":"invalid sandbox mount target '/\\u3000': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work/nvx/a","error":null}, +{"target":"/a/run/data-1","error":null}, +{"target":"/run/\u00e9/srv","error":null}, +{"target":"/\u00e9/nvx","error":null}, +{"target":"/run","error":null}, +{"target":"/srv/run","error":null}, +{"target":"/run","error":null}, +{"target":"/nvx","error":null}, +{"target":"/cache","error":null}, +{"target":"/data-1","error":null}, +{"target":"/work/srv","error":null}, +{"target":"/proc","error":"sandbox mount target /proc overlaps the reserved /proc tree"}, +{"target":"/cache/a","error":null}, +{"target":"/cache","error":null}, +{"target":"/nvx/srv/work","error":null}, +{"target":"/etc/run","error":null}, +{"target":"/nvx/\u00e9","error":null}, +{"target":"/\u00e9","error":null}, +{"target":"/work","error":null}, +{"target":"/srv","error":null}, +{"target":"/etc//data-1","error":"invalid sandbox mount target '/etc//data-1': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/srv/a/\u00e9","error":null}, +{"target":"/etc/nvx","error":null}, +{"target":"/run/\u00e9","error":null}, +{"target":"/run/srv","error":null}, +{"target":"/run","error":null}, +{"target":"/run","error":null}, +{"target":"/srv","error":null}, +{"target":"/nvx/work","error":null}, +{"target":"/a/data-1","error":null}, +{"target":"/data-1/run/data-1","error":null}, +{"target":"/nvx/data-1/nvx","error":null}, +{"target":"//work/run","error":"invalid sandbox mount target '//work/run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"target":"/work/nvx/nvx","error":null}, +{"target":"/etc/work/nvx","error":null}, +{"target":"/etc/srv","error":null}, +{"target":"/srv/etc","error":null}, +{"target":"/srv/\u00e9","error":null}, +{"target":"/work","error":null}, +{"target":"/nvx/work/a","error":null}, +{"target":"/work","error":null}, +{"target":"/work/etc","error":null}, +{"target":"/data-1/work","error":null} +], +"child_names": [ +{"index":0,"target":"/workspace","name":"0-c52ddf65534b7b46035084358ab7902b"}, +{"index":1,"target":"/workspace","name":"1-c52ddf65534b7b46035084358ab7902b"}, +{"index":999,"target":"/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","name":"999-cc75cd4428893a920419e41a6f2a00e4"}, +{"index":36,"target":"\u0000,machine-id proc","name":"36-a838dea7fcd4b1001d75151adcb4f1ce"}, +{"index":38,"target":"\u00e9..\u3000\\\u00e9/..","name":"38-13c26a98529e421f416d9d0f4a55805e"}, +{"index":6,"target":"runrunnvxwork ","name":"6-012351eb5c89460f34c10da2d10b5613"}, +{"index":43,"target":" procrun","name":"43-56d00e23134b6d56074b18c361b20dda"}, +{"index":3,"target":"etc\u3000..\u0000..","name":"3-65e3d59d391216b6183b1f837872181c"}, +{"index":50,"target":"machine-idprocnvx \u0000..work","name":"50-7f35b4dd127dafb8c3adc0e244cbfc14"}, +{"index":3,"target":" /amachine-idwork ,nvx","name":"3-b099591d4d744194452f205d859b4fe9"}, +{"index":32,"target":"\u0000.\u0000\u001c","name":"32-5017c11c8eb44e6ee24caff902a0ba67"}, +{"index":16,"target":"aprocrun/ ","name":"16-048031d8dc860fc54d825b7fb48a964a"}, +{"index":31,"target":"\u00e9","name":"31-4a99557e4033c3539de2eb65472017ca"}, +{"index":38,"target":"sharesmachine-id\u00e9\u0000\u0000proc=","name":"38-e400653deaa66bbe8a3280932ad0db6a"}, +{"index":23,"target":"work/\u001ca","name":"23-6c082eefd36340c75c073da5e38c85f5"}, +{"index":16,"target":"etcshares","name":"16-d9aebd32d9b4a90f878f508626e14474"}, +{"index":37,"target":"nvxrun\u00e9/,\u00e9\\","name":"37-da36198ba86793cf1a8fafff5574cce6"}, +{"index":30,"target":"\u001c/\u00e9\u3000shares","name":"30-dd5d4d060454a9b28d3ccb5c91c26082"}, +{"index":48,"target":"\u001c","name":"48-bd4fc42a21f1f860a1030e6eba23d53e"}, +{"index":39,"target":".....a..\\","name":"39-e125453adf94181f3bc359a7d93652ff"}, +{"index":41,"target":"runetcproc..\u3000.\\","name":"41-bf67bfc1b5e7e84572f14d2dd338378c"}, +{"index":13,"target":"\u00e9\\a ","name":"13-db7d12da777d5e1ea2ca24f934c9f38b"}, +{"index":30,"target":"\u00e9sharesproc\u00e9","name":"30-a262c664c07597843b776e35445f40e4"}, +{"index":44,"target":"a\u3000..proc","name":"44-790c04e63985d94f1364a5618f66f880"}, +{"index":24,"target":".\\=\u001c","name":"24-62e2d4b9f332d7aee89decb92dd30ba1"}, +{"index":32,"target":".etcnvx\u00e9\\\u0000","name":"32-6f6bdd93e820d5e3c0f3a7a8182063f1"}, +{"index":7,"target":"work","name":"7-00e13ed7af55b27622f1d6eab5bec014"}, +{"index":24,"target":",..run \u3000,..proc","name":"24-4b355337f01c4b8ea483431c32e9a3d0"}, +{"index":20,"target":"/","name":"20-8a5edab282632443219e051e4ade2d1d"}, +{"index":33,"target":" machine-id\\machine-id","name":"33-5a42917d15923698ee808a82626e8c31"}, +{"index":41,"target":".","name":"41-cdb4ee2aea69cc6a83331bbe96dc2caa"}, +{"index":9,"target":"\u001c..=","name":"9-081ce382fa80ab8fd738aa4144d536cc"}, +{"index":44,"target":"\u00e9work\u0000nvx=work\\","name":"44-465ea7ff674e5d2f57c6a4e0642f06fc"}, +{"index":36,"target":"\\proc..proc\u3000 ","name":"36-562834b582a54a4a6140a1407d7a2dc6"}, +{"index":27,"target":"machine-idsharesmachine-id.sharesrun","name":"27-539b8053a9211f7575a9180e4cb656ae"}, +{"index":30,"target":"nvx\u00e9/.","name":"30-00a51cad797dc02ae8799c4b38afdbab"}, +{"index":40,"target":"etc","name":"40-812de6e718f869feb16b45c6bbcfdb12"}, +{"index":8,"target":"=..run=proc","name":"8-947452e5e75a653fc6f8379c8da140d8"}, +{"index":42,"target":"work..machine-id\\a..,","name":"42-6c1fa9d46082b15f3161fe3fbcf5696b"}, +{"index":49,"target":"=.\u0000","name":"49-0b799fc0366e68b0fe573c69ec829bc5"}, +{"index":26,"target":"shares/work","name":"26-552a31c86bda15bf23d5a836ca2626b5"}, +{"index":7,"target":"=nvxnvx\u00e9","name":"7-004790cb1b6b3f790a8d739f45bd8331"}, +{"index":47,"target":"=\u3000\u3000","name":"47-c5273a1453d7bd93408d21a201af377f"}, +{"index":39,"target":"/\u0000\u3000proc/\u00e9","name":"39-c562e50aa269bbacb287e3a2d4efa82a"}, +{"index":33,"target":"nvxrun=ashares,..run","name":"33-d2104fab3f53feafc75587df245debe1"}, +{"index":8,"target":"workrunprocetc","name":"8-ee964ad063b2c879c80b78d3a52dd198"}, +{"index":37,"target":"etc\u00e9,etcwork=,machine-id","name":"37-b426aa229016764fef4eb415e7bae746"}, +{"index":17,"target":",","name":"17-d03502c43d74a30b936740a9517dc4ea"}, +{"index":45,"target":"a,.","name":"45-5b17b875efaf4bd0e46540d09eb4d9f7"}, +{"index":49,"target":"nvx.work","name":"49-190ca4e761d3a4238de8365ae7148985"}, +{"index":28,"target":"run","name":"28-acba25512100f80b56fc3ccd14c65be5"}, +{"index":30,"target":"etc.etcrun\u001cproc\u0000","name":"30-a1326f38320fd1f16228cb2ecf0a6216"}, +{"index":23,"target":"\u3000\u0000\u00e9aa","name":"23-8cdb4274efbdc4b6c1a83a7818d4c388"} +], +"mounts": [ +{"flavor":"posix","value":"/opt/hostedtoolcache,host-dir,rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/opt/hostedtoolcache","host_path":"host-dir","access":"rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/opt/hostedtoolcache,host-dir,rw","--mount-deny","logs","--mount-deny","secrets"],"fragment":" virtfs_dir=/opt/hostedtoolcache virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/opt/hostedtoolcache","host_path":"/fixture/cwd/host-dir","access":"rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/host-dir/logs","/fixture/cwd/host-dir/secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/tmp/mcp-runtime,host-dir,rw","denied":["mcp-logs"],"allowed":["mcp-logs/payloads"],"writable":["agent","cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/tmp/mcp-runtime","host_path":"host-dir","access":"rw","denied":["mcp-logs"],"allowed":["mcp-logs/payloads"],"writable":["agent","cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/tmp/mcp-runtime,host-dir,rw","--mount-deny","mcp-logs","--mount-allow","mcp-logs/payloads","--mount-write","agent","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/tmp/mcp-runtime virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/tmp/mcp-runtime","host_path":"/fixture/cwd/host-dir","access":"rw","denied":["mcp-logs"],"allowed":["mcp-logs/payloads"],"writable":["agent","cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/host-dir/mcp-logs"]],["allowed",["/fixture/cwd/host-dir/mcp-logs/payloads"]],["writable",["/fixture/cwd/host-dir/agent","/fixture/cwd/host-dir/cache"]]]}, +{"flavor":"posix","value":"/w,host,ro","denied":[],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory","error":"--mount-write narrows the writes of a read-write share; --mount /w is read-only"}, +{"flavor":"posix","value":"/w,host,rw","denied":[],"allowed":["path-0","path-1","path-2","path-3","path-4","path-5","path-6","path-7","path-8","path-9","path-10","path-11","path-12","path-13","path-14","path-15","path-16","path-17","path-18","path-19","path-20","path-21","path-22","path-23","path-24","path-25","path-26","path-27","path-28","path-29","path-30","path-31","path-32","path-33","path-34","path-35","path-36","path-37","path-38","path-39","path-40","path-41","path-42","path-43","path-44","path-45","path-46","path-47","path-48","path-49","path-50","path-51","path-52","path-53","path-54","path-55","path-56","path-57","path-58","path-59","path-60","path-61","path-62","path-63","path-64","path-65","path-66","path-67","path-68","path-69","path-70","path-71","path-72","path-73","path-74","path-75","path-76","path-77","path-78","path-79","path-80","path-81","path-82","path-83","path-84","path-85","path-86","path-87","path-88","path-89","path-90","path-91","path-92","path-93","path-94","path-95","path-96","path-97","path-98","path-99","path-100","path-101","path-102","path-103","path-104","path-105","path-106","path-107","path-108","path-109","path-110","path-111","path-112","path-113","path-114","path-115","path-116","path-117","path-118","path-119","path-120","path-121","path-122","path-123","path-124","path-125","path-126","path-127","path-128"],"writable":[],"owner":"vmm","kind":"directory","error":"a sandbox mount permits at most 128 allowed paths"}, +{"flavor":"posix","value":"/etc/app/settings.json,settings.json,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/etc/app/settings.json","host_path":"settings.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-3ddd35a70a0d7386a5451cd0d5dc3c39,settings.json,ro,file"],"fragment":" virtfs_dir=/etc/app/settings.json virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/etc/app/settings.json","host_path":"/fixture/cwd/settings.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/a,C:\\data\\x,y,rx","denied":["C:\\abs\\out","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/nvx,tools/cache,rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/nvx","host_path":"tools/cache","access":"rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/nvx,tools/cache,rw","--mount-deny","logs","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-write","/abs/out","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx","host_path":"/fixture/cwd/tools/cache","access":"rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/tools/cache/logs","/fixture/cwd/tools/cache/secrets"]],["allowed",["/fixture/cwd/tools/cache/logs/payloads"]],["writable",["/abs/out","/fixture/cwd/tools/cache/cache"]]]}, +{"flavor":"posix","value":"/run/a,w//x/.,ro","denied":["secrets","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/a","host_path":"w/x","access":"ro","denied":["secrets","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/a,w/x,ro","--mount-deny","secrets","--mount-deny","/abs/out"],"fragment":" virtfs_dir=/run/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/a","host_path":"/fixture/cwd/w/x","access":"ro","denied":["secrets","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/w/x/secrets","/abs/out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/run,tools/cache,rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/run","host_path":"tools/cache","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/run,tools/cache,rw","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads","--mount-write","/abs/out","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/run virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run","host_path":"/fixture/cwd/tools/cache","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/tools/cache/C:\\abs\\out"]],["allowed",["/fixture/cwd/tools/cache/logs/payloads"]],["writable",["/abs/out","/fixture/cwd/tools/cache/cache"]]]}, +{"flavor":"posix","value":"/data-1/data-1/cache,C:\\data\\x,y,rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/data-1/data-1/cache","host_path":"C:\\data\\x,y","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/data-1/data-1/cache,C:\\data\\x,y,rw","--mount-deny","/abs/out","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/data-1/data-1/cache virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/data-1/data-1/cache","host_path":"/fixture/cwd/C:\\data\\x,y","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/abs/out","/fixture/cwd/C:\\data\\x,y/secrets"]],["allowed",["/fixture/cwd/C:\\data\\x,y/logs/payloads"]],["writable",["/fixture/cwd/C:\\data\\x,y/cache"]]]}, +{"flavor":"posix","value":"/nvx/\u00e9,a/../b,rw","denied":[],"allowed":[],"writable":["out","cache"],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a parent component: a/../b"}, +{"flavor":"posix","value":"/work,?//a,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work","host_path":"?/a","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work,?/a,rw"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work","host_path":"/fixture/cwd/?/a","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/nvx/data-1/a,a/../b","denied":["logs","C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a parent component: a/../b"}, +{"flavor":"posix","value":"/a,C:/w,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/a","host_path":"C:/w","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-6a50dc8584134c7de537c0052ff6d236,C:/w,ro,file","--mount-owner","caller"],"fragment":" virtfs_dir=/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a","host_path":"/fixture/cwd/C:/w","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/work/run,C:/w","denied":["/abs/out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/run","host_path":"C:/w","access":"ro","denied":["/abs/out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/run,C:/w,ro","--mount-deny","/abs/out","--mount-deny","logs"],"fragment":" virtfs_dir=/work/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work/run","host_path":"/fixture/cwd/C:/w","access":"ro","denied":["/abs/out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/abs/out","/fixture/cwd/C:/w/logs"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/etc/work/data-1,x,y\u0000x,y,ro","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount host path contains a NUL character: 'x,y\\x00x,y'"}, +{"flavor":"posix","value":"/srv,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv,C:\\data\\x,y,ro","--mount-owner","caller"],"fragment":" virtfs_dir=/srv virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/srv","host_path":"/fixture/cwd/C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/a b,a\u0000b,ro","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/nvx/cache,C:/w,rx","denied":["secrets","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/work/a,/srv/data,rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/work/a","host_path":"/srv/data","access":"rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/work/a,/srv/data,rw","--mount-deny","C:\\abs\\out","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/work/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work/a","host_path":"/srv/data","access":"rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/srv/data/C:\\abs\\out","/srv/data/secrets"]],["allowed",["/srv/data/logs/payloads"]],["writable",["/srv/data/cache"]]]}, +{"flavor":"posix","value":"/,C:\\data\\x,y,ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/work/a,C:\\data\\x,y,rw","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":["cache"],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/a","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":["cache"],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/a,C:\\data\\x,y,rw","--mount-deny","secrets","--mount-deny","C:\\abs\\out","--mount-write","cache"],"fragment":" virtfs_dir=/work/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work/a","host_path":"/fixture/cwd/C:\\data\\x,y","access":"rw","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":["cache"],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:\\data\\x,y/secrets","/fixture/cwd/C:\\data\\x,y/C:\\abs\\out"]],["allowed",[]],["writable",["/fixture/cwd/C:\\data\\x,y/cache"]]]}, +{"flavor":"posix","value":"/nvx/nvx,work,ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/nvx/nvx","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/nvx/nvx,work,ro","--mount-deny","logs","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/nvx/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx/nvx","host_path":"/fixture/cwd/work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/work/logs","/fixture/cwd/work/secrets"]],["allowed",["/fixture/cwd/work/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/work,w//x/.,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/work","host_path":"w/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-0c9a453fad615c83c57d57c2b2be05fa,w/x,ro,file","--mount-owner","caller"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work","host_path":"/fixture/cwd/w/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/\u00e9/srv,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/\u00e9/srv","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-b52d86793a80d4b693be52d859a1fd8a,C:\\data\\x,y,ro,file"],"fragment":" virtfs_dir=/\u00e9/srv virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9/srv","host_path":"/fixture/cwd/C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,w//x/.","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/a,C:/w,rw","denied":["/abs/out"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory","mount":{"guest_target":"/a","host_path":"C:/w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},"arguments":["--mount","/a,C:/w,rw","--mount-deny","/abs/out","--mount-write","out","--mount-owner","caller"],"fragment":" virtfs_dir=/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/a","host_path":"/fixture/cwd/C:/w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/abs/out"]],["allowed",[]],["writable",["/fixture/cwd/C:/w/out"]]]}, +{"flavor":"posix","value":"/a/run,C:/w","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/a/run","host_path":"C:/w","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/a/run,C:/w,ro","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/a/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a/run","host_path":"/fixture/cwd/C:/w","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:/w/C:\\abs\\out"]],["allowed",["/fixture/cwd/C:/w/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,C:/w,ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/etc,w//x/.,rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/cache/etc/\u00e9,C:\\data\\x,y,rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/cache/etc/\u00e9","host_path":"C:\\data\\x,y","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-3d35e19069b9b96c28742438e4299b52,C:\\data\\x,y,rw,file","--mount-owner","caller"],"fragment":" virtfs_dir=/cache/etc/\u00e9 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/cache/etc/\u00e9","host_path":"/fixture/cwd/C:\\data\\x,y","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/run/shares\u3000work/nvx,C:/w,rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/run/shares\\u3000work/nvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/run/\\.\u001c/\u00e9,/srv/data,rw","denied":[],"allowed":[],"writable":["cache","/abs/out"],"owner":"caller","kind":"file","error":"invalid sandbox mount target '/run/\\\\.\\x1c/\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/data-1/cache/\u00e9,C:\\data\\x,y","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/\u00e9/cache/a,C:\\data\\x,y,rw","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/\u00e9/cache/a","host_path":"C:\\data\\x,y","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/\u00e9/cache/a,C:\\data\\x,y,rw","--mount-deny","/abs/out","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/\u00e9/cache/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/\u00e9/cache/a","host_path":"/fixture/cwd/C:\\data\\x,y","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/abs/out"]],["allowed",["/fixture/cwd/C:\\data\\x,y/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,C:/w,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/\u00e9/cache,/srv/data,rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/\u00e9/cache","host_path":"/srv/data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-93735fe6b8037c02e2ca771a5eb0fbe7,/srv/data,rw,file","--mount-owner","caller"],"fragment":" virtfs_dir=/\u00e9/cache virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/\u00e9/cache","host_path":"/srv/data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/machine-id\u001ca/etc/\u00e9,C:/w","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"file","error":"invalid sandbox mount target '/machine-id\\x1ca/etc/\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/etc/etc,w//x/.,rw","denied":["/abs/out"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/etc","host_path":"w/x","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/etc,w/x,rw","--mount-deny","/abs/out","--mount-allow","logs/payloads","--mount-allow","logs"],"fragment":" virtfs_dir=/etc/etc virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/etc/etc","host_path":"/fixture/cwd/w/x","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/abs/out"]],["allowed",["/fixture/cwd/w/x/logs/payloads","/fixture/cwd/w/x/logs"]],["writable",[]]]}, +{"flavor":"posix","value":"/nvx/work/data-1,C:/w,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/nvx/work/data-1","host_path":"C:/w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-f70c1ff3e0a8554d424014d45d31d57d,C:/w,rw,file"],"fragment":" virtfs_dir=/nvx/work/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx/work/data-1","host_path":"/fixture/cwd/C:/w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/work,C:/w","denied":[],"allowed":["logs"],"writable":[],"owner":"vmm","kind":"directory","error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"posix","value":"/run/nvx/cache,tools/cache","denied":["/abs/out","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/run/nvx/cache","host_path":"tools/cache","access":"ro","denied":["/abs/out","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/run/nvx/cache,tools/cache,ro","--mount-deny","/abs/out","--mount-deny","logs","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/run/nvx/cache virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/nvx/cache","host_path":"/fixture/cwd/tools/cache","access":"ro","denied":["/abs/out","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/abs/out","/fixture/cwd/tools/cache/logs"]],["allowed",["/fixture/cwd/tools/cache/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/work,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work,C:\\data\\x,y,ro"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work","host_path":"/fixture/cwd/C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/\u00e9/data-1/\u00e9,C:/w,ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/\u00e9/data-1/\u00e9","host_path":"C:/w","access":"ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/\u00e9/data-1/\u00e9,C:/w,ro","--mount-deny","logs","--mount-owner","caller"],"fragment":" virtfs_dir=/\u00e9/data-1/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9/data-1/\u00e9","host_path":"/fixture/cwd/C:/w","access":"ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:/w/logs"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,/srv/data,ro","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/srv/srv,C:/w,rw","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/srv","host_path":"C:/w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/srv,C:/w,rw","--mount-deny","/abs/out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/srv virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/srv","host_path":"/fixture/cwd/C:/w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/abs/out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,/srv/data,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/etc/\u00e9/\u00e9,tools/cache","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/\u00e9/\u00e9","host_path":"tools/cache","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/\u00e9/\u00e9,tools/cache,ro","--mount-deny","logs","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/etc/\u00e9/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/etc/\u00e9/\u00e9","host_path":"/fixture/cwd/tools/cache","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/tools/cache/logs"]],["allowed",["/fixture/cwd/tools/cache/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/etc/data-1/nvx,w//x/.","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/data-1/nvx","host_path":"w/x","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/data-1/nvx,w/x,ro","--mount-deny","secrets"],"fragment":" virtfs_dir=/etc/data-1/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/etc/data-1/nvx","host_path":"/fixture/cwd/w/x","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/w/x/secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/a/\u00e9/srv,tools/cache","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/a/\u00e9/srv","host_path":"tools/cache","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/a/\u00e9/srv,tools/cache,ro","--mount-deny","C:\\abs\\out","--mount-deny","/abs/out","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/a/\u00e9/srv virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a/\u00e9/srv","host_path":"/fixture/cwd/tools/cache","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/tools/cache/C:\\abs\\out","/abs/out"]],["allowed",["/fixture/cwd/tools/cache/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/run/a,tools/cache,rw","denied":[],"allowed":[],"writable":["cache","out","."],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/a","host_path":"tools/cache","access":"rw","denied":[],"allowed":[],"writable":["cache","out","."],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/a,tools/cache,rw","--mount-write","cache","--mount-write","out","--mount-write","."],"fragment":" virtfs_dir=/run/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run/a","host_path":"/fixture/cwd/tools/cache","access":"rw","denied":[],"allowed":[],"writable":["cache","out","."],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",["/fixture/cwd/tools/cache/cache","/fixture/cwd/tools/cache/out","/fixture/cwd/tools/cache"]]]}, +{"flavor":"posix","value":"/nvx,C:\\data\\x,y,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/nvx","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/nvx,C:\\data\\x,y,rw","--mount-deny","secrets"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx","host_path":"/fixture/cwd/C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:\\data\\x,y/secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/a b,a/../b,rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/nvx,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/nvx","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-30b37e7ae096735b949f4f464412ab3c,C:\\data\\x,y,ro,file"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx","host_path":"/fixture/cwd/C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/\u00e9,C:/w,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/\u00e9","host_path":"C:/w","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/\u00e9,C:/w,ro","--mount-deny","C:\\abs\\out"],"fragment":" virtfs_dir=/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9","host_path":"/fixture/cwd/C:/w","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:/w/C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/nvx/a/work,C:\\data\\x,y","denied":["a\u0000"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/etc,C:\\data\\x,y,ro","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/work/\u3000\u001cwork,w//x/.,rw","denied":["logs"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/work/\\u3000\\x1cwork': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/run/\u00e9,w//x/.,ro","denied":["secrets"],"allowed":["out"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/\u00e9","host_path":"w/x","access":"ro","denied":["secrets"],"allowed":["out"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/\u00e9,w/x,ro","--mount-deny","secrets","--mount-allow","out"],"fragment":" virtfs_dir=/run/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/\u00e9","host_path":"/fixture/cwd/w/x","access":"ro","denied":["secrets"],"allowed":["out"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/w/x/secrets"]],["allowed",["/fixture/cwd/w/x/out"]],["writable",[]]]}, +{"flavor":"posix","value":"/cache/run,C:\\data\\x,y,rx","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/data-1/data-1,work","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/data-1/data-1","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-fd1bb3d6d1bce0afe815991bf83f5962,work,ro,file"],"fragment":" virtfs_dir=/data-1/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/data-1","host_path":"/fixture/cwd/work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/run/srv/data-1,tools/cache","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/run/srv/data-1","host_path":"tools/cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-a8f01611937352bf22a395a8f556cae1,tools/cache,ro,file","--mount-owner","caller"],"fragment":" virtfs_dir=/run/srv/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/srv/data-1","host_path":"/fixture/cwd/tools/cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/data-1/data-1/work,tools/cache,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/data-1/data-1/work","host_path":"tools/cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-308e5770e516a27164f075a92cce7fdb,tools/cache,ro,file"],"fragment":" virtfs_dir=/data-1/data-1/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/data-1/work","host_path":"/fixture/cwd/tools/cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/a b,/srv/data","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/nvx,/srv/data,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/nvx","host_path":"/srv/data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-30b37e7ae096735b949f4f464412ab3c,/srv/data,rw,file"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx","host_path":"/srv/data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/,runshares/run,C:\\data\\x,y","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/srv/a,tools/cache,rx","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/a,a\u0000b,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a NUL character: 'a\\x00b'"}, +{"flavor":"posix","value":"/data-1/work,w//x/.","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/data-1/work","host_path":"w/x","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/data-1/work,w/x,ro","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/data-1/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/work","host_path":"/fixture/cwd/w/x","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/w/x/C:\\abs\\out"]],["allowed",["/fixture/cwd/w/x/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/etc/a/a,w//x/.","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"file","error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /etc/a/a shares a file, whose mode applies to the whole file"}, +{"flavor":"posix","value":"/\u00e9/etc,w//x/.","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/\u00e9/etc","host_path":"w/x","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/\u00e9/etc,w/x,ro","--mount-deny","C:\\abs\\out","--mount-deny","/abs/out","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/\u00e9/etc virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9/etc","host_path":"/fixture/cwd/w/x","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/w/x/C:\\abs\\out","/abs/out"]],["allowed",["/fixture/cwd/w/x/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,tools/cache","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/nvx/data-1,C:\\data\\x,y,ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/nvx/data-1","host_path":"C:\\data\\x,y","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/nvx/data-1,C:\\data\\x,y,ro","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/nvx/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx/data-1","host_path":"/fixture/cwd/C:\\data\\x,y","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:\\data\\x,y/secrets"]],["allowed",["/fixture/cwd/C:\\data\\x,y/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,tools/cache","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/a b,/srv/data,rw","denied":["secrets","/abs/out"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/run/data-1/etc,tools/cache,rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out","/abs/out"],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/data-1/etc","host_path":"tools/cache","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out","/abs/out"],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/data-1/etc,tools/cache,rw","--mount-deny","logs","--mount-allow","logs/payloads","--mount-write","out","--mount-write","/abs/out"],"fragment":" virtfs_dir=/run/data-1/etc virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run/data-1/etc","host_path":"/fixture/cwd/tools/cache","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out","/abs/out"],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/tools/cache/logs"]],["allowed",["/fixture/cwd/tools/cache/logs/payloads"]],["writable",["/fixture/cwd/tools/cache/out","/abs/out"]]]}, +{"flavor":"posix","value":"/nvx,/srv/data,ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/nvx","host_path":"/srv/data","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/nvx,/srv/data,ro","--mount-deny","/abs/out"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx","host_path":"/srv/data","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/abs/out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/a b,tools/cache,rw","denied":["secrets","/abs/out","a\u0000"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/run/work,tools/cache,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/work","host_path":"tools/cache","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/work,tools/cache,ro","--mount-deny","C:\\abs\\out"],"fragment":" virtfs_dir=/run/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/work","host_path":"/fixture/cwd/tools/cache","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/tools/cache/C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"//srv/run,/srv/data","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '//srv/run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/srv/srv,a/../b,rw","denied":["secrets","C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a parent component: a/../b"}, +{"flavor":"posix","value":"/work,w//x/.,rw","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/work","host_path":"w/x","access":"rw","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/work,w/x,rw","--mount-deny","logs","--mount-deny","/abs/out","--mount-owner","caller"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work","host_path":"/fixture/cwd/w/x","access":"rw","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/w/x/logs","/abs/out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/a,work,rw","denied":["/abs/out","."],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory","mount":{"guest_target":"/a","host_path":"work","access":"rw","denied":["/abs/out","."],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"},"arguments":["--mount","/a,work,rw","--mount-deny","/abs/out","--mount-deny",".","--mount-allow","logs/payloads","--mount-write","out","--mount-owner","caller"],"fragment":" virtfs_dir=/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/a","host_path":"/fixture/cwd/work","access":"rw","denied":["/abs/out","."],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/abs/out","/fixture/cwd/work"]],["allowed",["/fixture/cwd/work/logs/payloads"]],["writable",["/fixture/cwd/work/out"]]]}, +{"flavor":"posix","value":"/etc/data-1,w//x/.,rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/data-1","host_path":"w/x","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/data-1,w/x,rw","--mount-deny","/abs/out","--mount-deny","secrets","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/etc/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/etc/data-1","host_path":"/fixture/cwd/w/x","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/abs/out","/fixture/cwd/w/x/secrets"]],["allowed",["/fixture/cwd/w/x/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/srv,w//x/.,rx","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/\u00e9,/srv/data,rx","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"posix","value":"/srv/data-1/\u00e9,C:\\data\\x,y,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/data-1/\u00e9","host_path":"C:\\data\\x,y","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/data-1/\u00e9,C:\\data\\x,y,ro","--mount-deny","C:\\abs\\out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/data-1/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/srv/data-1/\u00e9","host_path":"/fixture/cwd/C:\\data\\x,y","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:\\data\\x,y/C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/proc,a\u0000b,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","error":"sandbox mount target /proc overlaps the reserved /proc tree"}, +{"flavor":"posix","value":"/run,/srv/data,ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run","host_path":"/srv/data","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run,/srv/data,ro","--mount-deny","logs","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run","host_path":"/srv/data","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/srv/data/logs"]],["allowed",["/srv/data/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/a/work,w//x/.,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/a/work","host_path":"w/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/a/work,w/x,ro","--mount-owner","caller"],"fragment":" virtfs_dir=/a/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a/work","host_path":"/fixture/cwd/w/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/etc,tools/cache,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"posix","value":"/nvx/a/a,/srv/data","denied":["/abs/out","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/nvx/a/a","host_path":"/srv/data","access":"ro","denied":["/abs/out","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/nvx/a/a,/srv/data,ro","--mount-deny","/abs/out","--mount-deny","secrets"],"fragment":" virtfs_dir=/nvx/a/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx/a/a","host_path":"/srv/data","access":"ro","denied":["/abs/out","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/abs/out","/srv/data/secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/data-1/a,/srv/data","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/data-1/a","host_path":"/srv/data","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-4b25c272fa19ed009dce984c919893ec,/srv/data,ro,file"],"fragment":" virtfs_dir=/data-1/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/a","host_path":"/srv/data","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/nvxrun,/srv/data,rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/nvxrun","host_path":"/srv/data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-6ab51ef8dc55669bf1c3c27372b1f413,/srv/data,rw,file","--mount-owner","caller"],"fragment":" virtfs_dir=/nvxrun virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvxrun","host_path":"/srv/data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/,/srv,tools/cache,rw","denied":[],"allowed":[],"writable":["/abs/out","cache"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"posix","value":"/run/data-1,C:\\data\\x,y,rw","denied":["secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/data-1","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/data-1,C:\\data\\x,y,rw","--mount-deny","secrets","--mount-write","out"],"fragment":" virtfs_dir=/run/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run/data-1","host_path":"/fixture/cwd/C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:\\data\\x,y/secrets"]],["allowed",[]],["writable",["/fixture/cwd/C:\\data\\x,y/out"]]]}, +{"flavor":"posix","value":"/nvx,/srv/data,rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["a\u0000"],"owner":"caller","kind":"directory","error":"sandbox mount writable paths must be nonempty"}, +{"flavor":"posix","value":"/work/data-1,C:\\data\\x,y,rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/data-1","host_path":"C:\\data\\x,y","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/data-1,C:\\data\\x,y,rw","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/work/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work/data-1","host_path":"/fixture/cwd/C:\\data\\x,y","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:\\data\\x,y/C:\\abs\\out"]],["allowed",["/fixture/cwd/C:\\data\\x,y/logs/payloads"]],["writable",[]]]}, +{"flavor":"posix","value":"/srv/a/run,w//x/.,ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/a/run","host_path":"w/x","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/a/run,w/x,ro","--mount-deny","/abs/out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/a/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/srv/a/run","host_path":"/fixture/cwd/w/x","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/abs/out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/srv/work,C:/w,rw","denied":["C:\\abs\\out"],"allowed":[],"writable":["cache","out"],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/work","host_path":"C:/w","access":"rw","denied":["C:\\abs\\out"],"allowed":[],"writable":["cache","out"],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/work,C:/w,rw","--mount-deny","C:\\abs\\out","--mount-write","cache","--mount-write","out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/work virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/work","host_path":"/fixture/cwd/C:/w","access":"rw","denied":["C:\\abs\\out"],"allowed":[],"writable":["cache","out"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:/w/C:\\abs\\out"]],["allowed",[]],["writable",["/fixture/cwd/C:/w/cache","/fixture/cwd/C:/w/out"]]]}, +{"flavor":"posix","value":"/data-1,C:/w,ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/data-1","host_path":"C:/w","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/data-1,C:/w,ro","--mount-deny","/abs/out"],"fragment":" virtfs_dir=/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1","host_path":"/fixture/cwd/C:/w","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["/abs/out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/srv/srv,w//x/.,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/srv/srv","host_path":"w/x","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/srv/srv,w/x,rw"],"fragment":" virtfs_dir=/srv/srv virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/srv","host_path":"/fixture/cwd/w/x","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/work/etc/run,w//x/.","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/etc/run","host_path":"w/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/etc/run,w/x,ro"],"fragment":" virtfs_dir=/work/etc/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work/etc/run","host_path":"/fixture/cwd/w/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/procmachine-id,C:/w,rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/procmachine-id","host_path":"C:/w","access":"rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/procmachine-id,C:/w,rw","--mount-deny","C:\\abs\\out","--mount-deny","logs","--mount-owner","caller"],"fragment":" virtfs_dir=/procmachine-id virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/procmachine-id","host_path":"/fixture/cwd/C:/w","access":"rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["/fixture/cwd/C:/w/C:\\abs\\out","/fixture/cwd/C:/w/logs"]],["allowed",[]],["writable",[]]]}, +{"flavor":"posix","value":"/srv/nvx/srv,C:/w,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/srv/nvx/srv","host_path":"C:/w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/srv/nvx/srv,C:/w,rw"],"fragment":" virtfs_dir=/srv/nvx/srv virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/nvx/srv","host_path":"/fixture/cwd/C:/w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/opt/hostedtoolcache,host-dir,rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/opt/hostedtoolcache","host_path":"host-dir","access":"rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/opt/hostedtoolcache,host-dir,rw","--mount-deny","logs","--mount-deny","secrets"],"fragment":" virtfs_dir=/opt/hostedtoolcache virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/opt/hostedtoolcache","host_path":"C:\\fixture\\cwd\\host-dir","access":"rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\host-dir\\logs","C:\\fixture\\cwd\\host-dir\\secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/tmp/mcp-runtime,host-dir,rw","denied":["mcp-logs"],"allowed":["mcp-logs/payloads"],"writable":["agent","cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/tmp/mcp-runtime","host_path":"host-dir","access":"rw","denied":["mcp-logs"],"allowed":["mcp-logs/payloads"],"writable":["agent","cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/tmp/mcp-runtime,host-dir,rw","--mount-deny","mcp-logs","--mount-allow","mcp-logs/payloads","--mount-write","agent","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/tmp/mcp-runtime virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/tmp/mcp-runtime","host_path":"C:\\fixture\\cwd\\host-dir","access":"rw","denied":["mcp-logs"],"allowed":["mcp-logs/payloads"],"writable":["agent","cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\host-dir\\mcp-logs"]],["allowed",["C:\\fixture\\cwd\\host-dir\\mcp-logs\\payloads"]],["writable",["C:\\fixture\\cwd\\host-dir\\agent","C:\\fixture\\cwd\\host-dir\\cache"]]]}, +{"flavor":"windows","value":"/w,host,ro","denied":[],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory","error":"--mount-write narrows the writes of a read-write share; --mount /w is read-only"}, +{"flavor":"windows","value":"/w,host,rw","denied":[],"allowed":["path-0","path-1","path-2","path-3","path-4","path-5","path-6","path-7","path-8","path-9","path-10","path-11","path-12","path-13","path-14","path-15","path-16","path-17","path-18","path-19","path-20","path-21","path-22","path-23","path-24","path-25","path-26","path-27","path-28","path-29","path-30","path-31","path-32","path-33","path-34","path-35","path-36","path-37","path-38","path-39","path-40","path-41","path-42","path-43","path-44","path-45","path-46","path-47","path-48","path-49","path-50","path-51","path-52","path-53","path-54","path-55","path-56","path-57","path-58","path-59","path-60","path-61","path-62","path-63","path-64","path-65","path-66","path-67","path-68","path-69","path-70","path-71","path-72","path-73","path-74","path-75","path-76","path-77","path-78","path-79","path-80","path-81","path-82","path-83","path-84","path-85","path-86","path-87","path-88","path-89","path-90","path-91","path-92","path-93","path-94","path-95","path-96","path-97","path-98","path-99","path-100","path-101","path-102","path-103","path-104","path-105","path-106","path-107","path-108","path-109","path-110","path-111","path-112","path-113","path-114","path-115","path-116","path-117","path-118","path-119","path-120","path-121","path-122","path-123","path-124","path-125","path-126","path-127","path-128"],"writable":[],"owner":"vmm","kind":"directory","error":"a sandbox mount permits at most 128 allowed paths"}, +{"flavor":"windows","value":"/etc/app/settings.json,settings.json,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/etc/app/settings.json","host_path":"settings.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-3ddd35a70a0d7386a5451cd0d5dc3c39,settings.json,ro,file"],"fragment":" virtfs_dir=/etc/app/settings.json virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/etc/app/settings.json","host_path":"C:\\fixture\\cwd\\settings.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/a,C:\\data\\x,y,rx","denied":["C:\\abs\\out","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/nvx,tools/cache,rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/nvx","host_path":"tools\\cache","access":"rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/nvx,tools\\cache,rw","--mount-deny","logs","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-write","/abs/out","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\tools\\cache\\logs","C:\\fixture\\cwd\\tools\\cache\\secrets"]],["allowed",["C:\\fixture\\cwd\\tools\\cache\\logs\\payloads"]],["writable",["C:\\abs\\out","C:\\fixture\\cwd\\tools\\cache\\cache"]]]}, +{"flavor":"windows","value":"/run/a,w//x/.,ro","denied":["secrets","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/a","host_path":"w\\x","access":"ro","denied":["secrets","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/a,w\\x,ro","--mount-deny","secrets","--mount-deny","/abs/out"],"fragment":" virtfs_dir=/run/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/a","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":["secrets","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\w\\x\\secrets","C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/run,tools/cache,rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/run","host_path":"tools\\cache","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/run,tools\\cache,rw","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads","--mount-write","/abs/out","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/run virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["/abs/out","cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",["C:\\fixture\\cwd\\tools\\cache\\logs\\payloads"]],["writable",["C:\\abs\\out","C:\\fixture\\cwd\\tools\\cache\\cache"]]]}, +{"flavor":"windows","value":"/data-1/data-1/cache,C:\\data\\x,y,rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/data-1/data-1/cache","host_path":"C:\\data\\x,y","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/data-1/data-1/cache,C:\\data\\x,y,rw","--mount-deny","/abs/out","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/data-1/data-1/cache virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/data-1/data-1/cache","host_path":"C:\\data\\x,y","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\data\\x,y\\secrets"]],["allowed",["C:\\data\\x,y\\logs\\payloads"]],["writable",["C:\\data\\x,y\\cache"]]]}, +{"flavor":"windows","value":"/nvx/\u00e9,a/../b,rw","denied":[],"allowed":[],"writable":["out","cache"],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a parent component: a\\..\\b"}, +{"flavor":"windows","value":"/work,?//a,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work","host_path":"?\\a","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work,?\\a,rw"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work","host_path":"C:\\fixture\\cwd\\?\\a","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/nvx/data-1/a,a/../b","denied":["logs","C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a parent component: a\\..\\b"}, +{"flavor":"windows","value":"/a,C:/w,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/a","host_path":"C:\\w","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-6a50dc8584134c7de537c0052ff6d236,C:\\w,ro,file","--mount-owner","caller"],"fragment":" virtfs_dir=/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a","host_path":"C:\\w","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/work/run,C:/w","denied":["/abs/out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/run","host_path":"C:\\w","access":"ro","denied":["/abs/out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/run,C:\\w,ro","--mount-deny","/abs/out","--mount-deny","logs"],"fragment":" virtfs_dir=/work/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work/run","host_path":"C:\\w","access":"ro","denied":["/abs/out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\w\\logs"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/etc/work/data-1,x,y\u0000x,y,ro","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount host path contains a NUL character: 'x,y\\x00x,y'"}, +{"flavor":"windows","value":"/srv,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv,C:\\data\\x,y,ro","--mount-owner","caller"],"fragment":" virtfs_dir=/srv virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/srv","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/a b,a\u0000b,ro","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/nvx/cache,C:/w,rx","denied":["secrets","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/work/a,/srv/data,rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory","mount":{"guest_target":"/work/a","host_path":"\\srv\\data","access":"rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"arguments":["--mount","/work/a,\\srv\\data,rw","--mount-deny","C:\\abs\\out","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-write","cache","--mount-owner","caller"],"fragment":" virtfs_dir=/work/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work/a","host_path":"C:\\srv\\data","access":"rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\srv\\data\\secrets"]],["allowed",["C:\\srv\\data\\logs\\payloads"]],["writable",["C:\\srv\\data\\cache"]]]}, +{"flavor":"windows","value":"/,C:\\data\\x,y,ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/work/a,C:\\data\\x,y,rw","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":["cache"],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/a","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":["cache"],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/a,C:\\data\\x,y,rw","--mount-deny","secrets","--mount-deny","C:\\abs\\out","--mount-write","cache"],"fragment":" virtfs_dir=/work/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work/a","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":["cache"],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\data\\x,y\\secrets","C:\\abs\\out"]],["allowed",[]],["writable",["C:\\data\\x,y\\cache"]]]}, +{"flavor":"windows","value":"/nvx/nvx,work,ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/nvx/nvx","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/nvx/nvx,work,ro","--mount-deny","logs","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/nvx/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx/nvx","host_path":"C:\\fixture\\cwd\\work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\work\\logs","C:\\fixture\\cwd\\work\\secrets"]],["allowed",["C:\\fixture\\cwd\\work\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/work,w//x/.,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/work","host_path":"w\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-0c9a453fad615c83c57d57c2b2be05fa,w\\x,ro,file","--mount-owner","caller"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/\u00e9/srv,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/\u00e9/srv","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-b52d86793a80d4b693be52d859a1fd8a,C:\\data\\x,y,ro,file"],"fragment":" virtfs_dir=/\u00e9/srv virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9/srv","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,w//x/.","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/a,C:/w,rw","denied":["/abs/out"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory","mount":{"guest_target":"/a","host_path":"C:\\w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},"arguments":["--mount","/a,C:\\w,rw","--mount-deny","/abs/out","--mount-write","out","--mount-owner","caller"],"fragment":" virtfs_dir=/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/a","host_path":"C:\\w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",["C:\\w\\out"]]]}, +{"flavor":"windows","value":"/a/run,C:/w","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/a/run","host_path":"C:\\w","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/a/run,C:\\w,ro","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/a/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a/run","host_path":"C:\\w","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",["C:\\w\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,C:/w,ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/etc,w//x/.,rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["cache"],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/cache/etc/\u00e9,C:\\data\\x,y,rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/cache/etc/\u00e9","host_path":"C:\\data\\x,y","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-3d35e19069b9b96c28742438e4299b52,C:\\data\\x,y,rw,file","--mount-owner","caller"],"fragment":" virtfs_dir=/cache/etc/\u00e9 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/cache/etc/\u00e9","host_path":"C:\\data\\x,y","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/run/shares\u3000work/nvx,C:/w,rw","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/run/shares\\u3000work/nvx': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/run/\\.\u001c/\u00e9,/srv/data,rw","denied":[],"allowed":[],"writable":["cache","/abs/out"],"owner":"caller","kind":"file","error":"invalid sandbox mount target '/run/\\\\.\\x1c/\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/data-1/cache/\u00e9,C:\\data\\x,y","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/\u00e9/cache/a,C:\\data\\x,y,rw","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/\u00e9/cache/a","host_path":"C:\\data\\x,y","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/\u00e9/cache/a,C:\\data\\x,y,rw","--mount-deny","/abs/out","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/\u00e9/cache/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/\u00e9/cache/a","host_path":"C:\\data\\x,y","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",["C:\\data\\x,y\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,C:/w,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/\u00e9/cache,/srv/data,rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/\u00e9/cache","host_path":"\\srv\\data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-93735fe6b8037c02e2ca771a5eb0fbe7,\\srv\\data,rw,file","--mount-owner","caller"],"fragment":" virtfs_dir=/\u00e9/cache virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/\u00e9/cache","host_path":"C:\\srv\\data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/machine-id\u001ca/etc/\u00e9,C:/w","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"file","error":"invalid sandbox mount target '/machine-id\\x1ca/etc/\u00e9': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/etc/etc,w//x/.,rw","denied":["/abs/out"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/etc","host_path":"w\\x","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/etc,w\\x,rw","--mount-deny","/abs/out","--mount-allow","logs/payloads","--mount-allow","logs"],"fragment":" virtfs_dir=/etc/etc virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/etc/etc","host_path":"C:\\fixture\\cwd\\w\\x","access":"rw","denied":["/abs/out"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",["C:\\fixture\\cwd\\w\\x\\logs\\payloads","C:\\fixture\\cwd\\w\\x\\logs"]],["writable",[]]]}, +{"flavor":"windows","value":"/nvx/work/data-1,C:/w,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/nvx/work/data-1","host_path":"C:\\w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-f70c1ff3e0a8554d424014d45d31d57d,C:\\w,rw,file"],"fragment":" virtfs_dir=/nvx/work/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx/work/data-1","host_path":"C:\\w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/work,C:/w","denied":[],"allowed":["logs"],"writable":[],"owner":"vmm","kind":"directory","error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"windows","value":"/run/nvx/cache,tools/cache","denied":["/abs/out","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/run/nvx/cache","host_path":"tools\\cache","access":"ro","denied":["/abs/out","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/run/nvx/cache,tools\\cache,ro","--mount-deny","/abs/out","--mount-deny","logs","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/run/nvx/cache virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/nvx/cache","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"ro","denied":["/abs/out","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\fixture\\cwd\\tools\\cache\\logs"]],["allowed",["C:\\fixture\\cwd\\tools\\cache\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/work,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work,C:\\data\\x,y,ro"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/\u00e9/data-1/\u00e9,C:/w,ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/\u00e9/data-1/\u00e9","host_path":"C:\\w","access":"ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/\u00e9/data-1/\u00e9,C:\\w,ro","--mount-deny","logs","--mount-owner","caller"],"fragment":" virtfs_dir=/\u00e9/data-1/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9/data-1/\u00e9","host_path":"C:\\w","access":"ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\w\\logs"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,/srv/data,ro","denied":["secrets","C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/srv/srv,C:/w,rw","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/srv","host_path":"C:\\w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/srv,C:\\w,rw","--mount-deny","/abs/out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/srv virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/srv","host_path":"C:\\w","access":"rw","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,/srv/data,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/etc/\u00e9/\u00e9,tools/cache","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/\u00e9/\u00e9","host_path":"tools\\cache","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/\u00e9/\u00e9,tools\\cache,ro","--mount-deny","logs","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/etc/\u00e9/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/etc/\u00e9/\u00e9","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\tools\\cache\\logs"]],["allowed",["C:\\fixture\\cwd\\tools\\cache\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/etc/data-1/nvx,w//x/.","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/data-1/nvx","host_path":"w\\x","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/data-1/nvx,w\\x,ro","--mount-deny","secrets"],"fragment":" virtfs_dir=/etc/data-1/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/etc/data-1/nvx","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\w\\x\\secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/a/\u00e9/srv,tools/cache","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/a/\u00e9/srv","host_path":"tools\\cache","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/a/\u00e9/srv,tools\\cache,ro","--mount-deny","C:\\abs\\out","--mount-deny","/abs/out","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/a/\u00e9/srv virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a/\u00e9/srv","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\abs\\out"]],["allowed",["C:\\fixture\\cwd\\tools\\cache\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/run/a,tools/cache,rw","denied":[],"allowed":[],"writable":["cache","out","."],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/a","host_path":"tools\\cache","access":"rw","denied":[],"allowed":[],"writable":["cache","out","."],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/a,tools\\cache,rw","--mount-write","cache","--mount-write","out","--mount-write","."],"fragment":" virtfs_dir=/run/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run/a","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"rw","denied":[],"allowed":[],"writable":["cache","out","."],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",["C:\\fixture\\cwd\\tools\\cache\\cache","C:\\fixture\\cwd\\tools\\cache\\out","C:\\fixture\\cwd\\tools\\cache"]]]}, +{"flavor":"windows","value":"/nvx,C:\\data\\x,y,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/nvx","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/nvx,C:\\data\\x,y,rw","--mount-deny","secrets"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\data\\x,y\\secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/a b,a/../b,rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/nvx,C:\\data\\x,y,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/nvx","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-30b37e7ae096735b949f4f464412ab3c,C:\\data\\x,y,ro,file"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx","host_path":"C:\\data\\x,y","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/\u00e9,C:/w,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/\u00e9","host_path":"C:\\w","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/\u00e9,C:\\w,ro","--mount-deny","C:\\abs\\out"],"fragment":" virtfs_dir=/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9","host_path":"C:\\w","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/nvx/a/work,C:\\data\\x,y","denied":["a\u0000"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/etc,C:\\data\\x,y,ro","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/work/\u3000\u001cwork,w//x/.,rw","denied":["logs"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/work/\\u3000\\x1cwork': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/run/\u00e9,w//x/.,ro","denied":["secrets"],"allowed":["out"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/\u00e9","host_path":"w\\x","access":"ro","denied":["secrets"],"allowed":["out"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/\u00e9,w\\x,ro","--mount-deny","secrets","--mount-allow","out"],"fragment":" virtfs_dir=/run/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/\u00e9","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":["secrets"],"allowed":["out"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\w\\x\\secrets"]],["allowed",["C:\\fixture\\cwd\\w\\x\\out"]],["writable",[]]]}, +{"flavor":"windows","value":"/cache/run,C:\\data\\x,y,rx","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/data-1/data-1,work","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/data-1/data-1","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-fd1bb3d6d1bce0afe815991bf83f5962,work,ro,file"],"fragment":" virtfs_dir=/data-1/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/data-1","host_path":"C:\\fixture\\cwd\\work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/run/srv/data-1,tools/cache","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/run/srv/data-1","host_path":"tools\\cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-a8f01611937352bf22a395a8f556cae1,tools\\cache,ro,file","--mount-owner","caller"],"fragment":" virtfs_dir=/run/srv/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/srv/data-1","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/data-1/data-1/work,tools/cache,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/data-1/data-1/work","host_path":"tools\\cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-308e5770e516a27164f075a92cce7fdb,tools\\cache,ro,file"],"fragment":" virtfs_dir=/data-1/data-1/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/data-1/work","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/a b,/srv/data","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/nvx,/srv/data,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/nvx","host_path":"\\srv\\data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-30b37e7ae096735b949f4f464412ab3c,\\srv\\data,rw,file"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvx","host_path":"C:\\srv\\data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/,runshares/run,C:\\data\\x,y","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'y'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/srv/a,tools/cache,rx","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/a,a\u0000b,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a NUL character: 'a\\x00b'"}, +{"flavor":"windows","value":"/data-1/work,w//x/.","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/data-1/work","host_path":"w\\x","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/data-1/work,w\\x,ro","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/data-1/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/work","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",["C:\\fixture\\cwd\\w\\x\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/etc/a/a,w//x/.","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"file","error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /etc/a/a shares a file, whose mode applies to the whole file"}, +{"flavor":"windows","value":"/\u00e9/etc,w//x/.","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/\u00e9/etc","host_path":"w\\x","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/\u00e9/etc,w\\x,ro","--mount-deny","C:\\abs\\out","--mount-deny","/abs/out","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/\u00e9/etc virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/\u00e9/etc","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":["C:\\abs\\out","/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\abs\\out"]],["allowed",["C:\\fixture\\cwd\\w\\x\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,tools/cache","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/nvx/data-1,C:\\data\\x,y,ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/nvx/data-1","host_path":"C:\\data\\x,y","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/nvx/data-1,C:\\data\\x,y,ro","--mount-deny","secrets","--mount-allow","logs/payloads","--mount-owner","caller"],"fragment":" virtfs_dir=/nvx/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx/data-1","host_path":"C:\\data\\x,y","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\data\\x,y\\secrets"]],["allowed",["C:\\data\\x,y\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,tools/cache","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/a b,/srv/data,rw","denied":["secrets","/abs/out"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/run/data-1/etc,tools/cache,rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out","/abs/out"],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/data-1/etc","host_path":"tools\\cache","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out","/abs/out"],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/data-1/etc,tools\\cache,rw","--mount-deny","logs","--mount-allow","logs/payloads","--mount-write","out","--mount-write","/abs/out"],"fragment":" virtfs_dir=/run/data-1/etc virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run/data-1/etc","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out","/abs/out"],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\tools\\cache\\logs"]],["allowed",["C:\\fixture\\cwd\\tools\\cache\\logs\\payloads"]],["writable",["C:\\fixture\\cwd\\tools\\cache\\out","C:\\abs\\out"]]]}, +{"flavor":"windows","value":"/nvx,/srv/data,ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/nvx","host_path":"\\srv\\data","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/nvx,\\srv\\data,ro","--mount-deny","/abs/out"],"fragment":" virtfs_dir=/nvx virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx","host_path":"C:\\srv\\data","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/a b,tools/cache,rw","denied":["secrets","/abs/out","a\u0000"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","error":"invalid sandbox mount target '/a b': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/run/work,tools/cache,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/work","host_path":"tools\\cache","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/work,tools\\cache,ro","--mount-deny","C:\\abs\\out"],"fragment":" virtfs_dir=/run/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run/work","host_path":"C:\\fixture\\cwd\\tools\\cache","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"//srv/run,/srv/data","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '//srv/run': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/srv/srv,a/../b,rw","denied":["secrets","C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory","error":"sandbox mount host path contains a parent component: a\\..\\b"}, +{"flavor":"windows","value":"/work,w//x/.,rw","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/work","host_path":"w\\x","access":"rw","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/work,w\\x,rw","--mount-deny","logs","--mount-deny","/abs/out","--mount-owner","caller"],"fragment":" virtfs_dir=/work virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work","host_path":"C:\\fixture\\cwd\\w\\x","access":"rw","denied":["logs","/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\fixture\\cwd\\w\\x\\logs","C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/a,work,rw","denied":["/abs/out","."],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory","mount":{"guest_target":"/a","host_path":"work","access":"rw","denied":["/abs/out","."],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"},"arguments":["--mount","/a,work,rw","--mount-deny","/abs/out","--mount-deny",".","--mount-allow","logs/payloads","--mount-write","out","--mount-owner","caller"],"fragment":" virtfs_dir=/a virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/a","host_path":"C:\\fixture\\cwd\\work","access":"rw","denied":["/abs/out","."],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\fixture\\cwd\\work"]],["allowed",["C:\\fixture\\cwd\\work\\logs\\payloads"]],["writable",["C:\\fixture\\cwd\\work\\out"]]]}, +{"flavor":"windows","value":"/etc/data-1,w//x/.,rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/etc/data-1","host_path":"w\\x","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/etc/data-1,w\\x,rw","--mount-deny","/abs/out","--mount-deny","secrets","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/etc/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/etc/data-1","host_path":"C:\\fixture\\cwd\\w\\x","access":"rw","denied":["/abs/out","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\fixture\\cwd\\w\\x\\secrets"]],["allowed",["C:\\fixture\\cwd\\w\\x\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/srv,w//x/.,rx","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/\u00e9,/srv/data,rx","denied":["/abs/out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","error":"unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)"}, +{"flavor":"windows","value":"/srv/data-1/\u00e9,C:\\data\\x,y,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/data-1/\u00e9","host_path":"C:\\data\\x,y","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/data-1/\u00e9,C:\\data\\x,y,ro","--mount-deny","C:\\abs\\out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/data-1/\u00e9 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/srv/data-1/\u00e9","host_path":"C:\\data\\x,y","access":"ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/proc,a\u0000b,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","error":"sandbox mount target /proc overlaps the reserved /proc tree"}, +{"flavor":"windows","value":"/run,/srv/data,ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run","host_path":"\\srv\\data","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run,\\srv\\data,ro","--mount-deny","logs","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/run","host_path":"C:\\srv\\data","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\srv\\data\\logs"]],["allowed",["C:\\srv\\data\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/a/work,w//x/.,ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/a/work","host_path":"w\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/a/work,w\\x,ro","--mount-owner","caller"],"fragment":" virtfs_dir=/a/work virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/a/work","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/etc,tools/cache,ro","denied":["C:\\abs\\out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","error":"sandbox mount target /etc is reserved"}, +{"flavor":"windows","value":"/nvx/a/a,/srv/data","denied":["/abs/out","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/nvx/a/a","host_path":"\\srv\\data","access":"ro","denied":["/abs/out","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/nvx/a/a,\\srv\\data,ro","--mount-deny","/abs/out","--mount-deny","secrets"],"fragment":" virtfs_dir=/nvx/a/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/nvx/a/a","host_path":"C:\\srv\\data","access":"ro","denied":["/abs/out","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\srv\\data\\secrets"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/data-1/a,/srv/data","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file","mount":{"guest_target":"/data-1/a","host_path":"\\srv\\data","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-4b25c272fa19ed009dce984c919893ec,\\srv\\data,ro,file"],"fragment":" virtfs_dir=/data-1/a virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1/a","host_path":"C:\\srv\\data","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/nvxrun,/srv/data,rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file","mount":{"guest_target":"/nvxrun","host_path":"\\srv\\data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"arguments":["--mount-aggregate","/run/nvx/shares","--mount-child","0-6ab51ef8dc55669bf1c3c27372b1f413,\\srv\\data,rw,file","--mount-owner","caller"],"fragment":" virtfs_dir=/nvxrun virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/nvxrun","host_path":"C:\\srv\\data","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/,/srv,tools/cache,rw","denied":[],"allowed":[],"writable":["/abs/out","cache"],"owner":"vmm","kind":"directory","error":"invalid sandbox mount target '/': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"flavor":"windows","value":"/run/data-1,C:\\data\\x,y,rw","denied":["secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory","mount":{"guest_target":"/run/data-1","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"},"arguments":["--mount","/run/data-1,C:\\data\\x,y,rw","--mount-deny","secrets","--mount-write","out"],"fragment":" virtfs_dir=/run/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/run/data-1","host_path":"C:\\data\\x,y","access":"rw","denied":["secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\data\\x,y\\secrets"]],["allowed",[]],["writable",["C:\\data\\x,y\\out"]]]}, +{"flavor":"windows","value":"/nvx,/srv/data,rw","denied":["C:\\abs\\out","secrets"],"allowed":["logs/payloads"],"writable":["a\u0000"],"owner":"caller","kind":"directory","error":"sandbox mount writable paths must be nonempty"}, +{"flavor":"windows","value":"/work/data-1,C:\\data\\x,y,rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/data-1","host_path":"C:\\data\\x,y","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/data-1,C:\\data\\x,y,rw","--mount-deny","C:\\abs\\out","--mount-allow","logs/payloads"],"fragment":" virtfs_dir=/work/data-1 virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/work/data-1","host_path":"C:\\data\\x,y","access":"rw","denied":["C:\\abs\\out"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",["C:\\data\\x,y\\logs\\payloads"]],["writable",[]]]}, +{"flavor":"windows","value":"/srv/a/run,w//x/.,ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/a/run","host_path":"w\\x","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/a/run,w\\x,ro","--mount-deny","/abs/out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/a/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/srv/a/run","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/srv/work,C:/w,rw","denied":["C:\\abs\\out"],"allowed":[],"writable":["cache","out"],"owner":"caller","kind":"directory","mount":{"guest_target":"/srv/work","host_path":"C:\\w","access":"rw","denied":["C:\\abs\\out"],"allowed":[],"writable":["cache","out"],"owner":"caller","kind":"directory"},"arguments":["--mount","/srv/work,C:\\w,rw","--mount-deny","C:\\abs\\out","--mount-write","cache","--mount-write","out","--mount-owner","caller"],"fragment":" virtfs_dir=/srv/work virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/work","host_path":"C:\\w","access":"rw","denied":["C:\\abs\\out"],"allowed":[],"writable":["cache","out"],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",["C:\\w\\cache","C:\\w\\out"]]]}, +{"flavor":"windows","value":"/data-1,C:/w,ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/data-1","host_path":"C:\\w","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/data-1,C:\\w,ro","--mount-deny","/abs/out"],"fragment":" virtfs_dir=/data-1 virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/data-1","host_path":"C:\\w","access":"ro","denied":["/abs/out"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/srv/srv,w//x/.,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/srv/srv","host_path":"w\\x","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/srv/srv,w\\x,rw"],"fragment":" virtfs_dir=/srv/srv virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/srv","host_path":"C:\\fixture\\cwd\\w\\x","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/work/etc/run,w//x/.","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/work/etc/run","host_path":"w\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/work/etc/run,w\\x,ro"],"fragment":" virtfs_dir=/work/etc/run virtfs_tag=microvm virtfs_mode=ro","absolute":{"guest_target":"/work/etc/run","host_path":"C:\\fixture\\cwd\\w\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/procmachine-id,C:/w,rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory","mount":{"guest_target":"/procmachine-id","host_path":"C:\\w","access":"rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"arguments":["--mount","/procmachine-id,C:\\w,rw","--mount-deny","C:\\abs\\out","--mount-deny","logs","--mount-owner","caller"],"fragment":" virtfs_dir=/procmachine-id virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/procmachine-id","host_path":"C:\\w","access":"rw","denied":["C:\\abs\\out","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},"absolute_policy":[["denied",["C:\\abs\\out","C:\\w\\logs"]],["allowed",[]],["writable",[]]]}, +{"flavor":"windows","value":"/srv/nvx/srv,C:/w,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory","mount":{"guest_target":"/srv/nvx/srv","host_path":"C:\\w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"arguments":["--mount","/srv/nvx/srv,C:\\w,rw"],"fragment":" virtfs_dir=/srv/nvx/srv virtfs_tag=microvm virtfs_mode=rw","absolute":{"guest_target":"/srv/nvx/srv","host_path":"C:\\w","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},"absolute_policy":[["denied",[]],["allowed",[]],["writable",[]]]} +], +"launches": [ +{"flavor":"posix","layers":["runtime,layer.erofs,a6b34c85-86b9-b646-18dc-28887414f5ab","custom,C:/layers/x.erofs,2089b5af-e1d7-1d94-940d-631f55b9c56a","distro,a//b.erofs,273ccf9a-9d5a-fd30-95b8-921f5d2dc314"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/s h","args":["--","a\u00e9","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a\u00e9","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"","error":"sandbox entrypoint must be an absolute path without spaces"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,a55e4eb1-7eeb-35d5-65b0-f6515125d6da"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["==","---serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","=\u00e9\u00e9","==","\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-","=--serve\u00e9","=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=--serve","a","a\u00e9\u00e9","\u00e9--serve=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9-","a=","--serve","=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=\u00e9"],"hostname":"example","identity":[65534,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,b8ea9084-e245-0269-f314-5038109e52c7","custom,a//b.erofs,84982b13-1f07-8906-6ec1-2925deb75152","distro,C:/layers/x.erofs,d1edc9fd-41f9-e89c-daf1-e00aca033141"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["="],"hostname":"nvx-sandbox","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,7ad560cb-c7d8-f83a-e691-da5182adada5","distro,layer.erofs,82ecd171-e7cf-f834-657d-da4581d68d3f","runtime,C:/layers/x.erofs,a093d2e7-193b-58b1-a44f-cee78f39af18"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=distro,0xd0003000,82ecd171-e7cf-f834-657d-da4581d68d3f nvx_layer=runtime,0xd0004000,a093d2e7-193b-58b1-a44f-cee78f39af18 nvx_layer=custom,0xd0005000,7ad560cb-c7d8-f83a-e691-da5182adada5 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_pids_max=65"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,6a18f14b-7162-bf87-25ae-bd7d60a056c1","custom,layer.erofs,d41e6f2e-e259-8f87-f16a-b0eb536f6387"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["=","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","---servea","=a","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","---serve-","-a-","--serve","==","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","---serve--serve","---serve","a","a--","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","\u00e9\u00e9","-\u00e9--serve"],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-02fb6c5f7c7f5714d48a715e6b5594f2,C:/host0,ro,file"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"posix","layers":["runtime,layer.erofs,fa3780d0-74bb-4dbb-77d3-8accf1ebeb7a","distro,a//b.erofs,d7ef509b-4a84-cd81-2da1-49e3f43f3b09","custom,layer.erofs,9a364c2e-3436-d5a3-8962-b8d75d6b7d1a"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9a=","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a=","-=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","a--serve","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-","--serve\u00e9\u00e9","=","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv3,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host4","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a/b.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-5741ea7e06d8828eb3b2e09237ece2e0,C:/host0,rw,file","--mount-child","1-ebad453409a50bcc524ab6e52c6efbb1,C:/host1,ro","--mount-child","2-a37404d6fb3eebf56550e9d970c91be4,host2,ro","--mount-child","3-eb11e7902679e44950d75a72a530745c,/srv3,ro","--mount-child","4-76f1f6798e7b5fb68f79a48ef3252a19,host4,ro","--mount-child","5-4044bb578db2763d7aa67419db26fc00,host5,ro","--mount-deny","/abs/secrets","--mount-deny","/srv3/secrets","--mount-deny","/fixture/cwd/host5/secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,ecd45898-c6a9-5923-3199-967b9abf7d03","custom,C:/layers/x.erofs,818aa272-63bd-1821-36d6-ccbe0207b303","distro,layer.erofs,35f11375-8399-9dc0-6b2f-0d9545582d31"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9-\u00e9","=\u00e9--serve","=a=","---serve=","a=="],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-8c60c153dff166a0243528dc448165a1,/srv0,rw,file","--mount-child","1-cfa2e151cef5975fdf2ce2ebf933a2a7,C:/host1,ro","--mount-deny","/abs/secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,35f11375-8399-9dc0-6b2f-0d9545582d31 nvx_layer=runtime,0xd0004000,ecd45898-c6a9-5923-3199-967b9abf7d03 nvx_layer=custom,0xd0005000,818aa272-63bd-1821-36d6-ccbe0207b303 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=\u00e9-\u00e9 nvx_arg==\u00e9--serve nvx_arg==a= nvx_arg=---serve= nvx_arg=a== nvx_share=0-8c60c153dff166a0243528dc448165a1,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw,file nvx_share=1-cfa2e151cef5975fdf2ce2ebf933a2a7,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro"}, +{"flavor":"posix","layers":["custom,layer.erofs,aed22c49-c733-1bea-fdd4-f8d4204e7498","runtime,a//b.erofs,316b4f19-337d-4146-8519-565eac369b4f"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["=\u00e9","\u00e9\u00e9","a--serve=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","a---serve"],"hostname":"nvx-sandbox","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:65534"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=runtime,0xd0004000,316b4f19-337d-4146-8519-565eac369b4f nvx_layer=custom,0xd0005000,aed22c49-c733-1bea-fdd4-f8d4204e7498 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=nvx-sandbox nvx_arg==\u00e9 nvx_arg=\u00e9\u00e9 nvx_arg=a--serve= nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg=a---serve nvx_pids_max=65"}, +{"flavor":"posix","layers":["custom,a//b.erofs,c7f97bdf-106d-df55-61a9-eb0faa803f3a","runtime,layer.erofs,83f82d7f-2371-9b7c-0e26-943daaa61eff","distro,a//b.erofs,30b5620f-c623-5963-6872-06e17b596643"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["\u00e9a="],"hostname":"-a","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"posix","layers":["runtime,layer.erofs,22540f15-7117-5e21-5452-55a9556d441a"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"example","identity":[1000,65534],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxx,/srv4,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxx,/srv5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-21f761192d21037ba953943fea2a5d1e,host0,rw","--mount-child","1-a511de7212996480363b20dff1c44eca,/srv1,ro,file","--mount-child","2-464974427cf0517032b88d59442f420f,/srv2,ro,file","--mount-child","3-cb13f10cd780ff479572d2415b932273,C:/host3,ro","--mount-child","4-5811e72dac0195813294e8cd65d8c5ad,/srv4,rw","--mount-child","5-a9cbb9d9ab2e34c0528321b6c7937bb7,/srv5,ro,file","--mount-deny","/abs/secrets"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=runtime,0xd0004000,22540f15-7117-5e21-5452-55a9556d441a nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example nvx_memory_max=1000000000000000000000000000000 nvx_share=0-21f761192d21037ba953943fea2a5d1e,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw nvx_share=1-a511de7212996480363b20dff1c44eca,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file nvx_share=2-464974427cf0517032b88d59442f420f,/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file nvx_share=3-cb13f10cd780ff479572d2415b932273,/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=4-5811e72dac0195813294e8cd65d8c5ad,/share/04xxxxxx,rw nvx_share=5-a9cbb9d9ab2e34c0528321b6c7937bb7,/share/05xxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"posix","layers":["custom,layer.erofs,a3e157e4-255a-4257-8176-51c423463089"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["-\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"nvx-sandbox","identity":[1000,65534],"memory_max":"1000000000000000000000000000000","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:65534","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","--mount-deny","secrets"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,f67d6ca6-1638-7a37-ea24-142db6296541","runtime,layer.erofs,9a0f0e67-9693-840c-31cf-06d86151d54e","custom,C:/layers/x.erofs,ad0acdad-3270-3bc9-0056-44a8918755b1"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-c59d3f59aa0d5e86cc7e5f8e78747e5b,/srv0,ro","--mount-child","1-5e5d181bf9cbdeff6972d68be4c5fbc6,host1,ro","--mount-deny","/srv0/secrets","--mount-deny","/fixture/cwd/host1/secrets"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,979d8723-9b7c-97cb-3c0c-5fce959e3cf4"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","aaa","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a-\u00e9"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxx,C:/host3","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host4,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv5,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3c65244a43442e19d033ac4c44a33921,/srv0,rw","--mount-child","1-a05432aadcd26b32de44dd594bbfe88e,C:/host1,rw","--mount-child","2-94603f322e418863a6b055db682067b2,host2,ro,file","--mount-child","3-f7a1733e6d3008ff4a7d52b605d07643,C:/host3,ro","--mount-child","4-c89c8cbc3a6ea932a0f1c082dfefdb61,host4,rw","--mount-child","5-2bea48a2a214c35c5a7324a6fe8cb964,/srv5,rw","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,37358704-42ba-7bc2-f7c8-5e3081e1ce65"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["=\u00e9="],"hostname":"example","identity":[1000,65534],"memory_max":"0","pids_max":"64","mounts":[],"command_line":" quiet loglevel=0 ","error":"--memory-max must be positive"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,45a311d8-bac0-b48e-88e2-c3f5aaf96468"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"example","identity":[1000,1001],"memory_max":"0","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","error":"--memory-max must be positive"}, +{"flavor":"posix","layers":["custom,layer.erofs,656a4d6d-68ff-9815-1e85-4d1acbb1a7c1","distro,C:/layers/x.erofs,6717cc9a-5a06-db08-681a-da2a855ad0f8","runtime,layer.erofs,80e671ee-cc09-1bd9-e007-0d2d511a571e","custom,a//b.erofs,cbda55fe-5c2b-a8a6-4394-5ef21b718a2c"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["=="],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":"0","mounts":[],"command_line":"quiet","error":"a sandbox requires one to three read-only layers"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,cdc95b00-831b-01c1-feae-4724354542f0"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["--serve--serve-","-a"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":"0","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxx,C:/host3","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host4","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","error":"--memory-max must be positive"}, +{"flavor":"posix","layers":["distro,layer.erofs,580e9941-5f5c-2c30-7ac2-127c4bd8e3c6"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["\u00e9","-"],"hostname":"example","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"posix","layers":["distro,a//b.erofs,8cf91078-9556-d1be-17dc-fae9bcba794b"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":[],"hostname":"Example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxx,C:/host0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host4,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,27c4fb6d-0c0e-4ea4-bbb5-b4d385bb4fe8"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":["--serve-=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9="],"hostname":"a-1","identity":[65534,65534],"memory_max":"268435456","pids_max":null,"mounts":[],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,3cf8bfb6-5fb6-a7bc-1f86-1531a352c85b","runtime,C:/layers/x.erofs,97843d62-9330-fda7-a4fa-ab7eeaac6288","custom,a//b.erofs,7b056600-c055-1ef7-1705-5d36f2d41d94"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["=---serve"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":"duplicate sandbox layer role(s): custom"}, +{"flavor":"posix","layers":["runtime,layer.erofs,50f0c385-3492-d186-d292-0a4727a31653","custom,layer.erofs,a40c25c8-1363-d257-5a07-4bbe91024a98"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["-"],"hostname":"example","identity":[1000,1001],"memory_max":"268435456","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-2c78b6849c761b0a4e53d46dc2504f51,host0,rw,file"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"posix","layers":["runtime,layer.erofs,b8d32eb7-6a38-70fa-b5b1-590ba4479c23"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve","aa","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","--serve"],"hostname":"nvx-sandbox","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:1001"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=runtime,0xd0004000,b8d32eb7-6a38-70fa-b5b1-590ba4479c23 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=nvx-sandbox nvx_arg=--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_arg=--serve nvx_arg=aa nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg=--serve nvx_memory_max=1000000000000000000000000000000"}, +{"flavor":"posix","layers":["distro,layer.erofs,91c1e6d7-63c1-35cc-e117-cd98af9dbee1"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["---serve=","\u00e9-=","--serve=","--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","\u00e9a\u00e9","=","\u00e9","-","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9--servea","--serve=-","aa\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","a","\u00e9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"nvx-sandbox","identity":[1000,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","1000:65534","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,adb35722-f24d-bd71-55fd-5392656d3c19"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["--serve==","\u00e9a"],"hostname":"example","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:65534","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=custom,0xd0005000,adb35722-f24d-bd71-55fd-5392656d3c19 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example nvx_arg=--serve== nvx_arg=\u00e9a nvx_pids_max=65"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,b6ef98b9-3131-a98e-89b9-b0273ea7a45c"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-e142a7fa495c57f448191a9c4f614815,host0,ro,file"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,bf51fb7a-ef3c-09cb-432c-5129ae3686bc"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["-\u00e9--serve"],"hostname":"example","identity":[0,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"\u3000quiet\u001c","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,0a4a8a0b-7cbe-af5d-f52c-55bb4e9761d5","distro,layer.erofs,42660302-8b2f-5d60-e6c6-5c5414b85a76"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-"],"hostname":"a-1","identity":[1000,65534],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-02b1781bbb998173cb0f0702399fe784,C:/host0,ro","--mount-child","1-5d639b04d652d0988014845c0beeaf84,C:/host1,ro","--mount-deny","/abs/secrets"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=distro,0xd0003000,42660302-8b2f-5d60-e6c6-5c5414b85a76 nvx_layer=custom,0xd0005000,0a4a8a0b-7cbe-af5d-f52c-55bb4e9761d5 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=a-1 nvx_arg==axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_memory_max=1000000000000000000000000000000 nvx_share=0-02b1781bbb998173cb0f0702399fe784,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-5d639b04d652d0988014845c0beeaf84,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro"}, +{"flavor":"posix","layers":["distro,a//b.erofs,acdd08a1-769a-c3fd-d20b-e8f0b22bdfa5","custom,C:/layers/x.erofs,8cd77740-856f-e40d-b0ce-e8a8dba164b3"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a/b.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-e0adba7f0f4a955dd08fdf94dad826f5,/srv0,ro","--mount-child","1-b3a4c7708540894b9b2ce350e616b8d2,/srv1,rw","--mount-child","2-7dee16ebb0e7995caafd6d31d1e39da5,/srv2,rw","--mount-deny","/srv2/secrets"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"posix","layers":["runtime,layer.erofs,dc6b958d-36cb-e08a-b2f0-6945285a3abe"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-4a833434ca95979702b308f4b703568f,C:/host0,ro,file"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"posix","layers":["custom,layer.erofs,f0916002-b89e-3079-70b4-99fb50379579"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":[],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3c65244a43442e19d033ac4c44a33921,/srv0,ro","--mount-child","1-5c550bfb07a1737f0da58168a49d9c18,host1,ro","--mount-deny","/srv0/secrets","--mount-deny","/fixture/cwd/host1/secrets"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,0aed6803-962d-9098-90df-c2cdf949064f","distro,layer.erofs,b84c1d3d-670c-bd20-1b15-9e58a57cc159","custom,layer.erofs,386e7680-0139-2cc3-143a-115e64d7b9a8","runtime,a//b.erofs,75b6a5ef-af13-ef61-f7df-0cef00020c8b"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve","=\u00e9a","a=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","--servea","=a","--a","a--serve","=\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a","--servea\u00e9","--servea","\u00e9","--serve=","a","=","-","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","error":"a sandbox requires one to three read-only layers"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,a54937bf-c3b5-4730-31d0-149d5d967101"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["--serve"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":"0","mounts":[],"command_line":"a\u001cnvx_x=1","error":"--pids-max must be positive"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,c855b135-ff10-b15f-5bc9-43ebe55f0ef4","runtime,C:/layers/x.erofs,5c3148c0-2bf5-3137-a7dd-cafc0d0283ba"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve\u00e9\u00e9","-","--serve","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[],"command_line":"\u3000quiet\u001c","error":"duplicate sandbox layer role(s): runtime"}, +{"flavor":"posix","layers":["custom,a//b.erofs,bd09dc76-81fd-1b51-4250-14657026e68f","distro,layer.erofs,1ef5a04a-eada-2969-9499-c436a26d1f57","runtime,C:/layers/x.erofs,8e704a5e-c05d-5a80-c3db-d9797c548e73"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["-=a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxx,C:/host2","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv4,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-a7466dbc8cc865270f4176f26210113f,/srv0,ro","--mount-child","1-c5994f1f4c2096feba876ac7f757ddca,host1,rw","--mount-child","2-68fe0c6ea9c3e8404a9caa4da5fa5b4e,C:/host2,ro","--mount-child","3-e869f244f3af5dd99d6cc7c4fe3440cf,C:/host3,rw,file","--mount-child","4-ab341b1fbf5adba3c3c81bd1bffa29ec,/srv4,rw","--mount-child","5-3893625aad5715d7f05fe85e17ca963d,host5,ro","--mount-deny","/srv0/secrets","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets","--mount-deny","/fixture/cwd/host5/secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,fde517fd-15ae-769b-c408-c4cf745c8c91"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["aa-"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxx,host1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-e1ed8cbca2406f01ea48580ea6726a7d,C:/host0,ro,file","--mount-child","1-1ec7b2c1648b15995214377627d3ac85,host1,ro,file"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=distro,0xd0003000,fde517fd-15ae-769b-c408-c4cf745c8c91 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=nvx-sandbox nvx_arg=aa- nvx_pids_max=100000000000000000000 nvx_share=0-e1ed8cbca2406f01ea48580ea6726a7d,/share/00xxxxxxxxx,ro,file nvx_share=1-1ec7b2c1648b15995214377627d3ac85,/share/01xxxxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,3c0d4ab6-c5f5-6f5a-d586-c927ae45ad51"],"scratch":"s//x.ext4","entrypoint":"bin/sh","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"a-1","identity":[65534,1001],"memory_max":"268435456","pids_max":"64","mounts":[],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","error":"sandbox entrypoint must be an absolute path without spaces"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,715d50b1-642e-1b8e-2c2f-7ae3bc2e03ac"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":"-1","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"virtfs_dir=/x","error":"--memory-max must be positive"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,2fa9ef3c-075b-32db-6fc2-fdf406368f63"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-2c78b6849c761b0a4e53d46dc2504f51,C:/host0,ro","--mount-child","1-201065f559735b54e181acb8c29ce165,/srv1,rw","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"posix","layers":["runtime,a//b.erofs,c7006cfe-c270-a90c-82a4-23f659021ab5"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["\u00e9\u00e9--serve"],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxx,/srv1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv3,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host4,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-a313b58db0a45dc2770ea305c5f9b9f8,/srv0,ro","--mount-child","1-5edd8a3ad4dce48ad486bbad079bf928,/srv1,rw","--mount-child","2-543dd5b82e0efa346e8b236337c3f558,C:/host2,ro,file","--mount-child","3-dd584fa8413827913e5193fc0de5173c,/srv3,ro","--mount-child","4-370859c2736ad4cce9f5c1c428db5efb,C:/host4,rw","--mount-child","5-824510f24100cb747d4a0e8800c34d09,C:/host5,ro,file","--mount-deny","/srv0/secrets","--mount-deny","/srv3/secrets","--mount-deny","/fixture/cwd/C:/host4/secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["runtime,layer.erofs,9d716755-a832-c8eb-e326-ccb202412244"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[""],"hostname":"example","identity":[65534,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxx,host1,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":" quiet loglevel=0 ","error":"sandbox arguments must be nonempty and contain no spaces"}, +{"flavor":"posix","layers":["custom,layer.erofs,0fe6f305-a1ba-b6d8-c7c1-e3c4984d3406"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["-"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-c2aae6c377ecef1e25ac1d534719a730,host0,ro","--mount-child","1-5cf6a70839b0add1b4e454dd132ebe48,C:/host1,rw","--mount-child","2-0b4c0be951ffb9b8913ca35a7f7c49fd,C:/host2,ro,file","--mount-deny","/fixture/cwd/host0/secrets","--mount-deny","/abs/secrets"],"kernel_command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9 nvx_sandbox=1 nvx_layer=custom,0xd0005000,0fe6f305-a1ba-b6d8-c7c1-e3c4984d3406 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=- nvx_memory_max=268435456 nvx_pids_max=100000000000000000000 nvx_share=0-c2aae6c377ecef1e25ac1d534719a730,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-5cf6a70839b0add1b4e454dd132ebe48,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw nvx_share=2-0b4c0be951ffb9b8913ca35a7f7c49fd,/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,c97e31d1-65d8-53d1-5b65-0eb79ec81bc7"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":"1000000000000000000000000000000","pids_max":"99999999999999999999","mounts":[],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=runtime,0xd0004000,c97e31d1-65d8-53d1-5b65-0eb79ec81bc7 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=nvx-sandbox nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=100000000000000000000"}, +{"flavor":"posix","layers":["distro,layer.erofs,42b4357a-f158-1dfb-204c-9322e928c494","custom,a//b.erofs,04648c41-7397-c262-b5f6-6578aaf5bc48","runtime,layer.erofs,e219f7e4-5f5e-87ca-6ab1-bc02ce3b2ecd"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["=","a","=","=a","--serve","\u00e9","--serve-=","\u00e9a\u00e9","--serve","-\u00e9=","=\u00e9","=-a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve-","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","a","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","a\u00e9\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","--serve=--serve"],"hostname":"example","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxx,host1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3c65244a43442e19d033ac4c44a33921,host0,ro","--mount-child","1-22d170812412da17d02598b400fddf7a,host1,ro","--mount-deny","/fixture/cwd/host0/secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["runtime,layer.erofs,ce8c30ed-7078-382e-a2f5-58d465c41c45","custom,layer.erofs,9637ff70-e851-069f-1aa1-245704a483a0"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"a-1","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxx,/srv2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxx,C:/host3,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv4","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-22fd938096be90da02b9f19802e59ee8,C:/host0,ro,file","--mount-child","1-96a4cc6bed123f930aded13f84153d18,C:/host1,ro","--mount-child","2-cd61d437c8f852227733feb5541d4de1,/srv2,rw","--mount-child","3-e675148d11962f657a15c09844148bf7,C:/host3,rw","--mount-child","4-01d7fa4428fbe3f2b5b260abb101438d,/srv4,ro","--mount-child","5-b43507f93d4f8e94c190f1fbbf7530f3,host5,ro","--mount-deny","/abs/secrets","--mount-deny","/fixture/cwd/C:/host3/secrets","--mount-deny","/srv4/secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,be50e9fe-48e6-328d-afa8-a42b4a9c4183","custom,C:/layers/x.erofs,f09059aa-8b88-3035-b44a-98c496aeeb34"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"example","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,be50e9fe-48e6-328d-afa8-a42b4a9c4183 nvx_layer=custom,0xd0005000,f09059aa-8b88-3035-b44a-98c496aeeb34 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,235e05b2-3e18-b2d4-d5c0-9b7f4e1c3d83","distro,C:/layers/x.erofs,0c8d13ee-2896-e0ec-5c63-a29f538beb32","custom,a//b.erofs,3a37bc91-a398-5563-9e33-4af0f1dd6953","runtime,layer.erofs,0cb4e2a4-f2c8-4b56-1e85-fc5b35526df9"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["a--serve","=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9"],"hostname":"example","identity":[1000,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host4,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","error":"a sandbox requires one to three read-only layers"}, +{"flavor":"posix","layers":["custom,a//b.erofs,3d2c838c-80d9-57a2-44fa-f475e3836922","runtime,C:/layers/x.erofs,998632ef-57f3-4330-1f76-d691aad62538"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["-\u00e9a","\u00e9=a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9","--serve=--serve","-","\u00e9","\u00e9aa","-=","-","\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","=","\u00e9a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","-a","--serve-","--\u00e9","--serve--serve\u00e9","--serve","\u00e9"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:65534"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=runtime,0xd0004000,998632ef-57f3-4330-1f76-d691aad62538 nvx_layer=custom,0xd0005000,3d2c838c-80d9-57a2-44fa-f475e3836922 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=-\u00e9a nvx_arg=\u00e9=a nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_arg=\u00e9 nvx_arg=--serve=--serve nvx_arg=- nvx_arg=\u00e9 nvx_arg=\u00e9aa nvx_arg=-= nvx_arg=- nvx_arg=\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg== nvx_arg=\u00e9a nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg=-a nvx_arg=--serve- nvx_arg=--\u00e9 nvx_arg=--serve--serve\u00e9 nvx_arg=--serve nvx_arg=\u00e9"}, +{"flavor":"posix","layers":["custom,layer.erofs,eb1dacef-aea8-e610-fb13-5ef9f9df5b30","distro,layer.erofs,a8ba9752-dc18-0619-8bf2-e529f721a7f2"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["\u00e9\u00e9","-\u00e9"],"hostname":"example","identity":[1000,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxx,host2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-4a2713e0cc2fb90cb2126d17e1220eb7,C:/host0,rw","--mount-child","1-e87c2b5e628be8e704986e9faf3f142a,C:/host1,rw,file","--mount-child","2-cd61d437c8f852227733feb5541d4de1,host2,rw,file","--mount-deny","/fixture/cwd/C:/host0/secrets"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"posix","layers":["custom,layer.erofs,7352ea9d-bcbd-8444-c72e-978c92a02f8c"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":[],"hostname":"example","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-b9fc329aa75d6c134a6529e4939ff1a6,/srv0,rw,file","--mount-child","1-33006c89e36575aff5b6d046f6f2f96d,/srv1,ro,file"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=custom,0xd0005000,7352ea9d-bcbd-8444-c72e-978c92a02f8c nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=example nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=100000000000000000000 nvx_share=0-b9fc329aa75d6c134a6529e4939ff1a6,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw,file nvx_share=1-33006c89e36575aff5b6d046f6f2f96d,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,b35a836a-d6e8-5246-0bd0-e94bf90f2a1b"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9a","=","a\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"a-1","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":"-5","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxx,host1,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u001cnvx_x=1","error":"--pids-max must be positive"}, +{"flavor":"posix","layers":["custom,layer.erofs,e422bf00-8ed7-6660-3005-f625faaf5c76","distro,layer.erofs,e60e3700-74a1-87ce-28d6-5850938e08db","runtime,C:/layers/x.erofs,d1cb4c61-cfaf-2d0d-8fa4-53886bd19a99"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["=a\u00e9"],"hostname":"a-1","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:1001","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","--mount-deny","secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,e60e3700-74a1-87ce-28d6-5850938e08db nvx_layer=runtime,0xd0004000,d1cb4c61-cfaf-2d0d-8fa4-53886bd19a99 nvx_layer=custom,0xd0005000,e422bf00-8ed7-6660-3005-f625faaf5c76 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=a-1 nvx_arg==a\u00e9 nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=100000000000000000000"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,b9027c65-8310-af42-e90e-82dd04c81cd6"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["--servea","-a-"],"hostname":"-a","identity":[65534,1001],"memory_max":"268435456","pids_max":null,"mounts":[],"command_line":"\u3000quiet\u001c","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"posix","layers":["custom,a//b.erofs,7b96bc5b-5c40-f0bd-23a6-88ecc3303ef6","runtime,C:/layers/x.erofs,29e83381-2324-76cc-fe6a-35dc68bf5bfb"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,26377940-8fb4-1b48-da39-23355972ed05"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["-","a","--serve=","--serve\u00e9-","a","a=-","=--serve--serve","\u00e9","\u00e9--","-aa","=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","\u00e9","--serve--serve--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":null,"pids_max":"-5","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,1807eecd-9991-7114-f303-34fad13ffd04","custom,C:/layers/x.erofs,bdb0146a-5e85-c2aa-08e0-4e121c0174a2","distro,C:/layers/x.erofs,c7ff0a0d-02de-b93b-6b95-0462c52389eb"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["-","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","=-"],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:65534","--mount","/share/00,host0,rw"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,9c82caeb-7fbf-649d-b7e1-df9580640bfe"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["a-","\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","--mount-deny","/abs/secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=custom,0xd0005000,9c82caeb-7fbf-649d-b7e1-df9580640bfe nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=a- nvx_arg=\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,381f84d2-d206-d7d7-3081-939562e65fcd"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["\u00e9"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":"0","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":"--memory-max must be positive"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,49296bbf-1475-1314-e0fd-e2ac954adb2e"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["a\u00e9","--serve","-","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"," a","--serve---serve","-=-","a=","--","-","=","=--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","\u00e9=\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","-\u00e9","--serve="],"hostname":"example","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxx,host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","error":"sandbox arguments must be nonempty and contain no spaces"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,9b52e70e-70fd-cfb4-72db-c138cb0eca66","custom,C:/layers/x.erofs,2fab978a-97cd-05e9-58ee-1b2d3540ecc4"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9--serve","--serve","--serve\u00e9=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxx,/srv2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-40d3499216dc30dfd1dc7903ab1f23a7,/srv0,ro","--mount-child","1-d36d1c6d47b3ba4479ff774e75161624,C:/host1,rw","--mount-child","2-36a6146809192afe31b01ea9b4edcf08,/srv2,rw","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,9b52e70e-70fd-cfb4-72db-c138cb0eca66 nvx_layer=custom,0xd0005000,2fab978a-97cd-05e9-58ee-1b2d3540ecc4 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg== nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9--serve nvx_arg=--serve nvx_arg=--serve\u00e9= nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=65 nvx_share=0-40d3499216dc30dfd1dc7903ab1f23a7,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-d36d1c6d47b3ba4479ff774e75161624,/share/01xxxxx,rw nvx_share=2-36a6146809192afe31b01ea9b4edcf08,/share/02xxxxxx,rw"}, +{"flavor":"posix","layers":["runtime,C:/layers/x.erofs,1abe6ab6-c726-b2ef-c5ed-b0021cdeaf8f"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":["\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","="],"hostname":"nvx-sandbox","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxx,host3,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv4,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-2241bf5b96325f17f97a5e4f20104b83,host0,rw,file","--mount-child","1-a511de7212996480363b20dff1c44eca,host1,ro","--mount-child","2-4681817b6f10bf28e089cedd591db827,/srv2,ro","--mount-child","3-1dbc893b42bf5ce8fa9e0c57d7388c4c,host3,rw,file","--mount-child","4-6a4a47f9f908bdc8a47a66893c722be9,/srv4,ro,file","--mount-child","5-77f2e48cc1bc25b5ac5e80766ca68527,C:/host5,rw","--mount-deny","/fixture/cwd/host1/secrets"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"posix","layers":["distro,C:/layers/x.erofs,e1e026dc-6fde-d8b6-16f5-a0408e4582e8","custom,C:/layers/x.erofs,eb7e6df6-d5cd-f69c-346a-e63bdffcf53b","runtime,layer.erofs,7f1f9b36-1d74-9010-3326-834ca2291b5a"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxx,host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv3,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxx,/srv4,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-05dc77dd67f9fecceeff493e481ece81,C:/host0,ro","--mount-child","1-4fd630655626924661ef766fdc56ac60,/srv1,rw","--mount-child","2-cd61d437c8f852227733feb5541d4de1,host2,ro","--mount-child","3-13ad6783a2331b91d2560e9d64f9655d,/srv3,rw,file","--mount-child","4-adf24416dbd760c05d1593b372bf4441,/srv4,rw","--mount-child","5-6baf0053ba2c8dc632afcc717c99ded3,C:/host5,rw","--mount-deny","/srv1/secrets","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["runtime,layer.erofs,3d413624-bdca-383d-8cb0-83c4baa05bfe","distro,C:/layers/x.erofs,30c51bc6-62c8-8b78-d2c7-eee2419698b9"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["-a--serve"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":"-1","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"a\u0000b","error":"--memory-max must be positive"}, +{"flavor":"posix","layers":["distro,a//b.erofs,8b55b36a-20da-fa71-ffcd-4e51b5e10357","runtime,layer.erofs,59ed1876-a053-3a73-e8e3-a7b96c93fa47","custom,a//b.erofs,e3f5c114-9242-8806-a00c-d69f4977fa56"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["--serve--","="],"hostname":"nvx-sandbox","identity":[0,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxx,host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"posix","layers":["custom,a//b.erofs,9e691881-0300-ca52-2c50-e8c83e948dcf","runtime,a//b.erofs,02c9b13d-015e-2e21-3d98-f8a7d27a7b17","distro,a//b.erofs,476b8313-9c4a-1345-0d3b-ec2d9b5bd505"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["\u00e9\u00e9=","\u00e9"],"hostname":"example","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxx,C:/host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a/b.erofs,ro","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-5199f194ddda981cceff0332039ee7c6,C:/host0,ro","--mount-child","1-db7e49c20b31d0686b4bcd4a20a7de6c,host1,rw","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"posix","layers":["distro,layer.erofs,9aafe5c8-9f83-3396-72e7-c5c7d3b81854","custom,a//b.erofs,ff999368-15f9-fab7-86fe-020310920f88","runtime,layer.erofs,b7f48d84-d46a-24ec-c6f3-a70eb87faf46"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["a--serve","--serve","--serve--","a","=="],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":"64","mounts":[],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,9aafe5c8-9f83-3396-72e7-c5c7d3b81854 nvx_layer=runtime,0xd0004000,b7f48d84-d46a-24ec-c6f3-a70eb87faf46 nvx_layer=custom,0xd0005000,ff999368-15f9-fab7-86fe-020310920f88 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=a--serve nvx_arg=--serve nvx_arg=--serve-- nvx_arg=a nvx_arg=== nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=65"}, +{"flavor":"posix","layers":["distro,layer.erofs,98168019-db31-b55e-c718-6c47562e9e46"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["="],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,98168019-db31-b55e-c718-6c47562e9e46 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg== nvx_pids_max=100000000000000000000"}, +{"flavor":"posix","layers":["custom,layer.erofs,9ea29769-eccc-6ffc-ed84-a34742514a38","distro,C:/layers/x.erofs,0c405b57-4609-3001-3b79-876fc48932ff"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["=--servea","--serveaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-a","-\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","-","-=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=a","--","--serve","--serve","-a","=","--serve--serve","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="],"hostname":"a-1","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-52bb4bef8f9da7c9469d7528821375fb,C:/host0,ro","--mount-child","1-b9216949e403b48c7029a83f5853e685,host1,ro,file","--mount-child","2-eb23e0baf016a233a77ff9d1c2246ea9,/srv2,rw,file"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["distro,a//b.erofs,4eb9daf7-6320-096e-1b33-8ff6c19d9fb5"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=--serve=","\u00e9","\u00e9"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-a7738b94c784eb9f25519afb93eb632c,C:/host0,ro,file","--mount-child","1-beb5cac71bf749dd6c188b6843897bcd,host1,rw,file","--mount-child","2-a37404d6fb3eebf56550e9d970c91be4,/srv2,ro,file"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["runtime,layer.erofs,3874f07d-eb38-4ccd-d920-f80fbbe3b5f0"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9-"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:1001","--mount","/share/00xxxxxxxxxxxxxx,C:/host0,ro"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=runtime,0xd0004000,3874f07d-eb38-4ccd-d920-f80fbbe3b5f0 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=\u00e9- nvx_pids_max=65"}, +{"flavor":"posix","layers":["distro,layer.erofs,9b160fb3-4e90-e71d-6ade-9dc9405aa9f5"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01,C:/host1,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-b1b1a0197f57610192a87247301356fd,host0,ro","--mount-child","1-932de0c49c6028fcd06c916b12e85dd0,C:/host1,rw","--mount-deny","/abs/secrets","--mount-deny","/fixture/cwd/C:/host1/secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,9b160fb3-4e90-e71d-6ade-9dc9405aa9f5 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_pids_max=100000000000000000000 nvx_share=0-b1b1a0197f57610192a87247301356fd,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-932de0c49c6028fcd06c916b12e85dd0,/share/01,rw"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,40bfe2b3-c3d1-27bb-eaf5-90131bec9b6d","distro,layer.erofs,99d9626a-0b52-f6ea-b8ce-902131f7af0a","runtime,a//b.erofs,54564aca-d5d6-e34f-5382-66bf6146e432"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["\u00e9=","=a","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=-","=--serve","--serve=--serve","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","--serve","-a\u00e9","a==","-==","-","\u00e9","-a","=\u00e9\u00e9","\u00e9=\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=\u00e9"],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["distro,a//b.erofs,f42346ad-28e5-1e35-42f2-0362b5f9da80","runtime,a//b.erofs,6f00cdad-33b4-8768-d835-7ca07babe0e5"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"-a","identity":[65534,65534],"memory_max":"-1","pids_max":null,"mounts":[],"command_line":" quiet loglevel=0 ","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"posix","layers":["custom,C:/layers/x.erofs,b42d715a-326e-eabe-c3e5-e4c4159865e1","runtime,C:/layers/x.erofs,e187ff8f-bf00-817c-9f43-0131c7c6c5b3","distro,C:/layers/x.erofs,e25afba1-b959-b870-41b2-087b91666dbb"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9-=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"example","identity":[65534,1001],"memory_max":"268435456","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxx,/srv1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","runtime:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","custom:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-acf644408abeff938806e3129864f602,C:/host0,ro","--mount-child","1-546b7fd4256b09d559c8697c06ac533c,/srv1,rw","--mount-child","2-fef4721029507bcaa3789bb592da710c,/srv2,ro","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets","--mount-deny","/srv2/secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["custom,layer.erofs,542a5777-baad-ecbb-cf4f-c9d582336147","distro,C:/layers/x.erofs,75f56217-c521-b77b-e792-f88901fa3b68","runtime,C:/layers/x.erofs,8cd07922-d2db-a8ef-26e2-fbc9500af744"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9","a-=","=a","\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9=--serve","\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-=","\u00e9\u00e9a","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=--serve","--serve\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve\u00e9","a-\u00e9","a=--serve","\u00e9=-","--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","a"],"hostname":"example","identity":[0,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"a\u0000b","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"posix","layers":["custom,layer.erofs,6472274c-203d-9af4-5426-bc1fe2a9da7f","distro,layer.erofs,9bd8619b-d800-af1e-737c-24f861ab86f9","runtime,layer.erofs,79294eb6-ba2f-fc82-d85c-ae57c80d9e5f"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["--serve"],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-dcb90d56303d1d52c1115cf2b5537551,/srv0,rw","--mount-child","1-a05432aadcd26b32de44dd594bbfe88e,C:/host1,rw","--mount-deny","/abs/secrets","--mount-deny","/abs/secrets"],"kernel_command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9 nvx_sandbox=1 nvx_layer=distro,0xd0003000,9bd8619b-d800-af1e-737c-24f861ab86f9 nvx_layer=runtime,0xd0004000,79294eb6-ba2f-fc82-d85c-ae57c80d9e5f nvx_layer=custom,0xd0005000,6472274c-203d-9af4-5426-bc1fe2a9da7f nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example nvx_arg=--serve nvx_pids_max=100000000000000000000 nvx_share=0-dcb90d56303d1d52c1115cf2b5537551,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw nvx_share=1-a05432aadcd26b32de44dd594bbfe88e,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw"}, +{"flavor":"posix","layers":["custom,a//b.erofs,31a4c298-cabd-b7f5-2606-e4c0c083cb44"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["-\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9\u00e9"],"hostname":"example","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"posix","layers":["custom,a//b.erofs,1243acfd-655b-e052-eb75-57e9e5bc6825"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["a-","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:a/b.erofs,ro","--microvm-sandbox-block","scratch:file:s/x.ext4","--microvm-workload-identity","1000:65534"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=custom,0xd0005000,1243acfd-655b-e052-eb75-57e9e5bc6825 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=a- nvx_arg=-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa nvx_pids_max=100000000000000000000"}, +{"flavor":"posix","layers":["custom,layer.erofs,09795141-58d4-10d2-6c91-f38c58ec1049","distro,C:/layers/x.erofs,1ecbf796-df8f-416b-9d18-ea465ac64fc2","runtime,a//b.erofs,e6ade518-bb51-b59b-9485-62c504e60773"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["=aa"],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:/layers/x.erofs,ro","--microvm-sandbox-block","runtime:file:a/b.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:/s/scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-4a2713e0cc2fb90cb2126d17e1220eb7,C:/host0,ro,file"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"posix","layers":["custom,a//b.erofs,5abed48a-edba-04dc-9b45-38bf7d4e05b5","distro,C:/layers/x.erofs,97cc1592-5175-c246-e89e-a9e378e97844"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["a-","\u00e9\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a==","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve\u00e9"],"hostname":"nvx-sandbox","identity":[65534,0],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"a\u0000b","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount","/workspace,host,rw"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount","/workspace,host,rw"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/srv/cache,cache","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-c52ddf65534b7b46035084358ab7902b,host,rw","--mount-child","1-2a93e82e011c66d5c7dbd25da5f2e6c1,cache,ro"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-c52ddf65534b7b46035084358ab7902b,/workspace,rw nvx_share=1-2a93e82e011c66d5c7dbd25da5f2e6c1,/srv/cache,ro"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/srv/cache,cache","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-c52ddf65534b7b46035084358ab7902b,host,rw","--mount-child","1-2a93e82e011c66d5c7dbd25da5f2e6c1,cache,ro"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/etc/app/settings.json,settings.json","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3ddd35a70a0d7386a5451cd0d5dc3c39,settings.json,ro,file"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-3ddd35a70a0d7386a5451cd0d5dc3c39,/etc/app/settings.json,ro,file"}, +{"flavor":"posix","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/etc/app/settings.json,settings.json","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3ddd35a70a0d7386a5451cd0d5dc3c39,settings.json,ro,file"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["runtime,layer.erofs,a6b34c85-86b9-b646-18dc-28887414f5ab","custom,C:/layers/x.erofs,2089b5af-e1d7-1d94-940d-631f55b9c56a","distro,a//b.erofs,273ccf9a-9d5a-fd30-95b8-921f5d2dc314"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/s h","args":["--","a\u00e9","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a\u00e9","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"","error":"sandbox entrypoint must be an absolute path without spaces"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,a55e4eb1-7eeb-35d5-65b0-f6515125d6da"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["==","---serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","=\u00e9\u00e9","==","\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-","=--serve\u00e9","=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=--serve","a","a\u00e9\u00e9","\u00e9--serve=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9-","a=","--serve","=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=\u00e9"],"hostname":"example","identity":[65534,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,b8ea9084-e245-0269-f314-5038109e52c7","custom,a//b.erofs,84982b13-1f07-8906-6ec1-2925deb75152","distro,C:/layers/x.erofs,d1edc9fd-41f9-e89c-daf1-e00aca033141"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["="],"hostname":"nvx-sandbox","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,7ad560cb-c7d8-f83a-e691-da5182adada5","distro,layer.erofs,82ecd171-e7cf-f834-657d-da4581d68d3f","runtime,C:/layers/x.erofs,a093d2e7-193b-58b1-a44f-cee78f39af18"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=distro,0xd0003000,82ecd171-e7cf-f834-657d-da4581d68d3f nvx_layer=runtime,0xd0004000,a093d2e7-193b-58b1-a44f-cee78f39af18 nvx_layer=custom,0xd0005000,7ad560cb-c7d8-f83a-e691-da5182adada5 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_pids_max=65"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,6a18f14b-7162-bf87-25ae-bd7d60a056c1","custom,layer.erofs,d41e6f2e-e259-8f87-f16a-b0eb536f6387"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["=","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","---servea","=a","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","---serve-","-a-","--serve","==","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","---serve--serve","---serve","a","a--","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","\u00e9\u00e9","-\u00e9--serve"],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-02fb6c5f7c7f5714d48a715e6b5594f2,C:\\host0,ro,file"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"windows","layers":["runtime,layer.erofs,fa3780d0-74bb-4dbb-77d3-8accf1ebeb7a","distro,a//b.erofs,d7ef509b-4a84-cd81-2da1-49e3f43f3b09","custom,layer.erofs,9a364c2e-3436-d5a3-8962-b8d75d6b7d1a"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9a=","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a=","-=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","a--serve","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-","--serve\u00e9\u00e9","=","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv3,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host4","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a\\b.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-5741ea7e06d8828eb3b2e09237ece2e0,C:\\host0,rw,file","--mount-child","1-ebad453409a50bcc524ab6e52c6efbb1,C:\\host1,ro","--mount-child","2-a37404d6fb3eebf56550e9d970c91be4,host2,ro","--mount-child","3-eb11e7902679e44950d75a72a530745c,\\srv3,ro","--mount-child","4-76f1f6798e7b5fb68f79a48ef3252a19,host4,ro","--mount-child","5-4044bb578db2763d7aa67419db26fc00,host5,ro","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\srv3\\secrets","--mount-deny","C:\\fixture\\cwd\\host5\\secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,ecd45898-c6a9-5923-3199-967b9abf7d03","custom,C:/layers/x.erofs,818aa272-63bd-1821-36d6-ccbe0207b303","distro,layer.erofs,35f11375-8399-9dc0-6b2f-0d9545582d31"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9-\u00e9","=\u00e9--serve","=a=","---serve=","a=="],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-8c60c153dff166a0243528dc448165a1,\\srv0,rw,file","--mount-child","1-cfa2e151cef5975fdf2ce2ebf933a2a7,C:\\host1,ro","--mount-deny","C:\\abs\\secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,35f11375-8399-9dc0-6b2f-0d9545582d31 nvx_layer=runtime,0xd0004000,ecd45898-c6a9-5923-3199-967b9abf7d03 nvx_layer=custom,0xd0005000,818aa272-63bd-1821-36d6-ccbe0207b303 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=\u00e9-\u00e9 nvx_arg==\u00e9--serve nvx_arg==a= nvx_arg=---serve= nvx_arg=a== nvx_share=0-8c60c153dff166a0243528dc448165a1,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw,file nvx_share=1-cfa2e151cef5975fdf2ce2ebf933a2a7,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro"}, +{"flavor":"windows","layers":["custom,layer.erofs,aed22c49-c733-1bea-fdd4-f8d4204e7498","runtime,a//b.erofs,316b4f19-337d-4146-8519-565eac369b4f"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["=\u00e9","\u00e9\u00e9","a--serve=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","a---serve"],"hostname":"nvx-sandbox","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:65534"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=runtime,0xd0004000,316b4f19-337d-4146-8519-565eac369b4f nvx_layer=custom,0xd0005000,aed22c49-c733-1bea-fdd4-f8d4204e7498 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=nvx-sandbox nvx_arg==\u00e9 nvx_arg=\u00e9\u00e9 nvx_arg=a--serve= nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg=a---serve nvx_pids_max=65"}, +{"flavor":"windows","layers":["custom,a//b.erofs,c7f97bdf-106d-df55-61a9-eb0faa803f3a","runtime,layer.erofs,83f82d7f-2371-9b7c-0e26-943daaa61eff","distro,a//b.erofs,30b5620f-c623-5963-6872-06e17b596643"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["\u00e9a="],"hostname":"-a","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"windows","layers":["runtime,layer.erofs,22540f15-7117-5e21-5452-55a9556d441a"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"example","identity":[1000,65534],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxx,/srv4,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxx,/srv5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-21f761192d21037ba953943fea2a5d1e,host0,rw","--mount-child","1-a511de7212996480363b20dff1c44eca,\\srv1,ro,file","--mount-child","2-464974427cf0517032b88d59442f420f,\\srv2,ro,file","--mount-child","3-cb13f10cd780ff479572d2415b932273,C:\\host3,ro","--mount-child","4-5811e72dac0195813294e8cd65d8c5ad,\\srv4,rw","--mount-child","5-a9cbb9d9ab2e34c0528321b6c7937bb7,\\srv5,ro,file","--mount-deny","C:\\abs\\secrets"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=runtime,0xd0004000,22540f15-7117-5e21-5452-55a9556d441a nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example nvx_memory_max=1000000000000000000000000000000 nvx_share=0-21f761192d21037ba953943fea2a5d1e,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw nvx_share=1-a511de7212996480363b20dff1c44eca,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file nvx_share=2-464974427cf0517032b88d59442f420f,/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file nvx_share=3-cb13f10cd780ff479572d2415b932273,/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=4-5811e72dac0195813294e8cd65d8c5ad,/share/04xxxxxx,rw nvx_share=5-a9cbb9d9ab2e34c0528321b6c7937bb7,/share/05xxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"windows","layers":["custom,layer.erofs,a3e157e4-255a-4257-8176-51c423463089"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["-\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"nvx-sandbox","identity":[1000,65534],"memory_max":"1000000000000000000000000000000","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:65534","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","--mount-deny","secrets"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,f67d6ca6-1638-7a37-ea24-142db6296541","runtime,layer.erofs,9a0f0e67-9693-840c-31cf-06d86151d54e","custom,C:/layers/x.erofs,ad0acdad-3270-3bc9-0056-44a8918755b1"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-c59d3f59aa0d5e86cc7e5f8e78747e5b,\\srv0,ro","--mount-child","1-5e5d181bf9cbdeff6972d68be4c5fbc6,host1,ro","--mount-deny","C:\\srv0\\secrets","--mount-deny","C:\\fixture\\cwd\\host1\\secrets"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,979d8723-9b7c-97cb-3c0c-5fce959e3cf4"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","aaa","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a-\u00e9"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxx,C:/host3","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host4,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv5,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3c65244a43442e19d033ac4c44a33921,\\srv0,rw","--mount-child","1-a05432aadcd26b32de44dd594bbfe88e,C:\\host1,rw","--mount-child","2-94603f322e418863a6b055db682067b2,host2,ro,file","--mount-child","3-f7a1733e6d3008ff4a7d52b605d07643,C:\\host3,ro","--mount-child","4-c89c8cbc3a6ea932a0f1c082dfefdb61,host4,rw","--mount-child","5-2bea48a2a214c35c5a7324a6fe8cb964,\\srv5,rw","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,37358704-42ba-7bc2-f7c8-5e3081e1ce65"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["=\u00e9="],"hostname":"example","identity":[1000,65534],"memory_max":"0","pids_max":"64","mounts":[],"command_line":" quiet loglevel=0 ","error":"--memory-max must be positive"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,45a311d8-bac0-b48e-88e2-c3f5aaf96468"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"example","identity":[1000,1001],"memory_max":"0","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","error":"--memory-max must be positive"}, +{"flavor":"windows","layers":["custom,layer.erofs,656a4d6d-68ff-9815-1e85-4d1acbb1a7c1","distro,C:/layers/x.erofs,6717cc9a-5a06-db08-681a-da2a855ad0f8","runtime,layer.erofs,80e671ee-cc09-1bd9-e007-0d2d511a571e","custom,a//b.erofs,cbda55fe-5c2b-a8a6-4394-5ef21b718a2c"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["=="],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":"0","mounts":[],"command_line":"quiet","error":"a sandbox requires one to three read-only layers"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,cdc95b00-831b-01c1-feae-4724354542f0"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["--serve--serve-","-a"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":"0","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxx,C:/host3","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host4","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","error":"--memory-max must be positive"}, +{"flavor":"windows","layers":["distro,layer.erofs,580e9941-5f5c-2c30-7ac2-127c4bd8e3c6"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["\u00e9","-"],"hostname":"example","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"windows","layers":["distro,a//b.erofs,8cf91078-9556-d1be-17dc-fae9bcba794b"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":[],"hostname":"Example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxx,C:/host0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host4,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,27c4fb6d-0c0e-4ea4-bbb5-b4d385bb4fe8"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":["--serve-=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9="],"hostname":"a-1","identity":[65534,65534],"memory_max":"268435456","pids_max":null,"mounts":[],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,3cf8bfb6-5fb6-a7bc-1f86-1531a352c85b","runtime,C:/layers/x.erofs,97843d62-9330-fda7-a4fa-ab7eeaac6288","custom,a//b.erofs,7b056600-c055-1ef7-1705-5d36f2d41d94"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["=---serve"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":"duplicate sandbox layer role(s): custom"}, +{"flavor":"windows","layers":["runtime,layer.erofs,50f0c385-3492-d186-d292-0a4727a31653","custom,layer.erofs,a40c25c8-1363-d257-5a07-4bbe91024a98"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["-"],"hostname":"example","identity":[1000,1001],"memory_max":"268435456","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-2c78b6849c761b0a4e53d46dc2504f51,host0,rw,file"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"windows","layers":["runtime,layer.erofs,b8d32eb7-6a38-70fa-b5b1-590ba4479c23"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve","aa","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","--serve"],"hostname":"nvx-sandbox","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:1001"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=runtime,0xd0004000,b8d32eb7-6a38-70fa-b5b1-590ba4479c23 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=nvx-sandbox nvx_arg=--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_arg=--serve nvx_arg=aa nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg=--serve nvx_memory_max=1000000000000000000000000000000"}, +{"flavor":"windows","layers":["distro,layer.erofs,91c1e6d7-63c1-35cc-e117-cd98af9dbee1"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["---serve=","\u00e9-=","--serve=","--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","\u00e9a\u00e9","=","\u00e9","-","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9--servea","--serve=-","aa\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","a","\u00e9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"nvx-sandbox","identity":[1000,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","1000:65534","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,\\srv0,ro"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,adb35722-f24d-bd71-55fd-5392656d3c19"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["--serve==","\u00e9a"],"hostname":"example","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:65534","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=custom,0xd0005000,adb35722-f24d-bd71-55fd-5392656d3c19 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example nvx_arg=--serve== nvx_arg=\u00e9a nvx_pids_max=65"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,b6ef98b9-3131-a98e-89b9-b0273ea7a45c"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-e142a7fa495c57f448191a9c4f614815,host0,ro,file"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,bf51fb7a-ef3c-09cb-432c-5129ae3686bc"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["-\u00e9--serve"],"hostname":"example","identity":[0,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"\u3000quiet\u001c","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,0a4a8a0b-7cbe-af5d-f52c-55bb4e9761d5","distro,layer.erofs,42660302-8b2f-5d60-e6c6-5c5414b85a76"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-"],"hostname":"a-1","identity":[1000,65534],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-02b1781bbb998173cb0f0702399fe784,C:\\host0,ro","--mount-child","1-5d639b04d652d0988014845c0beeaf84,C:\\host1,ro","--mount-deny","C:\\abs\\secrets"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=distro,0xd0003000,42660302-8b2f-5d60-e6c6-5c5414b85a76 nvx_layer=custom,0xd0005000,0a4a8a0b-7cbe-af5d-f52c-55bb4e9761d5 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=a-1 nvx_arg==axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_memory_max=1000000000000000000000000000000 nvx_share=0-02b1781bbb998173cb0f0702399fe784,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-5d639b04d652d0988014845c0beeaf84,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro"}, +{"flavor":"windows","layers":["distro,a//b.erofs,acdd08a1-769a-c3fd-d20b-e8f0b22bdfa5","custom,C:/layers/x.erofs,8cd77740-856f-e40d-b0ce-e8a8dba164b3"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a\\b.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-e0adba7f0f4a955dd08fdf94dad826f5,\\srv0,ro","--mount-child","1-b3a4c7708540894b9b2ce350e616b8d2,\\srv1,rw","--mount-child","2-7dee16ebb0e7995caafd6d31d1e39da5,\\srv2,rw","--mount-deny","C:\\srv2\\secrets"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"windows","layers":["runtime,layer.erofs,dc6b958d-36cb-e08a-b2f0-6945285a3abe"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-4a833434ca95979702b308f4b703568f,C:\\host0,ro,file"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"windows","layers":["custom,layer.erofs,f0916002-b89e-3079-70b4-99fb50379579"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":[],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3c65244a43442e19d033ac4c44a33921,\\srv0,ro","--mount-child","1-5c550bfb07a1737f0da58168a49d9c18,host1,ro","--mount-deny","C:\\srv0\\secrets","--mount-deny","C:\\fixture\\cwd\\host1\\secrets"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,0aed6803-962d-9098-90df-c2cdf949064f","distro,layer.erofs,b84c1d3d-670c-bd20-1b15-9e58a57cc159","custom,layer.erofs,386e7680-0139-2cc3-143a-115e64d7b9a8","runtime,a//b.erofs,75b6a5ef-af13-ef61-f7df-0cef00020c8b"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve","=\u00e9a","a=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","--servea","=a","--a","a--serve","=\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a","--servea\u00e9","--servea","\u00e9","--serve=","a","=","-","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","error":"a sandbox requires one to three read-only layers"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,a54937bf-c3b5-4730-31d0-149d5d967101"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["--serve"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":"0","mounts":[],"command_line":"a\u001cnvx_x=1","error":"--pids-max must be positive"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,c855b135-ff10-b15f-5bc9-43ebe55f0ef4","runtime,C:/layers/x.erofs,5c3148c0-2bf5-3137-a7dd-cafc0d0283ba"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve\u00e9\u00e9","-","--serve","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[],"command_line":"\u3000quiet\u001c","error":"duplicate sandbox layer role(s): runtime"}, +{"flavor":"windows","layers":["custom,a//b.erofs,bd09dc76-81fd-1b51-4250-14657026e68f","distro,layer.erofs,1ef5a04a-eada-2969-9499-c436a26d1f57","runtime,C:/layers/x.erofs,8e704a5e-c05d-5a80-c3db-d9797c548e73"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["-=a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxx,C:/host2","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv4,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-a7466dbc8cc865270f4176f26210113f,\\srv0,ro","--mount-child","1-c5994f1f4c2096feba876ac7f757ddca,host1,rw","--mount-child","2-68fe0c6ea9c3e8404a9caa4da5fa5b4e,C:\\host2,ro","--mount-child","3-e869f244f3af5dd99d6cc7c4fe3440cf,C:\\host3,rw,file","--mount-child","4-ab341b1fbf5adba3c3c81bd1bffa29ec,\\srv4,rw","--mount-child","5-3893625aad5715d7f05fe85e17ca963d,host5,ro","--mount-deny","C:\\srv0\\secrets","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\fixture\\cwd\\host5\\secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,fde517fd-15ae-769b-c408-c4cf745c8c91"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["aa-"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxx,host1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-e1ed8cbca2406f01ea48580ea6726a7d,C:\\host0,ro,file","--mount-child","1-1ec7b2c1648b15995214377627d3ac85,host1,ro,file"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=distro,0xd0003000,fde517fd-15ae-769b-c408-c4cf745c8c91 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=nvx-sandbox nvx_arg=aa- nvx_pids_max=100000000000000000000 nvx_share=0-e1ed8cbca2406f01ea48580ea6726a7d,/share/00xxxxxxxxx,ro,file nvx_share=1-1ec7b2c1648b15995214377627d3ac85,/share/01xxxxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,3c0d4ab6-c5f5-6f5a-d586-c927ae45ad51"],"scratch":"s//x.ext4","entrypoint":"bin/sh","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"a-1","identity":[65534,1001],"memory_max":"268435456","pids_max":"64","mounts":[],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","error":"sandbox entrypoint must be an absolute path without spaces"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,715d50b1-642e-1b8e-2c2f-7ae3bc2e03ac"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":"-1","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"virtfs_dir=/x","error":"--memory-max must be positive"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,2fa9ef3c-075b-32db-6fc2-fdf406368f63"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-2c78b6849c761b0a4e53d46dc2504f51,C:\\host0,ro","--mount-child","1-201065f559735b54e181acb8c29ce165,\\srv1,rw","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"windows","layers":["runtime,a//b.erofs,c7006cfe-c270-a90c-82a4-23f659021ab5"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["\u00e9\u00e9--serve"],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxx,/srv1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv3,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host4,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-a313b58db0a45dc2770ea305c5f9b9f8,\\srv0,ro","--mount-child","1-5edd8a3ad4dce48ad486bbad079bf928,\\srv1,rw","--mount-child","2-543dd5b82e0efa346e8b236337c3f558,C:\\host2,ro,file","--mount-child","3-dd584fa8413827913e5193fc0de5173c,\\srv3,ro","--mount-child","4-370859c2736ad4cce9f5c1c428db5efb,C:\\host4,rw","--mount-child","5-824510f24100cb747d4a0e8800c34d09,C:\\host5,ro,file","--mount-deny","C:\\srv0\\secrets","--mount-deny","C:\\srv3\\secrets","--mount-deny","C:\\host4\\secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["runtime,layer.erofs,9d716755-a832-c8eb-e326-ccb202412244"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[""],"hostname":"example","identity":[65534,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxx,host1,ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":" quiet loglevel=0 ","error":"sandbox arguments must be nonempty and contain no spaces"}, +{"flavor":"windows","layers":["custom,layer.erofs,0fe6f305-a1ba-b6d8-c7c1-e3c4984d3406"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["-"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-c2aae6c377ecef1e25ac1d534719a730,host0,ro","--mount-child","1-5cf6a70839b0add1b4e454dd132ebe48,C:\\host1,rw","--mount-child","2-0b4c0be951ffb9b8913ca35a7f7c49fd,C:\\host2,ro,file","--mount-deny","C:\\fixture\\cwd\\host0\\secrets","--mount-deny","C:\\abs\\secrets"],"kernel_command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9 nvx_sandbox=1 nvx_layer=custom,0xd0005000,0fe6f305-a1ba-b6d8-c7c1-e3c4984d3406 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=- nvx_memory_max=268435456 nvx_pids_max=100000000000000000000 nvx_share=0-c2aae6c377ecef1e25ac1d534719a730,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-5cf6a70839b0add1b4e454dd132ebe48,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw nvx_share=2-0b4c0be951ffb9b8913ca35a7f7c49fd,/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,c97e31d1-65d8-53d1-5b65-0eb79ec81bc7"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":"1000000000000000000000000000000","pids_max":"99999999999999999999","mounts":[],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=runtime,0xd0004000,c97e31d1-65d8-53d1-5b65-0eb79ec81bc7 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=nvx-sandbox nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=100000000000000000000"}, +{"flavor":"windows","layers":["distro,layer.erofs,42b4357a-f158-1dfb-204c-9322e928c494","custom,a//b.erofs,04648c41-7397-c262-b5f6-6578aaf5bc48","runtime,layer.erofs,e219f7e4-5f5e-87ca-6ab1-bc02ce3b2ecd"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["=","a","=","=a","--serve","\u00e9","--serve-=","\u00e9a\u00e9","--serve","-\u00e9=","=\u00e9","=-a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve-","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","a","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","a\u00e9\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","--serve=--serve"],"hostname":"example","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxx,host1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3c65244a43442e19d033ac4c44a33921,host0,ro","--mount-child","1-22d170812412da17d02598b400fddf7a,host1,ro","--mount-deny","C:\\fixture\\cwd\\host0\\secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["runtime,layer.erofs,ce8c30ed-7078-382e-a2f5-58d465c41c45","custom,layer.erofs,9637ff70-e851-069f-1aa1-245704a483a0"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"a-1","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxx,/srv2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxx,C:/host3,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv4","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host5","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-22fd938096be90da02b9f19802e59ee8,C:\\host0,ro,file","--mount-child","1-96a4cc6bed123f930aded13f84153d18,C:\\host1,ro","--mount-child","2-cd61d437c8f852227733feb5541d4de1,\\srv2,rw","--mount-child","3-e675148d11962f657a15c09844148bf7,C:\\host3,rw","--mount-child","4-01d7fa4428fbe3f2b5b260abb101438d,\\srv4,ro","--mount-child","5-b43507f93d4f8e94c190f1fbbf7530f3,host5,ro","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\host3\\secrets","--mount-deny","C:\\srv4\\secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,be50e9fe-48e6-328d-afa8-a42b4a9c4183","custom,C:/layers/x.erofs,f09059aa-8b88-3035-b44a-98c496aeeb34"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"example","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,be50e9fe-48e6-328d-afa8-a42b4a9c4183 nvx_layer=custom,0xd0005000,f09059aa-8b88-3035-b44a-98c496aeeb34 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,235e05b2-3e18-b2d4-d5c0-9b7f4e1c3d83","distro,C:/layers/x.erofs,0c8d13ee-2896-e0ec-5c63-a29f538beb32","custom,a//b.erofs,3a37bc91-a398-5563-9e33-4af0f1dd6953","runtime,layer.erofs,0cb4e2a4-f2c8-4b56-1e85-fc5b35526df9"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["a--serve","=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9"],"hostname":"example","identity":[1000,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host3,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host4,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","error":"a sandbox requires one to three read-only layers"}, +{"flavor":"windows","layers":["custom,a//b.erofs,3d2c838c-80d9-57a2-44fa-f475e3836922","runtime,C:/layers/x.erofs,998632ef-57f3-4330-1f76-d691aad62538"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["-\u00e9a","\u00e9=a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9","--serve=--serve","-","\u00e9","\u00e9aa","-=","-","\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","=","\u00e9a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","-a","--serve-","--\u00e9","--serve--serve\u00e9","--serve","\u00e9"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":" quiet loglevel=0 ","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:65534"],"kernel_command_line":"quiet loglevel=0 nvx_sandbox=1 nvx_layer=runtime,0xd0004000,998632ef-57f3-4330-1f76-d691aad62538 nvx_layer=custom,0xd0005000,3d2c838c-80d9-57a2-44fa-f475e3836922 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=-\u00e9a nvx_arg=\u00e9=a nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_arg=\u00e9 nvx_arg=--serve=--serve nvx_arg=- nvx_arg=\u00e9 nvx_arg=\u00e9aa nvx_arg=-= nvx_arg=- nvx_arg=\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg== nvx_arg=\u00e9a nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- nvx_arg=-a nvx_arg=--serve- nvx_arg=--\u00e9 nvx_arg=--serve--serve\u00e9 nvx_arg=--serve nvx_arg=\u00e9"}, +{"flavor":"windows","layers":["custom,layer.erofs,eb1dacef-aea8-e610-fb13-5ef9f9df5b30","distro,layer.erofs,a8ba9752-dc18-0619-8bf2-e529f721a7f2"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["\u00e9\u00e9","-\u00e9"],"hostname":"example","identity":[1000,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxx,host2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"nvx_sandbox=0","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-4a2713e0cc2fb90cb2126d17e1220eb7,C:\\host0,rw","--mount-child","1-e87c2b5e628be8e704986e9faf3f142a,C:\\host1,rw,file","--mount-child","2-cd61d437c8f852227733feb5541d4de1,host2,rw,file","--mount-deny","C:\\host0\\secrets"],"kernel_error":"nvx_sandbox is owned by the sandbox profile"}, +{"flavor":"windows","layers":["custom,layer.erofs,7352ea9d-bcbd-8444-c72e-978c92a02f8c"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":[],"hostname":"example","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-b9fc329aa75d6c134a6529e4939ff1a6,\\srv0,rw,file","--mount-child","1-33006c89e36575aff5b6d046f6f2f96d,\\srv1,ro,file"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=custom,0xd0005000,7352ea9d-bcbd-8444-c72e-978c92a02f8c nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=example nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=100000000000000000000 nvx_share=0-b9fc329aa75d6c134a6529e4939ff1a6,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw,file nvx_share=1-33006c89e36575aff5b6d046f6f2f96d,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro,file"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,b35a836a-d6e8-5246-0bd0-e94bf90f2a1b"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9a","=","a\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"a-1","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":"-5","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxx,host1,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u001cnvx_x=1","error":"--pids-max must be positive"}, +{"flavor":"windows","layers":["custom,layer.erofs,e422bf00-8ed7-6660-3005-f625faaf5c76","distro,layer.erofs,e60e3700-74a1-87ce-28d6-5850938e08db","runtime,C:/layers/x.erofs,d1cb4c61-cfaf-2d0d-8fa4-53886bd19a99"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["=a\u00e9"],"hostname":"a-1","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:1001","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,\\srv0,ro","--mount-deny","secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,e60e3700-74a1-87ce-28d6-5850938e08db nvx_layer=runtime,0xd0004000,d1cb4c61-cfaf-2d0d-8fa4-53886bd19a99 nvx_layer=custom,0xd0005000,e422bf00-8ed7-6660-3005-f625faaf5c76 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=a-1 nvx_arg==a\u00e9 nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=100000000000000000000"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,b9027c65-8310-af42-e90e-82dd04c81cd6"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["--servea","-a-"],"hostname":"-a","identity":[65534,1001],"memory_max":"268435456","pids_max":null,"mounts":[],"command_line":"\u3000quiet\u001c","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"windows","layers":["custom,a//b.erofs,7b96bc5b-5c40-f0bd-23a6-88ecc3303ef6","runtime,C:/layers/x.erofs,29e83381-2324-76cc-fe6a-35dc68bf5bfb"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,26377940-8fb4-1b48-da39-23355972ed05"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["-","a","--serve=","--serve\u00e9-","a","a=-","=--serve--serve","\u00e9","\u00e9--","-aa","=axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","\u00e9","--serve--serve--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":null,"pids_max":"-5","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,1807eecd-9991-7114-f303-34fad13ffd04","custom,C:/layers/x.erofs,bdb0146a-5e85-c2aa-08e0-4e121c0174a2","distro,C:/layers/x.erofs,c7ff0a0d-02de-b93b-6b95-0462c52389eb"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["-","\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","=-"],"hostname":"a-1","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:65534","--mount","/share/00,host0,rw"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,9c82caeb-7fbf-649d-b7e1-df9580640bfe"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["a-","\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001","--mount","/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,\\srv0,ro","--mount-deny","/abs/secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=custom,0xd0005000,9c82caeb-7fbf-649d-b7e1-df9580640bfe nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=a- nvx_arg=\u00e9axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,381f84d2-d206-d7d7-3081-939562e65fcd"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["\u00e9"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":"0","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","error":"--memory-max must be positive"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,49296bbf-1475-1314-e0fd-e2ac954adb2e"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["a\u00e9","--serve","-","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"," a","--serve---serve","-=-","a=","--","-","=","=--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--serve","\u00e9=\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","-\u00e9","--serve="],"hostname":"example","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxx,host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","error":"sandbox arguments must be nonempty and contain no spaces"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,9b52e70e-70fd-cfb4-72db-c138cb0eca66","custom,C:/layers/x.erofs,2fab978a-97cd-05e9-58ee-1b2d3540ecc4"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9--serve","--serve","--serve\u00e9=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxx,/srv2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u3000quiet\u001c","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-40d3499216dc30dfd1dc7903ab1f23a7,\\srv0,ro","--mount-child","1-d36d1c6d47b3ba4479ff774e75161624,C:\\host1,rw","--mount-child","2-36a6146809192afe31b01ea9b4edcf08,\\srv2,rw","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,9b52e70e-70fd-cfb4-72db-c138cb0eca66 nvx_layer=custom,0xd0005000,2fab978a-97cd-05e9-58ee-1b2d3540ecc4 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg== nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9--serve nvx_arg=--serve nvx_arg=--serve\u00e9= nvx_arg=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=65 nvx_share=0-40d3499216dc30dfd1dc7903ab1f23a7,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-d36d1c6d47b3ba4479ff774e75161624,/share/01xxxxx,rw nvx_share=2-36a6146809192afe31b01ea9b4edcf08,/share/02xxxxxx,rw"}, +{"flavor":"windows","layers":["runtime,C:/layers/x.erofs,1abe6ab6-c726-b2ef-c5ed-b0021cdeaf8f"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":["\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","="],"hostname":"nvx-sandbox","identity":[1000,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxx,host3,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv4,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-2241bf5b96325f17f97a5e4f20104b83,host0,rw,file","--mount-child","1-a511de7212996480363b20dff1c44eca,host1,ro","--mount-child","2-4681817b6f10bf28e089cedd591db827,\\srv2,ro","--mount-child","3-1dbc893b42bf5ce8fa9e0c57d7388c4c,host3,rw,file","--mount-child","4-6a4a47f9f908bdc8a47a66893c722be9,\\srv4,ro,file","--mount-child","5-77f2e48cc1bc25b5ac5e80766ca68527,C:\\host5,rw","--mount-deny","C:\\fixture\\cwd\\host1\\secrets"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"windows","layers":["distro,C:/layers/x.erofs,e1e026dc-6fde-d8b6-16f5-a0408e4582e8","custom,C:/layers/x.erofs,eb7e6df6-d5cd-f69c-346a-e63bdffcf53b","runtime,layer.erofs,7f1f9b36-1d74-9010-3326-834ca2291b5a"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":"268435456","pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv1,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxx,host2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv3,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/04xxxxxxxxxxxxxxxxxxxx,/srv4,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/05xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host5,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-05dc77dd67f9fecceeff493e481ece81,C:\\host0,ro","--mount-child","1-4fd630655626924661ef766fdc56ac60,\\srv1,rw","--mount-child","2-cd61d437c8f852227733feb5541d4de1,host2,ro","--mount-child","3-13ad6783a2331b91d2560e9d64f9655d,\\srv3,rw,file","--mount-child","4-adf24416dbd760c05d1593b372bf4441,\\srv4,rw","--mount-child","5-6baf0053ba2c8dc632afcc717c99ded3,C:\\host5,rw","--mount-deny","C:\\srv1\\secrets","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["runtime,layer.erofs,3d413624-bdca-383d-8cb0-83c4baa05bfe","distro,C:/layers/x.erofs,30c51bc6-62c8-8b78-d2c7-eee2419698b9"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["-a--serve"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,1001],"memory_max":"-1","pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"a\u0000b","error":"--memory-max must be positive"}, +{"flavor":"windows","layers":["distro,a//b.erofs,8b55b36a-20da-fa71-ffcd-4e51b5e10357","runtime,layer.erofs,59ed1876-a053-3a73-e8e3-a7b96c93fa47","custom,a//b.erofs,e3f5c114-9242-8806-a00c-d69f4977fa56"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["--serve--","="],"hostname":"nvx-sandbox","identity":[0,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxx,host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host2,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"virtfs_dir=/x","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"windows","layers":["custom,a//b.erofs,9e691881-0300-ca52-2c50-e8c83e948dcf","runtime,a//b.erofs,02c9b13d-015e-2e21-3d98-f8a7d27a7b17","distro,a//b.erofs,476b8313-9c4a-1345-0d3b-ec2d9b5bd505"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":["\u00e9\u00e9=","\u00e9"],"hostname":"example","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[{"value":"/share/00xxxxx,C:/host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"a\u0000b","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a\\b.erofs,ro","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-5199f194ddda981cceff0332039ee7c6,C:\\host0,ro","--mount-child","1-db7e49c20b31d0686b4bcd4a20a7de6c,host1,rw","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets"],"kernel_error":"kernel command line contains an embedded NUL"}, +{"flavor":"windows","layers":["distro,layer.erofs,9aafe5c8-9f83-3396-72e7-c5c7d3b81854","custom,a//b.erofs,ff999368-15f9-fab7-86fe-020310920f88","runtime,layer.erofs,b7f48d84-d46a-24ec-c6f3-a70eb87faf46"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["a--serve","--serve","--serve--","a","=="],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":"1000000000000000000000000000000","pids_max":"64","mounts":[],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:1001"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,9aafe5c8-9f83-3396-72e7-c5c7d3b81854 nvx_layer=runtime,0xd0004000,b7f48d84-d46a-24ec-c6f3-a70eb87faf46 nvx_layer=custom,0xd0005000,ff999368-15f9-fab7-86fe-020310920f88 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=a--serve nvx_arg=--serve nvx_arg=--serve-- nvx_arg=a nvx_arg=== nvx_memory_max=1000000000000000000000000000000 nvx_pids_max=65"}, +{"flavor":"windows","layers":["distro,layer.erofs,98168019-db31-b55e-c718-6c47562e9e46"],"scratch":"s//x.ext4","entrypoint":"/bin/workload","args":["="],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,98168019-db31-b55e-c718-6c47562e9e46 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg== nvx_pids_max=100000000000000000000"}, +{"flavor":"windows","layers":["custom,layer.erofs,9ea29769-eccc-6ffc-ed84-a34742514a38","distro,C:/layers/x.erofs,0c405b57-4609-3001-3b79-876fc48932ff"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["=--servea","--serveaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-a","-\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","-","-=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=a","--","--serve","--serve","-a","=","--serve--serve","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="],"hostname":"a-1","identity":[1000,65534],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-52bb4bef8f9da7c9469d7528821375fb,C:\\host0,ro","--mount-child","1-b9216949e403b48c7029a83f5853e685,host1,ro,file","--mount-child","2-eb23e0baf016a233a77ff9d1c2246ea9,\\srv2,rw,file"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["distro,a//b.erofs,4eb9daf7-6320-096e-1b33-8ff6c19d9fb5"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=--serve=","\u00e9","\u00e9"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host1,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-a7738b94c784eb9f25519afb93eb632c,C:\\host0,ro,file","--mount-child","1-beb5cac71bf749dd6c188b6843897bcd,host1,rw,file","--mount-child","2-a37404d6fb3eebf56550e9d970c91be4,\\srv2,ro,file"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["runtime,layer.erofs,3874f07d-eb38-4ccd-d920-f80fbbe3b5f0"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9-"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxx,C:/host0","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:1001","--mount","/share/00xxxxxxxxxxxxxx,C:\\host0,ro"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=runtime,0xd0004000,3874f07d-eb38-4ccd-d920-f80fbbe3b5f0 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/workload nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=\u00e9- nvx_pids_max=65"}, +{"flavor":"windows","layers":["distro,layer.erofs,9b160fb3-4e90-e71d-6ade-9dc9405aa9f5"],"scratch":"scratch.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[65534,1001],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01,C:/host1,rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"quiet","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-b1b1a0197f57610192a87247301356fd,host0,ro","--mount-child","1-932de0c49c6028fcd06c916b12e85dd0,C:\\host1,rw","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\host1\\secrets"],"kernel_command_line":"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,9b160fb3-4e90-e71d-6ade-9dc9405aa9f5 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_pids_max=100000000000000000000 nvx_share=0-b1b1a0197f57610192a87247301356fd,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,ro nvx_share=1-932de0c49c6028fcd06c916b12e85dd0,/share/01,rw"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,40bfe2b3-c3d1-27bb-eaf5-90131bec9b6d","distro,layer.erofs,99d9626a-0b52-f6ea-b8ce-902131f7af0a","runtime,a//b.erofs,54564aca-d5d6-e34f-5382-66bf6146e432"],"scratch":"s//x.ext4","entrypoint":"/bin/sh","args":["\u00e9=","=a","--servexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=-","=--serve","--serve=--serve","a","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-","--serve","-a\u00e9","a==","-==","-","\u00e9","-a","=\u00e9\u00e9","\u00e9=\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=\u00e9"],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["distro,a//b.erofs,f42346ad-28e5-1e35-42f2-0362b5f9da80","runtime,a//b.erofs,6f00cdad-33b4-8768-d835-7ca07babe0e5"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":[],"hostname":"-a","identity":[65534,65534],"memory_max":"-1","pids_max":null,"mounts":[],"command_line":" quiet loglevel=0 ","error":"sandbox hostname must be a lowercase RFC 1123 label up to 63 characters"}, +{"flavor":"windows","layers":["custom,C:/layers/x.erofs,b42d715a-326e-eabe-c3e5-e4c4159865e1","runtime,C:/layers/x.erofs,e187ff8f-bf00-817c-9f43-0131c7c6c5b3","distro,C:/layers/x.erofs,e25afba1-b959-b870-41b2-087b91666dbb"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9-=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"hostname":"example","identity":[65534,1001],"memory_max":"268435456","pids_max":"64","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxx,/srv1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv2","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","runtime:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","custom:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-acf644408abeff938806e3129864f602,C:\\host0,ro","--mount-child","1-546b7fd4256b09d559c8697c06ac533c,\\srv1,rw","--mount-child","2-fef4721029507bcaa3789bb592da710c,\\srv2,ro","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\srv2\\secrets"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["custom,layer.erofs,542a5777-baad-ecbb-cf4f-c9d582336147","distro,C:/layers/x.erofs,75f56217-c521-b77b-e792-f88901fa3b68","runtime,C:/layers/x.erofs,8cd07922-d2db-a8ef-26e2-fbc9500af744"],"scratch":"scratch.ext4","entrypoint":"/bin/workload","args":["\u00e9","a-=","=a","\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00e9=--serve","\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","-=","\u00e9\u00e9a","axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=--serve","--serve\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve\u00e9","a-\u00e9","a=--serve","\u00e9=-","--serve","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","=","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa","a"],"hostname":"example","identity":[0,1001],"memory_max":"1000000000000000000000000000000","pids_max":null,"mounts":[],"command_line":"a\u0000b","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"windows","layers":["custom,layer.erofs,6472274c-203d-9af4-5426-bc1fe2a9da7f","distro,layer.erofs,9bd8619b-d800-af1e-737c-24f861ab86f9","runtime,layer.erofs,79294eb6-ba2f-fc82-d85c-ae57c80d9e5f"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["--serve"],"hostname":"example","identity":[65534,1001],"memory_max":null,"pids_max":"99999999999999999999","mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,/srv0,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host1,rw","denied":["/abs/secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","runtime:file:layer.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-dcb90d56303d1d52c1115cf2b5537551,\\srv0,rw","--mount-child","1-a05432aadcd26b32de44dd594bbfe88e,C:\\host1,rw","--mount-deny","C:\\abs\\secrets","--mount-deny","C:\\abs\\secrets"],"kernel_command_line":"\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9 nvx_sandbox=1 nvx_layer=distro,0xd0003000,9bd8619b-d800-af1e-737c-24f861ab86f9 nvx_layer=runtime,0xd0004000,79294eb6-ba2f-fc82-d85c-ae57c80d9e5f nvx_layer=custom,0xd0005000,6472274c-203d-9af4-5426-bc1fe2a9da7f nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=example nvx_arg=--serve nvx_pids_max=100000000000000000000 nvx_share=0-dcb90d56303d1d52c1115cf2b5537551,/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw nvx_share=1-a05432aadcd26b32de44dd594bbfe88e,/share/01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,rw"}, +{"flavor":"windows","layers":["custom,a//b.erofs,31a4c298-cabd-b7f5-2606-e4c0c083cb44"],"scratch":"C:/s/scratch.ext4","entrypoint":"/usr/bin/python3","args":["-\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9\u00e9"],"hostname":"example","identity":[65534,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"virtfs_dir=/x","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_error":"virtfs_dir is owned by the sandbox --mount option"}, +{"flavor":"windows","layers":["custom,a//b.erofs,1243acfd-655b-e052-eb75-57e9e5bc6825"],"scratch":"s//x.ext4","entrypoint":"/usr/bin/python3","args":["a-","-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa"],"hostname":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","identity":[1000,65534],"memory_max":null,"pids_max":"99999999999999999999","mounts":[],"command_line":"","arguments":["--machine","microvm","--microvm-sandbox-block","custom:file:a\\b.erofs,ro","--microvm-sandbox-block","scratch:file:s\\x.ext4","--microvm-workload-identity","1000:65534"],"kernel_command_line":"nvx_sandbox=1 nvx_layer=custom,0xd0005000,1243acfd-655b-e052-eb75-57e9e5bc6825 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/usr/bin/python3 nvx_hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa nvx_arg=a- nvx_arg=-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa nvx_pids_max=100000000000000000000"}, +{"flavor":"windows","layers":["custom,layer.erofs,09795141-58d4-10d2-6c91-f38c58ec1049","distro,C:/layers/x.erofs,1ecbf796-df8f-416b-9d18-ea465ac64fc2","runtime,a//b.erofs,e6ade518-bb51-b59b-9485-62c504e60773"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["=aa"],"hostname":"a-1","identity":[65534,1001],"memory_max":null,"pids_max":null,"mounts":[{"value":"/share/00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,C:/host0,ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"a\u001cnvx_x=1","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:C:\\layers\\x.erofs,ro","--microvm-sandbox-block","runtime:file:a\\b.erofs,ro","--microvm-sandbox-block","custom:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:C:\\s\\scratch.ext4","--microvm-workload-identity","65534:1001","--mount-aggregate","/run/nvx/shares","--mount-child","0-4a2713e0cc2fb90cb2126d17e1220eb7,C:\\host0,ro,file"],"kernel_error":"nvx_x is owned by the sandbox profile"}, +{"flavor":"windows","layers":["custom,a//b.erofs,5abed48a-edba-04dc-9b45-38bf7d4e05b5","distro,C:/layers/x.erofs,97cc1592-5175-c246-e89e-a9e378e97844"],"scratch":"C:/s/scratch.ext4","entrypoint":"/bin/sh","args":["a-","\u00e9\u00e9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","a==","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","--serve\u00e9"],"hostname":"nvx-sandbox","identity":[65534,0],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"a\u0000b","error":"sandbox workload identity must be a non-root UID:GID"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount","/workspace,host,rw"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount","/workspace,host,rw"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/srv/cache,cache","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-c52ddf65534b7b46035084358ab7902b,host,rw","--mount-child","1-2a93e82e011c66d5c7dbd25da5f2e6c1,cache,ro"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-c52ddf65534b7b46035084358ab7902b,/workspace,rw nvx_share=1-2a93e82e011c66d5c7dbd25da5f2e6c1,/srv/cache,ro"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/workspace,host,rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"value":"/srv/cache,cache","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-c52ddf65534b7b46035084358ab7902b,host,rw","--mount-child","1-2a93e82e011c66d5c7dbd25da5f2e6c1,cache,ro"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/etc/app/settings.json,settings.json","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3ddd35a70a0d7386a5451cd0d5dc3c39,settings.json,ro,file"],"kernel_command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nvx_sandbox=1 nvx_layer=distro,0xd0003000,11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-3ddd35a70a0d7386a5451cd0d5dc3c39,/etc/app/settings.json,ro,file"}, +{"flavor":"windows","layers":["distro,layer.erofs,11111111-1111-1111-1111-111111111111"],"scratch":"scratch.ext4","entrypoint":"/bin/sh","args":[],"hostname":"nvx-sandbox","identity":[65534,65534],"memory_max":null,"pids_max":null,"mounts":[{"value":"/etc/app/settings.json,settings.json","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}],"command_line":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","arguments":["--machine","microvm","--microvm-sandbox-block","distro:file:layer.erofs,ro","--microvm-sandbox-block","scratch:file:scratch.ext4","--microvm-workload-identity","65534:65534","--mount-aggregate","/run/nvx/shares","--mount-child","0-3ddd35a70a0d7386a5451cd0d5dc3c39,settings.json,ro,file"],"kernel_error":"sandbox kernel command line exceeds its 1024-byte x86 budget; attach fewer --mount shares or use shorter targets and arguments"} +] +} diff --git a/nvx-rs/tests/fixtures/sandbox_options.json b/nvx-rs/tests/fixtures/sandbox_options.json new file mode 100644 index 000000000..3d7d2627d --- /dev/null +++ b/nvx-rs/tests/fixtures/sandbox_options.json @@ -0,0 +1,901 @@ +{ +"generator": "scripts/nvx_rs_fixtures.py", +"python": "3.14", +"cwds": {"posix": "/fixture/cwd", "windows": "C:\\fixture\\cwd"}, +"run_share_targets": [ +{"targets":["/workspace","/opt/hostedtoolcache","/srv/cache"],"error":null}, +{"targets":["/workspace","/workspace/cache"],"error":"--mount targets /workspace and /workspace/cache overlap; a share cannot hide another"}, +{"targets":["/run/nvx","/b"],"error":"--mount targets /run/nvx/shares and /run/nvx overlap; a share cannot hide another"}, +{"targets":["/run/nvx/shares/x"],"error":"--mount targets /run/nvx/shares and /run/nvx/shares/x overlap; a share cannot hide another"}, +{"targets":["/a b","/b"],"error":"invalid --mount target '/a b': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["relative","/b"],"error":"invalid --mount target 'relative': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"error":null}, +{"targets":[],"error":null}, +{"targets":["\u3000\u3000","/\u001cetcmachine-id"," \u3000\u00e9.\u001c"],"error":"invalid --mount target '\\u3000\\u3000': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u0000\\work\u3000"],"error":"invalid --mount target '\\x00\\\\work\\u3000': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["proc","/,"],"error":"invalid --mount target 'proc': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[".,\u0000 ","\\run","\u3000"],"error":"invalid --mount target '.,\\x00 ': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["run=","\u3000"],"error":"invalid --mount target 'run=': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u00e9",".machine-id","/\\work"],"error":"invalid --mount target '\u00e9': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[".. "],"error":"invalid --mount target '.. ': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[",a","\u0000shares"],"error":"invalid --mount target ',a': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u0000proc",".shares","proc"],"error":"invalid --mount target '\\x00proc': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["nvx"],"error":"invalid --mount target 'nvx': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["nvxprocmachine-id","shares"],"error":"invalid --mount target 'nvxprocmachine-id': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["etca\u001c."],"error":"invalid --mount target 'etca\\x1c.': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u3000shares etcetc,",",proc\u001ca","shares.\u00e9shares \u3000"],"error":"invalid --mount target '\\u3000shares etcetc,': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\\ work"],"error":"invalid --mount target '\\\\ work': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["=.,\u00e9,shares"],"error":"invalid --mount target '=.,\u00e9,shares': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["="],"error":"invalid --mount target '=': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[" \u3000\u3000"],"error":"invalid --mount target ' \\u3000\\u3000': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[".run=proc=","work..\u0000\u0000","nvx/\\..\u0000."],"error":"invalid --mount target '.run=proc=': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u0000\u0000runetc/shares"],"error":"invalid --mount target '\\x00\\x00runetc/shares': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u0000machine-id.","=a "],"error":"invalid --mount target '\\x00machine-id.': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["proc.\u3000runmachine-id","machine-ida,\\a"],"error":"invalid --mount target 'proc.\\u3000runmachine-id': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u00e9.\\\\","\u3000"],"error":"invalid --mount target '\u00e9.\\\\\\\\': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["run"],"error":"invalid --mount target 'run': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["run,.. machine-id","worksharesnvx="],"error":"invalid --mount target 'run,.. machine-id': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u001crunmachine-id\u3000work"],"error":"invalid --mount target '\\x1crunmachine-id\\u3000work': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["awork,\u00e9..,"],"error":"invalid --mount target 'awork,\u00e9..,': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["proc=","work machine-id,\u001c"],"error":"invalid --mount target 'proc=': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["shares"],"error":"invalid --mount target 'shares': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u3000","..\\"],"error":"invalid --mount target '\\u3000': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["nvx,","\u3000 arunaa","work"],"error":"invalid --mount target 'nvx,': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[".,ashares\u00e9 ","/..\\. .."],"error":"invalid --mount target '.,ashares\u00e9 ': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[",nvx\\proc=","run==","work..,="],"error":"invalid --mount target ',nvx\\\\proc=': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[",\u001c"],"error":"invalid --mount target ',\\x1c': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["=\u001cnvx",".","\\"],"error":"invalid --mount target '=\\x1cnvx': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u001cshares\u0000","nvx/machine-id\u0000","run\u00e9\u00e9machine-idmachine-id"],"error":"invalid --mount target '\\x1cshares\\x00': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["worknvxshares",".\u0000","nvxshares=shares"],"error":"invalid --mount target 'worknvxshares': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[",proc.machine-id","\\procawork\u0000"],"error":"invalid --mount target ',proc.machine-id': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":[".nvx",",sharesnvxrun,","/machine-idmachine-id .."],"error":"invalid --mount target '.nvx': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\\etcworknvx","\\etc \u00e9\u001c"],"error":"invalid --mount target '\\\\etcworknvx': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["\u0000nvx","/"],"error":"invalid --mount target '\\x00nvx': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/work","/a/data-1/run","/nvx/\u00e9","/work/data-1/data-1"],"error":"--mount targets /work and /work/data-1/data-1 overlap; a share cannot hide another"}, +{"targets":["/srv"],"error":null}, +{"targets":["/srv","/a/cache/run","/srv/a/srv"],"error":"--mount targets /srv and /srv/a/srv overlap; a share cannot hide another"}, +{"targets":["/data-1","/cache/a/a","/nvx","/etc"],"error":null}, +{"targets":["/srv","/etc/srv"],"error":null}, +{"targets":["/srv/srv/etc","/a/a/cache","/srv/etc/etc","/data-1/cache/etc"],"error":null}, +{"targets":["/nvx/cache/etc"],"error":null}, +{"targets":["/a/a","/work"],"error":null}, +{"targets":["/srv/nvx","/work/nvx/etc","/work","/work/data-1"],"error":"--mount targets /work/nvx/etc and /work overlap; a share cannot hide another"}, +{"targets":["/nvx/nvx/cache","/run"],"error":"--mount targets /run/nvx/shares and /run overlap; a share cannot hide another"}, +{"targets":["/aetc/data-1","/run/\u001c","/srv/etc/\u00e9"],"error":"invalid --mount target '/run/\\x1c': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/cache/data-1","/etc"],"error":null}, +{"targets":["/srv/work/\u00e9","/work/run","/nvx"],"error":null}, +{"targets":["/a/srv","/srv/\u00e9","/srv/work","/nvx/cache/etc"],"error":null}, +{"targets":["/data-1/srv","/cache","/srv","/\u00e9/\u00e9/a"],"error":null}, +{"targets":["/run/run/cache"],"error":null}, +{"targets":["/a//","/cache","/\u00e9","/srv/work"],"error":"invalid --mount target '/a//': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/work/a","/nvx/\u00e9","/work/srv/data-1","/data-1/\u00e9/srv"],"error":null}, +{"targets":["/srv/data-1/\u00e9","/etc/nvx","/srv/cache","/\u001c\\/work"],"error":"invalid --mount target '/\\x1c\\\\/work': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/work/nvx","/srv/data-1","/srv/etc","/\u0000"],"error":"invalid --mount target '/\\x00': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/a/nvx/etc","/run/data-1/\u00e9","/etc/nvx/cache","/etc/work/nvx"],"error":null}, +{"targets":["/\u00e9/etc/etc","/work"],"error":null}, +{"targets":["//srv"],"error":"invalid --mount target '//srv': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/cache/work/nvx"],"error":null}, +{"targets":["/run/run"],"error":null}, +{"targets":["/\\","/cache","/nvx/data-1/\u00e9","/\u00e9/etc"],"error":"invalid --mount target '/\\\\': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/\u00e9","/ /\u00e9","/cache/srv/work","/work"],"error":"invalid --mount target '/ /\u00e9': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/srv/data-1/nvx"],"error":null}, +{"targets":["/data-1","/run"],"error":"--mount targets /run/nvx/shares and /run overlap; a share cannot hide another"}, +{"targets":["/data-1/cache/srv","/\u00e9","/a/data-1/etc"],"error":null}, +{"targets":["/","/data-1/data-1/etc"],"error":"invalid --mount target '/': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/run/a/a","/nvx/a","/\u00e9"],"error":null}, +{"targets":["/nvx/work"],"error":null}, +{"targets":["/data-1/data-1","/a/nvx","/srv"],"error":null}, +{"targets":["/run"],"error":"--mount targets /run/nvx/shares and /run overlap; a share cannot hide another"}, +{"targets":["/srv"],"error":null}, +{"targets":["/.\\","/srv","/work/\u00e9/\u00e9","/=/etc/\u00e9"],"error":"invalid --mount target '/.\\\\': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/nvx/a/a","/\u00e9"],"error":null}, +{"targets":["/a","/data-1/nvx","/etc/srv/run\u001cwork","/work/srv/cache"],"error":"invalid --mount target '/etc/srv/run\\x1cwork': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/data-1/srv","/run/work/cache","/etc/a/\u3000nvx"],"error":"invalid --mount target '/etc/a/\\u3000nvx': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/cache/a/a","/a/etc","/run/srv/work"],"error":null}, +{"targets":["/cache","/nvx/etc/a","/run/run/srv","/cache/data-1/a"],"error":"--mount targets /cache and /cache/data-1/a overlap; a share cannot hide another"}, +{"targets":["/etc","/cache","/work","/data-1/work"],"error":null}, +{"targets":["/etc/\u00e9","/data-1/srv/run","/a/work"],"error":null}, +{"targets":["/cache/srv/data-1","/\u00e9/a/\u00e9","/cache/srv"],"error":"--mount targets /cache/srv/data-1 and /cache/srv overlap; a share cannot hide another"}, +{"targets":["/cache/nvx/\u00e9","/run/a/etc"],"error":null}, +{"targets":["/./nvx","/\u00e9","/work/etc","/nvx/srv"],"error":"invalid --mount target '/./nvx': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/work","/\u00e9/work"],"error":null}, +{"targets":["/\u00e9/srv","/cache/\u0000","/data-1/cache"],"error":"invalid --mount target '/cache/\\x00': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/work/run/run","/work","/etc"],"error":"--mount targets /work/run/run and /work overlap; a share cannot hide another"}, +{"targets":["/etc","/etc/cache/srv","/data-1/srv"],"error":"--mount targets /etc and /etc/cache/srv overlap; a share cannot hide another"}, +{"targets":["/data-1/nvx/nvx","/srv/work"],"error":null}, +{"targets":["/run/run","/work"],"error":null}, +{"targets":["/cache/cache/nvx","//\\","/run/run","/cache/data-1/run"],"error":"invalid --mount target '//\\\\': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/srv/work","/nvx/data-1","/nvx","/etc/a/cache"],"error":"--mount targets /nvx/data-1 and /nvx overlap; a share cannot hide another"}, +{"targets":["/\u00e9","/\\\\","/a/work/srv"],"error":"invalid --mount target '/\\\\\\\\': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/etc/\u00e9","/run/a/etc"],"error":null}, +{"targets":["/etc/a"],"error":null}, +{"targets":["/run/cache","/\u00e9/nvx/data-1","/a/\u00e9","/run/run"],"error":null}, +{"targets":["/nvx/srv","/data-1/run"],"error":null}, +{"targets":["/etc/etc/\u00e9"],"error":null}, +{"targets":["/srv/run/srv","/\u00e9/run/etc","/work/data-1","/nvx/etc"],"error":null}, +{"targets":["/nvx/run/etc","/etc/data-1/run","/cache","/run/a/data-1"],"error":null}, +{"targets":["/\u00e9/work","/nvx/nvx","/etc/run"],"error":null}, +{"targets":["/run/data-1/cache","/procrun\u001c/a/a"],"error":"invalid --mount target '/procrun\\x1c/a/a': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/a/\u00e9/work","/nvx/run"],"error":null}, +{"targets":["/","/cache/cache/run"],"error":"invalid --mount target '/': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"}, +{"targets":["/cache/cache","/data-1/run","/cache/\u00e9"],"error":null}, +{"targets":["/nvx"],"error":null}, +{"targets":["/data-1/work/data-1"],"error":null}, +{"targets":["/cache/srv","/srv"],"error":null}, +{"targets":["/run","/work/run/cache","/cache/srv/cache","/"],"error":"--mount targets /run/nvx/shares and /run overlap; a share cannot hide another"}, +{"targets":["/data-1/etc/srv","/srv/nvx/nvx"],"error":null}, +{"targets":["/\u00e9work","/work/\u00e9/work"],"error":null}, +{"targets":["/data-1/run/cache","/data-1/srv/data-1"],"error":null}, +{"targets":["/cache"],"error":null}, +{"targets":["/work/run"],"error":null}, +{"targets":["/srv/work/nvx"],"error":null}, +{"targets":["/\u00e9","/nvx","/nvx/run","/run/run"],"error":"--mount targets /nvx and /nvx/run overlap; a share cannot hide another"}, +{"targets":["/\u00e9","//"],"error":"invalid --mount target '//': with several shares, or a shared file, each target must be an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','"} +], +"sandbox_mounts": [ +{"flavor":"posix","mounts":["/workspace,work,rw","/opt/hostedtoolcache,tools,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",1,"credentials"]],"owner":null,"files":[],"result":[{"guest_target":"/workspace","host_path":"work","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/opt/hostedtoolcache","host_path":"tools","access":"ro","denied":["credentials"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/w,work,rw"],"policy":[["denied",null,"secrets"]],"owner":"caller","files":[],"result":[{"guest_target":"/w","host_path":"work","access":"rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/workspace,work,rw","/opt/hostedtoolcache,tools"],"policy":[["allowed",null,"logs"],["denied",null,"secrets"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/etc/app/settings.json,settings.json,rw"],"policy":[["writable",0,"settings.json"]],"owner":null,"files":["settings.json"],"error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /etc/app/settings.json shares a file, whose mode applies to the whole file"}, +{"flavor":"posix","mounts":[],"policy":[["denied",null,"x"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":[],"policy":[["writable",null,"early"]],"owner":null,"files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,f.json"],"policy":[],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,tools,rw","/m1,work","/m2,tools,ro"],"policy":[["writable",null,"early"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"logs"]],"owner":"vmm","files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,tools,rw","/m1,f.json,ro"],"policy":[["writable",0,"out"],["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":[],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["allowed",0,""]],"owner":null,"files":["f.json"],"error":"sandbox mount allowed paths must be nonempty"}, +{"flavor":"posix","mounts":["/m0,f.json,rw","/m1,f.json"],"policy":[],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"posix","mounts":["/m0,f.json"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,rw"],"policy":[],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"}]}, +{"flavor":"posix","mounts":["/m0,f.json,rw","/m1,tools"],"policy":[["denied",1,"logs"],["denied",1,"/d"],["allowed",1,"logs/payloads"],["denied",1,""]],"owner":null,"files":["f.json"],"error":"sandbox mount denied paths must be nonempty"}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":null,"files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,C:/x,ro"],"policy":[["denied",0,"/d"],["denied",0,"logs"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["/d","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x,rw","/m1,work,ro","/m2,C:/x"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",2,"/d"],["denied",2,"logs"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"rw","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"C:/x","access":"ro","denied":["/d","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work,ro","/m1,C:/x,rw"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["writable",1,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"C:/x","access":"rw","denied":["logs"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,tools","/m1,tools,rw","/m2,work,rw"],"policy":[["denied",2,"logs"],["denied",2,"secrets"],["writable",2,"out"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["logs","secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json"],"policy":[],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["writable",0,"out"]],"owner":"vmm","files":["f.json"],"error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /m0 shares a file, whose mode applies to the whole file"}, +{"flavor":"posix","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"secrets"],["denied",0,"/d"]],"owner":"vmm","files":["f.json"],"error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /m0 shares a file, whose mode applies to the whole file"}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["denied",0,"secrets"],["denied",0,"logs"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d","secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["logs","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,rw","/m1,work,ro","/m2,f.json"],"policy":[["denied",0,"/d"],["denied",0,"secrets"],["writable",0,"out"],["denied",1,"logs"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"logs"],["allowed",2,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["/d","secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work","/m1,f.json","/m2,f.json,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"/d"],["allowed",1,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,ro"],"policy":[["denied",0,"logs"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x","/m1,f.json","/m2,work,ro"],"policy":[["allowed",null,"early"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["allowed",2,""]],"owner":null,"files":["f.json"],"error":"with several --mount options, each --mount-allow must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,work,rw"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["writable",0,"out"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"rw","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,ro","/m1,tools,ro"],"policy":[["denied",1,"/d"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,tools","/m1,C:/x","/m2,work,rw"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["denied",1,"logs"],["denied",1,"/d"],["denied",2,"logs"],["allowed",2,"logs/payloads"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"C:/x","access":"ro","denied":["logs","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x,rw"],"policy":[["allowed",null,"early"],["writable",0,"out"]],"owner":null,"files":["f.json"],"error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"posix","mounts":["/m0,work,rw","/m1,C:/x","/m2,C:/x,rw"],"policy":[["writable",0,"out"],["denied",2,"/d"],["denied",2,""]],"owner":"vmm","files":[],"error":"sandbox mount denied paths must be nonempty"}, +{"flavor":"posix","mounts":["/m0,C:/x","/m1,C:/x,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["denied",1,"/d"],["allowed",1,"logs"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:/x","access":"ro","denied":["logs","/d"],"allowed":["logs"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[["allowed",null,"early"]],"owner":"caller","files":[],"error":"with several --mount options, each --mount-allow must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,C:/x,ro","/m1,f.json"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"posix","mounts":["/m0,tools,rw","/m1,work,ro","/m2,tools,rw"],"policy":[["denied",null,"early"],["denied",0,"secrets"],["denied",0,"logs"],["denied",1,"logs"],["denied",2,"logs"],["denied",2,"secrets"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["writable",0,"out"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["logs","/d"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,work,ro"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["writable",0,"logs"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"error":"--mount-write narrows the writes of a read-write share; --mount /m0 is read-only"}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[["denied",null,"early"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["early"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,ro","/m1,f.json,rw"],"policy":[],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,work","/m2,tools,ro"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["denied",1,"/d"],["allowed",1,"logs/payloads"],["denied",2,"secrets"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"tools","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work,ro","/m1,C:/x,rw","/m2,work,ro"],"policy":[["denied",1,"logs"],["allowed",1,"logs/payloads"],["writable",1,"out"],["denied",2,"logs"],["denied",2,"/d"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:/x","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"ro","denied":["logs","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json"],"policy":[],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,work","/m1,work,rw"],"policy":[["denied",0,"secrets"],["denied",1,"logs"],["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,tools,ro","/m1,tools,rw","/m2,f.json,ro"],"policy":[["writable",1,"out"],["allowed",1,""],["denied",2,"/d"]],"owner":"vmm","files":[],"error":"sandbox mount allowed paths must be nonempty"}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,tools,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["denied",1,"secrets"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,work,ro","/m1,f.json,ro"],"policy":[["denied",null,"early"],["writable",0,"logs"],["denied",1,"/d"]],"owner":"vmm","files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,C:/x,ro","/m1,C:/x","/m2,C:/x,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"C:/x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":[],"policy":[["allowed",null,"early"]],"owner":"caller","files":[],"error":"with several --mount options, each --mount-allow must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,work,ro","/m1,work"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,f.json"],"policy":[],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,ro","/m1,f.json,rw","/m2,f.json"],"policy":[["denied",1,"/d"],["denied",1,"secrets"],["denied",2,"logs"],["denied",2,"/d"],["allowed",2,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"rw","denied":["/d","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":["logs","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,rw","/m1,work"],"policy":[["writable",0,"out"],["allowed",0,"logs"]],"owner":"caller","files":["f.json"],"error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"posix","mounts":["/m0,work,rw","/m1,tools,rw"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",1,"secrets"],["denied",1,"/d"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"rw","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,tools,ro"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["writable",0,""]],"owner":"caller","files":[],"error":"sandbox mount writable paths must be nonempty"}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["allowed",0,"logs"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work","/m1,C:/x,rw","/m2,f.json,ro"],"policy":[["denied",null,"early"],["denied",0,"logs"],["denied",0,"secrets"],["denied",1,"/d"],["denied",2,"secrets"],["denied",2,"/d"],["allowed",2,"logs/payloads"]],"owner":"caller","files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,f.json"],"policy":[],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,tools,ro","/m1,f.json,rw"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"posix","mounts":["/m0,C:/x","/m1,tools,rw"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["writable",1,"logs"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["logs"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,C:/x,rw"],"policy":[["denied",1,"secrets"],["allowed",1,"logs/payloads"],["writable",1,"out"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:/x","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,tools,rw","/m1,tools,ro","/m2,C:/x,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",1,"logs"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"logs"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"C:/x","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,f.json","/m2,work,ro"],"policy":[],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m2","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,C:/x,rw","/m2,f.json,rw"],"policy":[["denied",1,"secrets"],["denied",1,"/d"],["writable",1,"logs"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"C:/x","access":"rw","denied":["secrets","/d"],"allowed":[],"writable":["logs"],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"posix","mounts":["/m0,f.json,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,work,ro","/m2,C:/x,ro"],"policy":[["denied",1,"secrets"],["denied",1,"/d"],["denied",2,"secrets"],["denied",2,"/d"],["allowed",2,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"C:/x","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"caller","files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,tools,ro","/m1,work,rw","/m2,f.json"],"policy":[["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"logs"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x,ro"],"policy":[["denied",0,"logs"],["denied",0,"secrets"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":null,"files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,work,rw"],"policy":[["allowed",0,""]],"owner":"caller","files":[],"error":"sandbox mount allowed paths must be nonempty"}, +{"flavor":"posix","mounts":["/m0,tools,ro","/m1,tools","/m2,tools,rw"],"policy":[["denied",0,"/d"],["denied",1,"/d"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"/d"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"tools","access":"rw","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":null,"files":[],"result":[]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":null,"files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,C:/x,rw","/m1,C:/x,rw","/m2,work,rw"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["writable",1,"out"],["denied",1,""],["denied",2,"secrets"],["denied",2,"logs"],["allowed",2,"logs/payloads"],["writable",2,"out"]],"owner":null,"files":[],"error":"sandbox mount denied paths must be nonempty"}, +{"flavor":"posix","mounts":["/m0,C:/x,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work,rw"],"policy":[["denied",null,"early"],["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"rw","denied":["early","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,rw","/m1,work,ro","/m2,tools,rw"],"policy":[["denied",1,"secrets"],["writable",1,""],["denied",2,"secrets"],["allowed",2,"logs/payloads"],["writable",2,"out"],["denied",2,"logs"]],"owner":"caller","files":["f.json"],"error":"sandbox mount writable paths must be nonempty"}, +{"flavor":"posix","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,ro","/m1,work,rw","/m2,work,rw"],"policy":[["writable",1,"out"],["denied",2,"secrets"],["denied",2,"logs"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m1","host_path":"work","access":"rw","denied":[],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["secrets","logs"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,rw","/m1,C:/x,rw"],"policy":[["writable",1,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m1","host_path":"C:/x","access":"rw","denied":[],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x,ro","/m1,C:/x"],"policy":[["writable",null,"early"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"/d"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,tools,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work","/m1,work,rw","/m2,tools,rw"],"policy":[["writable",null,"early"],["denied",0,"logs"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["denied",1,"/d"],["writable",1,"out"],["denied",1,""],["denied",2,"logs"],["writable",2,"out"]],"owner":null,"files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,tools","/m1,C:/x,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["denied",1,"/d"],["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:/x","access":"ro","denied":["/d","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"caller","files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,C:/x,ro","/m1,f.json,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["denied",1,"secrets"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["/d","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work,rw","/m1,C:/x","/m2,f.json,ro"],"policy":[["denied",null,"early"],["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["writable",0,"out"]],"owner":"caller","files":["f.json"],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,tools,rw","/m1,tools,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json,ro","/m1,C:/x,ro"],"policy":[["writable",null,"early"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,C:/x,ro","/m2,f.json"],"policy":[["denied",1,"secrets"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"C:/x","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,f.json,rw","/m1,f.json"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["denied",1,"secrets"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["/d","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x","/m1,f.json"],"policy":[["allowed",0,"logs"]],"owner":null,"files":[],"error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"posix","mounts":["/m0,tools,ro","/m1,tools","/m2,work,rw"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["denied",2,"logs"],["allowed",2,"logs/payloads"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["logs","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work","/m1,tools"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json"],"policy":[],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":null,"files":[],"result":[]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,work,ro"],"policy":[["writable",null,"early"]],"owner":"vmm","files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,tools,rw","/m1,tools"],"policy":[["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,f.json","/m1,work,ro"],"policy":[["denied",1,"secrets"],["denied",1,"/d"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"posix","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"posix","mounts":["/m0,tools,ro","/m1,tools,ro","/m2,tools"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["denied",1,"logs"],["allowed",1,"logs/payloads"],["denied",2,"/d"],["denied",2,"logs"],["allowed",2,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"tools","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,work","/m1,C:/x,rw"],"policy":[["denied",null,"early"],["denied",0,"secrets"],["denied",0,"/d"],["denied",1,"logs"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"posix","mounts":["/m0,C:/x","/m1,tools","/m2,work,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"logs"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"posix","mounts":["/m0,C:/x,rw"],"policy":[["writable",null,"early"],["writable",0,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:/x","access":"rw","denied":[],"allowed":[],"writable":["early","out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/workspace,work,rw","/opt/hostedtoolcache,tools,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",1,"credentials"]],"owner":null,"files":[],"result":[{"guest_target":"/workspace","host_path":"work","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/opt/hostedtoolcache","host_path":"tools","access":"ro","denied":["credentials"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/w,work,rw"],"policy":[["denied",null,"secrets"]],"owner":"caller","files":[],"result":[{"guest_target":"/w","host_path":"work","access":"rw","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/workspace,work,rw","/opt/hostedtoolcache,tools"],"policy":[["allowed",null,"logs"],["denied",null,"secrets"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/etc/app/settings.json,settings.json,rw"],"policy":[["writable",0,"settings.json"]],"owner":null,"files":["settings.json"],"error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /etc/app/settings.json shares a file, whose mode applies to the whole file"}, +{"flavor":"windows","mounts":[],"policy":[["denied",null,"x"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":[],"policy":[["writable",null,"early"]],"owner":null,"files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,f.json"],"policy":[],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,tools,rw","/m1,work","/m2,tools,ro"],"policy":[["writable",null,"early"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"logs"]],"owner":"vmm","files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,tools,rw","/m1,f.json,ro"],"policy":[["writable",0,"out"],["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":[],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["allowed",0,""]],"owner":null,"files":["f.json"],"error":"sandbox mount allowed paths must be nonempty"}, +{"flavor":"windows","mounts":["/m0,f.json,rw","/m1,f.json"],"policy":[],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"windows","mounts":["/m0,f.json"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,rw"],"policy":[],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"}]}, +{"flavor":"windows","mounts":["/m0,f.json,rw","/m1,tools"],"policy":[["denied",1,"logs"],["denied",1,"/d"],["allowed",1,"logs/payloads"],["denied",1,""]],"owner":null,"files":["f.json"],"error":"sandbox mount denied paths must be nonempty"}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":null,"files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,C:/x,ro"],"policy":[["denied",0,"/d"],["denied",0,"logs"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["/d","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x,rw","/m1,work,ro","/m2,C:/x"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",2,"/d"],["denied",2,"logs"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"rw","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"C:\\x","access":"ro","denied":["/d","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work,ro","/m1,C:/x,rw"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["writable",1,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"C:\\x","access":"rw","denied":["logs"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,tools","/m1,tools,rw","/m2,work,rw"],"policy":[["denied",2,"logs"],["denied",2,"secrets"],["writable",2,"out"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["logs","secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json"],"policy":[],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["writable",0,"out"]],"owner":"vmm","files":["f.json"],"error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /m0 shares a file, whose mode applies to the whole file"}, +{"flavor":"windows","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"secrets"],["denied",0,"/d"]],"owner":"vmm","files":["f.json"],"error":"--mount-deny, --mount-allow, and --mount-write name paths inside a shared directory, but --mount /m0 shares a file, whose mode applies to the whole file"}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["denied",0,"secrets"],["denied",0,"logs"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d","secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["logs","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,rw","/m1,work,ro","/m2,f.json"],"policy":[["denied",0,"/d"],["denied",0,"secrets"],["writable",0,"out"],["denied",1,"logs"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"logs"],["allowed",2,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["/d","secrets"],"allowed":[],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work","/m1,f.json","/m2,f.json,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"/d"],["allowed",1,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,ro"],"policy":[["denied",0,"logs"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x","/m1,f.json","/m2,work,ro"],"policy":[["allowed",null,"early"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["allowed",2,""]],"owner":null,"files":["f.json"],"error":"with several --mount options, each --mount-allow must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,work,rw"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["writable",0,"out"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"rw","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,ro","/m1,tools,ro"],"policy":[["denied",1,"/d"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,tools","/m1,C:/x","/m2,work,rw"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["denied",1,"logs"],["denied",1,"/d"],["denied",2,"logs"],["allowed",2,"logs/payloads"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"C:\\x","access":"ro","denied":["logs","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x,rw"],"policy":[["allowed",null,"early"],["writable",0,"out"]],"owner":null,"files":["f.json"],"error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"windows","mounts":["/m0,work,rw","/m1,C:/x","/m2,C:/x,rw"],"policy":[["writable",0,"out"],["denied",2,"/d"],["denied",2,""]],"owner":"vmm","files":[],"error":"sandbox mount denied paths must be nonempty"}, +{"flavor":"windows","mounts":["/m0,C:/x","/m1,C:/x,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["denied",1,"/d"],["allowed",1,"logs"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:\\x","access":"ro","denied":["logs","/d"],"allowed":["logs"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[["allowed",null,"early"]],"owner":"caller","files":[],"error":"with several --mount options, each --mount-allow must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,C:/x,ro","/m1,f.json"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"windows","mounts":["/m0,tools,rw","/m1,work,ro","/m2,tools,rw"],"policy":[["denied",null,"early"],["denied",0,"secrets"],["denied",0,"logs"],["denied",1,"logs"],["denied",2,"logs"],["denied",2,"secrets"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,f.json,rw"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["writable",0,"out"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["logs","/d"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,work,ro"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["writable",0,"logs"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"error":"--mount-write narrows the writes of a read-write share; --mount /m0 is read-only"}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[["denied",null,"early"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["early"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,ro","/m1,f.json,rw"],"policy":[],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,work","/m2,tools,ro"],"policy":[["denied",0,"secrets"],["denied",0,"logs"],["denied",1,"/d"],["allowed",1,"logs/payloads"],["denied",2,"secrets"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"tools","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work,ro","/m1,C:/x,rw","/m2,work,ro"],"policy":[["denied",1,"logs"],["allowed",1,"logs/payloads"],["writable",1,"out"],["denied",2,"logs"],["denied",2,"/d"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:\\x","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"ro","denied":["logs","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json"],"policy":[],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,work","/m1,work,rw"],"policy":[["denied",0,"secrets"],["denied",1,"logs"],["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"rw","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,tools,ro","/m1,tools,rw","/m2,f.json,ro"],"policy":[["writable",1,"out"],["allowed",1,""],["denied",2,"/d"]],"owner":"vmm","files":[],"error":"sandbox mount allowed paths must be nonempty"}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,tools,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["denied",1,"secrets"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,work,ro","/m1,f.json,ro"],"policy":[["denied",null,"early"],["writable",0,"logs"],["denied",1,"/d"]],"owner":"vmm","files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,C:/x,ro","/m1,C:/x","/m2,C:/x,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"C:\\x","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":[],"policy":[["allowed",null,"early"]],"owner":"caller","files":[],"error":"with several --mount options, each --mount-allow must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,work,ro","/m1,work"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,f.json"],"policy":[],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,ro","/m1,f.json,rw","/m2,f.json"],"policy":[["denied",1,"/d"],["denied",1,"secrets"],["denied",2,"logs"],["denied",2,"/d"],["allowed",2,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"rw","denied":["/d","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":["logs","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,rw","/m1,work"],"policy":[["writable",0,"out"],["allowed",0,"logs"]],"owner":"caller","files":["f.json"],"error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"windows","mounts":["/m0,work,rw","/m1,tools,rw"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",1,"secrets"],["denied",1,"/d"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"rw","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,tools,ro"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["writable",0,""]],"owner":"caller","files":[],"error":"sandbox mount writable paths must be nonempty"}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["allowed",0,"logs"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["secrets"],"allowed":["logs/payloads","logs"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work","/m1,C:/x,rw","/m2,f.json,ro"],"policy":[["denied",null,"early"],["denied",0,"logs"],["denied",0,"secrets"],["denied",1,"/d"],["denied",2,"secrets"],["denied",2,"/d"],["allowed",2,"logs/payloads"]],"owner":"caller","files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,f.json"],"policy":[],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,tools,ro","/m1,f.json,rw"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"windows","mounts":["/m0,C:/x","/m1,tools,rw"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["writable",1,"logs"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["logs"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,C:/x,rw"],"policy":[["denied",1,"secrets"],["allowed",1,"logs/payloads"],["writable",1,"out"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:\\x","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,tools,rw","/m1,tools,ro","/m2,C:/x,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["writable",0,"out"],["denied",1,"logs"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"logs"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":["out"],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"C:\\x","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,f.json","/m2,work,ro"],"policy":[],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m2","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,C:/x,rw","/m2,f.json,rw"],"policy":[["denied",1,"secrets"],["denied",1,"/d"],["writable",1,"logs"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"C:\\x","access":"rw","denied":["secrets","/d"],"allowed":[],"writable":["logs"],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"windows","mounts":["/m0,f.json,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,work,ro","/m2,C:/x,ro"],"policy":[["denied",1,"secrets"],["denied",1,"/d"],["denied",2,"secrets"],["denied",2,"/d"],["allowed",2,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"C:\\x","access":"ro","denied":["secrets","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"caller","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"caller","files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,tools,ro","/m1,work,rw","/m2,f.json"],"policy":[["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"logs"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x,ro"],"policy":[["denied",0,"logs"],["denied",0,"secrets"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["logs","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":null,"files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,work,rw"],"policy":[["allowed",0,""]],"owner":"caller","files":[],"error":"sandbox mount allowed paths must be nonempty"}, +{"flavor":"windows","mounts":["/m0,tools,ro","/m1,tools","/m2,tools,rw"],"policy":[["denied",0,"/d"],["denied",1,"/d"],["denied",1,"secrets"],["allowed",1,"logs/payloads"],["denied",2,"secrets"],["denied",2,"/d"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"tools","access":"rw","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":null,"files":[],"result":[]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":null,"files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,C:/x,rw","/m1,C:/x,rw","/m2,work,rw"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["writable",1,"out"],["denied",1,""],["denied",2,"secrets"],["denied",2,"logs"],["allowed",2,"logs/payloads"],["writable",2,"out"]],"owner":null,"files":[],"error":"sandbox mount denied paths must be nonempty"}, +{"flavor":"windows","mounts":["/m0,C:/x,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work,rw"],"policy":[["denied",null,"early"],["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":null,"files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"rw","denied":["early","/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,rw","/m1,work,ro","/m2,tools,rw"],"policy":[["denied",1,"secrets"],["writable",1,""],["denied",2,"secrets"],["allowed",2,"logs/payloads"],["writable",2,"out"],["denied",2,"logs"]],"owner":"caller","files":["f.json"],"error":"sandbox mount writable paths must be nonempty"}, +{"flavor":"windows","mounts":["/m0,work,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,ro","/m1,work,rw","/m2,work,rw"],"policy":[["writable",1,"out"],["denied",2,"secrets"],["denied",2,"logs"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m1","host_path":"work","access":"rw","denied":[],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["secrets","logs"],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,rw","/m1,C:/x,rw"],"policy":[["writable",1,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"file"},{"guest_target":"/m1","host_path":"C:\\x","access":"rw","denied":[],"allowed":[],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x,ro","/m1,C:/x"],"policy":[["writable",null,"early"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"/d"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,tools,ro"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work","/m1,work,rw","/m2,tools,rw"],"policy":[["writable",null,"early"],["denied",0,"logs"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"logs"],["denied",1,"/d"],["writable",1,"out"],["denied",1,""],["denied",2,"logs"],["writable",2,"out"]],"owner":null,"files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,tools","/m1,C:/x,ro"],"policy":[["denied",0,"secrets"],["denied",0,"/d"],["denied",1,"/d"],["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"C:\\x","access":"ro","denied":["/d","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"caller","files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,C:/x,ro","/m1,f.json,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["denied",1,"secrets"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["/d","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work,rw","/m1,C:/x","/m2,f.json,ro"],"policy":[["denied",null,"early"],["denied",0,"secrets"],["denied",0,"/d"],["allowed",0,"logs/payloads"],["writable",0,"out"]],"owner":"caller","files":["f.json"],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,tools,rw","/m1,tools,ro"],"policy":[["denied",0,"secrets"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json,ro","/m1,C:/x,ro"],"policy":[["writable",null,"early"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,C:/x,ro","/m2,f.json"],"policy":[["denied",1,"secrets"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"},{"guest_target":"/m1","host_path":"C:\\x","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"file"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,f.json,rw","/m1,f.json"],"policy":[["denied",0,"/d"],["denied",0,"logs"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["denied",1,"secrets"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"rw","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"f.json","access":"ro","denied":["/d","secrets"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x","/m1,f.json"],"policy":[["allowed",0,"logs"]],"owner":null,"files":[],"error":"--mount-allow exposes a path inside a --mount-deny path of the same share, which has none"}, +{"flavor":"windows","mounts":["/m0,tools,ro","/m1,tools","/m2,work,rw"],"policy":[["denied",0,"logs"],["denied",0,"/d"],["denied",2,"logs"],["allowed",2,"logs/payloads"],["writable",2,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["logs","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"rw","denied":["logs"],"allowed":["logs/payloads"],"writable":["out"],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work","/m1,tools"],"policy":[["denied",0,"logs"],["denied",0,"secrets"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"logs"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"work","access":"ro","denied":["logs","secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json"],"policy":[],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":null,"files":[],"result":[]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,work,ro"],"policy":[["writable",null,"early"]],"owner":"vmm","files":["f.json"],"error":"with several --mount options, each --mount-write must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,tools,rw","/m1,tools"],"policy":[["denied",1,"secrets"],["allowed",1,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"rw","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,f.json","/m1,work,ro"],"policy":[["denied",1,"secrets"],["denied",1,"/d"]],"owner":"caller","files":[],"result":[{"guest_target":"/m0","host_path":"f.json","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"caller","kind":"directory"},{"guest_target":"/m1","host_path":"work","access":"ro","denied":["secrets","/d"],"allowed":[],"writable":[],"owner":"caller","kind":"directory"}]}, +{"flavor":"windows","mounts":[],"policy":[],"owner":"vmm","files":["f.json"],"result":[]}, +{"flavor":"windows","mounts":["/m0,tools,ro","/m1,tools,ro","/m2,tools"],"policy":[["denied",0,"logs"],["allowed",0,"logs/payloads"],["denied",1,"/d"],["denied",1,"logs"],["allowed",1,"logs/payloads"],["denied",2,"/d"],["denied",2,"logs"],["allowed",2,"logs/payloads"]],"owner":"vmm","files":[],"result":[{"guest_target":"/m0","host_path":"tools","access":"ro","denied":["logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"tools","access":"ro","denied":["/d","logs"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,work","/m1,C:/x,rw"],"policy":[["denied",null,"early"],["denied",0,"secrets"],["denied",0,"/d"],["denied",1,"logs"]],"owner":null,"files":[],"error":"with several --mount options, each --mount-deny must follow the --mount whose host directory it names"}, +{"flavor":"windows","mounts":["/m0,C:/x","/m1,tools","/m2,work,ro"],"policy":[["denied",0,"/d"],["allowed",0,"logs/payloads"],["denied",1,"secrets"],["denied",1,"logs"]],"owner":null,"files":[],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"ro","denied":["/d"],"allowed":["logs/payloads"],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m1","host_path":"tools","access":"ro","denied":["secrets","logs"],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"},{"guest_target":"/m2","host_path":"work","access":"ro","denied":[],"allowed":[],"writable":[],"owner":"vmm","kind":"directory"}]}, +{"flavor":"windows","mounts":["/m0,C:/x,rw"],"policy":[["writable",null,"early"],["writable",0,"out"]],"owner":"caller","files":["f.json"],"result":[{"guest_target":"/m0","host_path":"C:\\x","access":"rw","denied":[],"allowed":[],"writable":["early","out"],"owner":"caller","kind":"directory"}]} +], +"environments": [ +{"entries":["KEY0=value","KEY1=value","KEY2=value","KEY3=value","KEY4=value","KEY5=value","KEY6=value","KEY7=value","KEY8=value","KEY9=value","KEY10=value","KEY11=value","KEY12=value","KEY13=value","KEY14=value","KEY15=value","KEY16=value","KEY17=value","KEY18=value","KEY19=value","KEY20=value","KEY21=value","KEY22=value","KEY23=value","KEY24=value","KEY25=value","KEY26=value","KEY27=value","KEY28=value","KEY29=value","KEY30=value","KEY31=value","KEY32=value","KEY33=value","KEY34=value","KEY35=value","KEY36=value","KEY37=value","KEY38=value","KEY39=value","KEY40=value","KEY41=value","KEY42=value","KEY43=value","KEY44=value","KEY45=value","KEY46=value","KEY47=value","KEY48=value","KEY49=value","KEY50=value","KEY51=value","KEY52=value","KEY53=value","KEY54=value","KEY55=value","KEY56=value","KEY57=value","KEY58=value","KEY59=value","KEY60=value","KEY61=value","KEY62=value","KEY63=value","KEY64=value","KEY65=value","KEY66=value","KEY67=value","KEY68=value","KEY69=value","KEY70=value","KEY71=value","KEY72=value","KEY73=value","KEY74=value","KEY75=value","KEY76=value","KEY77=value","KEY78=value","KEY79=value","KEY80=value","KEY81=value","KEY82=value","KEY83=value","KEY84=value","KEY85=value","KEY86=value","KEY87=value","KEY88=value","KEY89=value","KEY90=value","KEY91=value","KEY92=value","KEY93=value","KEY94=value","KEY95=value","KEY96=value","KEY97=value","KEY98=value","KEY99=value","KEY100=value","KEY101=value","KEY102=value","KEY103=value","KEY104=value","KEY105=value","KEY106=value","KEY107=value","KEY108=value","KEY109=value","KEY110=value","KEY111=value","KEY112=value","KEY113=value","KEY114=value","KEY115=value","KEY116=value","KEY117=value","KEY118=value","KEY119=value","KEY120=value","KEY121=value","KEY122=value","KEY123=value","KEY124=value","KEY125=value","KEY126=value","KEY127=value","KEY128=value","KEY129=value","KEY130=value","KEY131=value","KEY132=value","KEY133=value","KEY134=value","KEY135=value","KEY136=value","KEY137=value","KEY138=value","KEY139=value","KEY140=value","KEY141=value","KEY142=value","KEY143=value","KEY144=value","KEY145=value","KEY146=value","KEY147=value","KEY148=value","KEY149=value","KEY150=value","KEY151=value","KEY152=value","KEY153=value","KEY154=value","KEY155=value","KEY156=value","KEY157=value","KEY158=value","KEY159=value","KEY160=value","KEY161=value","KEY162=value","KEY163=value","KEY164=value","KEY165=value","KEY166=value","KEY167=value","KEY168=value","KEY169=value","KEY170=value","KEY171=value","KEY172=value","KEY173=value","KEY174=value","KEY175=value","KEY176=value","KEY177=value","KEY178=value","KEY179=value","KEY180=value","KEY181=value","KEY182=value","KEY183=value","KEY184=value","KEY185=value","KEY186=value","KEY187=value","KEY188=value","KEY189=value","KEY190=value","KEY191=value","KEY192=value","KEY193=value","KEY194=value","KEY195=value","KEY196=value","KEY197=value","KEY198=value","KEY199=value","KEY200=value","KEY201=value","KEY202=value","KEY203=value","KEY204=value","KEY205=value","KEY206=value","KEY207=value","KEY208=value","KEY209=value","KEY210=value","KEY211=value","KEY212=value","KEY213=value","KEY214=value","KEY215=value","KEY216=value","KEY217=value","KEY218=value","KEY219=value","KEY220=value","KEY221=value","KEY222=value","KEY223=value","KEY224=value","KEY225=value","KEY226=value","KEY227=value","KEY228=value","KEY229=value","KEY230=value","KEY231=value","KEY232=value","KEY233=value","KEY234=value","KEY235=value","KEY236=value","KEY237=value","KEY238=value","KEY239=value","KEY240=value","KEY241=value","KEY242=value","KEY243=value","KEY244=value","KEY245=value","KEY246=value","KEY247=value","KEY248=value","KEY249=value","KEY250=value","KEY251=value","KEY252=value","KEY253=value","KEY254=value","KEY255=value","KEY256=value"],"error":"managed exec environment exceeds 256 entries"}, +{"entries":["KEY0=value","KEY1=value","KEY2=value","KEY3=value","KEY4=value","KEY5=value","KEY6=value","KEY7=value","KEY8=value","KEY9=value","KEY10=value","KEY11=value","KEY12=value","KEY13=value","KEY14=value","KEY15=value","KEY16=value","KEY17=value","KEY18=value","KEY19=value","KEY20=value","KEY21=value","KEY22=value","KEY23=value","KEY24=value","KEY25=value","KEY26=value","KEY27=value","KEY28=value","KEY29=value","KEY30=value","KEY31=value","KEY32=value","KEY33=value","KEY34=value","KEY35=value","KEY36=value","KEY37=value","KEY38=value","KEY39=value","KEY40=value","KEY41=value","KEY42=value","KEY43=value","KEY44=value","KEY45=value","KEY46=value","KEY47=value","KEY48=value","KEY49=value","KEY50=value","KEY51=value","KEY52=value","KEY53=value","KEY54=value","KEY55=value","KEY56=value","KEY57=value","KEY58=value","KEY59=value","KEY60=value","KEY61=value","KEY62=value","KEY63=value","KEY64=value","KEY65=value","KEY66=value","KEY67=value","KEY68=value","KEY69=value","KEY70=value","KEY71=value","KEY72=value","KEY73=value","KEY74=value","KEY75=value","KEY76=value","KEY77=value","KEY78=value","KEY79=value","KEY80=value","KEY81=value","KEY82=value","KEY83=value","KEY84=value","KEY85=value","KEY86=value","KEY87=value","KEY88=value","KEY89=value","KEY90=value","KEY91=value","KEY92=value","KEY93=value","KEY94=value","KEY95=value","KEY96=value","KEY97=value","KEY98=value","KEY99=value","KEY100=value","KEY101=value","KEY102=value","KEY103=value","KEY104=value","KEY105=value","KEY106=value","KEY107=value","KEY108=value","KEY109=value","KEY110=value","KEY111=value","KEY112=value","KEY113=value","KEY114=value","KEY115=value","KEY116=value","KEY117=value","KEY118=value","KEY119=value","KEY120=value","KEY121=value","KEY122=value","KEY123=value","KEY124=value","KEY125=value","KEY126=value","KEY127=value","KEY128=value","KEY129=value","KEY130=value","KEY131=value","KEY132=value","KEY133=value","KEY134=value","KEY135=value","KEY136=value","KEY137=value","KEY138=value","KEY139=value","KEY140=value","KEY141=value","KEY142=value","KEY143=value","KEY144=value","KEY145=value","KEY146=value","KEY147=value","KEY148=value","KEY149=value","KEY150=value","KEY151=value","KEY152=value","KEY153=value","KEY154=value","KEY155=value","KEY156=value","KEY157=value","KEY158=value","KEY159=value","KEY160=value","KEY161=value","KEY162=value","KEY163=value","KEY164=value","KEY165=value","KEY166=value","KEY167=value","KEY168=value","KEY169=value","KEY170=value","KEY171=value","KEY172=value","KEY173=value","KEY174=value","KEY175=value","KEY176=value","KEY177=value","KEY178=value","KEY179=value","KEY180=value","KEY181=value","KEY182=value","KEY183=value","KEY184=value","KEY185=value","KEY186=value","KEY187=value","KEY188=value","KEY189=value","KEY190=value","KEY191=value","KEY192=value","KEY193=value","KEY194=value","KEY195=value","KEY196=value","KEY197=value","KEY198=value","KEY199=value","KEY200=value","KEY201=value","KEY202=value","KEY203=value","KEY204=value","KEY205=value","KEY206=value","KEY207=value","KEY208=value","KEY209=value","KEY210=value","KEY211=value","KEY212=value","KEY213=value","KEY214=value","KEY215=value","KEY216=value","KEY217=value","KEY218=value","KEY219=value","KEY220=value","KEY221=value","KEY222=value","KEY223=value","KEY224=value","KEY225=value","KEY226=value","KEY227=value","KEY228=value","KEY229=value","KEY230=value","KEY231=value","KEY232=value","KEY233=value","KEY234=value","KEY235=value","KEY236=value","KEY237=value","KEY238=value","KEY239=value","KEY240=value","KEY241=value","KEY242=value","KEY243=value","KEY244=value","KEY245=value","KEY246=value","KEY247=value","KEY248=value","KEY249=value","KEY250=value","KEY251=value","KEY252=value","KEY253=value","KEY254=value","KEY255=value"],"error":null}, +{"entries":["NO_EQUALS"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["=empty-key"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["KEY=embedded\u0000nul"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["KEY=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["K=vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"],"error":null}, +{"entries":["DUPLICATE=one","DUPLICATE=two"],"error":"managed exec environment contains duplicate key: DUPLICATE"}, +{"entries":["A=1","B=2"],"error":null}, +{"entries":[],"error":null}, +{"entries":[],"error":null}, +{"entries":["\u0000"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["Bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u0000"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxB","B"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["","","B",""],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["=\u0000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA","BA=","\u0000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABA","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA=\u00e9"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":[""],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["\u00e9\u00e9B","=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=A="],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["",""],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["==","B","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxB","B="],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["AA","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=\u00e9A","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u0000\u00e9"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["\u0000\u00e9",""],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["B\u0000="],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA\u00e9\u00e9A"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["Axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["\u00e9AA"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":[],"error":null}, +{"entries":["\u00e9B\u00e9","\u00e9A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["Bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",""],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["AB\u00e9\u0000",""],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["B","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u00e9"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":[],"error":null}, +{"entries":["B\u0000\u0000A\u00e9","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","=A"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["=\u0000BAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","","=","\u0000A"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["","BB\u0000"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["","\u00e9\u0000\u0000B="],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["AB\u00e9","B"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["","=Bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u0000"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":[""],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u0000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=="],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["\u00c9=\u00e9","A=a=b","K_1=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","PATH=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603","B="],"error":null}, +{"entries":["B=1"],"error":null}, +{"entries":["K_1=1","NO_EQUALS","PATH= spaced "],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["\u00c9=\u00e9","K_1=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"error":null}, +{"entries":["\u00c9=a=b","K_1=","K_1=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603"],"error":"managed exec environment contains duplicate key: K_1"}, +{"entries":["B=\u00e9"],"error":null}, +{"entries":[],"error":null}, +{"entries":[],"error":null}, +{"entries":[],"error":null}, +{"entries":["B= spaced ","B=a=b","=x","K_1=\u00e9","B= spaced "],"error":"managed exec environment contains duplicate key: B"}, +{"entries":["K_1= spaced ","PATH=","A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","B= spaced ","\u00c9=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603"],"error":null}, +{"entries":["\u00c9= spaced ","A=1","K_1= spaced ","A=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603"],"error":"managed exec environment contains duplicate key: A"}, +{"entries":["\u00c9=","B=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603","PATH= spaced ","K_1=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603"],"error":null}, +{"entries":["NO_EQUALS","A="],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["B=\u00e9","A=1","K_1= spaced "],"error":null}, +{"entries":["A=\u00e9"],"error":null}, +{"entries":[],"error":null}, +{"entries":["K_1=a=b","PATH=1","B=1"],"error":null}, +{"entries":["K_1="],"error":null}, +{"entries":["A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00c9= spaced ","A=1"],"error":"managed exec environment contains duplicate key: A"}, +{"entries":[],"error":null}, +{"entries":["A=","K_1=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","B=a=b","\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","NO_EQUALS"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","\u00c9="],"error":"managed exec environment contains duplicate key: \u00c9"}, +{"entries":[],"error":null}, +{"entries":["PATH=1","PATH=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"error":"managed exec environment contains duplicate key: PATH"}, +{"entries":["B=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603","A=\u0000","B=\u00e9","B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":["A=","B=a=b","A=a=b","\u00c9=1"],"error":"managed exec environment contains duplicate key: A"}, +{"entries":["K_1=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603"],"error":null}, +{"entries":["\u00c9=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603","K_1= spaced "],"error":null}, +{"entries":["NO_EQUALS"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"}, +{"entries":[],"error":null}, +{"entries":["PATH= spaced "],"error":null}, +{"entries":["B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","A=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603","PATH=a=b"],"error":null}, +{"entries":["B=1"],"error":null}, +{"entries":["A=\u00e9","PATH=a=b","PATH=","B="],"error":"managed exec environment contains duplicate key: PATH"}, +{"entries":[],"error":null}, +{"entries":["K_1=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],"error":null}, +{"entries":["\u00c9=a=b","B=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603","B=\u00e9","K_1=a=b"],"error":"managed exec environment contains duplicate key: B"}, +{"entries":["\u00c9=\u00e9","K_1=\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603\u2603","\u00c9=","\u00c9=1","\u00c9= spaced "],"error":"managed exec environment contains duplicate key: \u00c9"}, +{"entries":["A=\u0000"],"error":"managed exec environment entries must be non-empty KEY=VALUE strings of at most 4096 bytes"} +], +"environment_files": [ +{"text":"[]","environment":[]}, +{"text":"[\"A=1\", \"B=2\"]","environment":["A=1","B=2"]}, +{"text":"[\"DUPLICATE=one\", \"DUPLICATE=two\"]","error":"managed exec environment contains duplicate key: DUPLICATE"}, +{"spaces":1048577,"error":"managed execution environment file exceeds 1048576-byte limit"}, +{"spaces":1048576,"error":"failed to read managed execution environment: {path}"}, +{"text":"{}","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"\"A=1\"","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[1]","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[NaN]","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[[\"A=1\"]]","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"","error":"failed to read managed execution environment: {path}"}, +{"text":"[","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=1\",]","error":"failed to read managed execution environment: {path}"}, +{"text":"\ufeff[]","error":"failed to read managed execution environment: {path}"}, +{"bytes":"5b22ff225d","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=1\", \"B=\\ud800\"]","error":"'utf-8' codec can't encode character '\\ud800' in position 2: surrogates not allowed"}, +{"text":"[\"B=\\udc00\\ud800x\"]","error":"'utf-8' codec can't encode characters in position 2-3: surrogates not allowed"}, +{"text":"[11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111]","error":"Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit"}, +{"text":"[\"A=\\u00e9\"]\r\n","environment":["A=\u00e9"]}, +{"text":"[\"A=11\", \"B=\\u00eNaN9\", \"C=\\ud8\"d\\ude00\"]","error":"failed to read managed execution environment: {path}"}, +{"text":"+[]","error":"failed to read managed execution environment: {path}"}, +{"text":"d800:]","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\r\n[1 , 2\n ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[u","error":"failed to read managed execution environment: {path}"}, +{"text":"[","error":"failed to read managed execution environment: {path}"}, +{"text":"{}1","error":"failed to read managed execution environment: {path}"}, +{"text":"[]NaN","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":" \t\r\n[true , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":" \t\r\ntrue1 { 2 NaN ","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","environment":["A=1","B=\u00e9","C=\ud83d\ude00"]}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude0Infinity0\"]","error":"failed to read managed execution environment: {path}"}, +{"text":"{}","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":" \t\r\n01 , 2 ]","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=1\", \"B=\\u0\ne9\", \"C=\\ud83d\\ude00\"]","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[{","error":"failed to read managed execution environment: {path}"}, +{"text":" \u00e9\r\n+[ , 2 ] ","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=1\t\", B=\\u{00e9\", \"C=\\ud83d\\ude00\"]","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\+detrue00\"","error":"failed to read managed execution environment: {path}"}, +{"text":"[1","error":"failed to read managed execution environment: {path}"}, +{"text":"-{}","error":"failed to read managed execution environment: {path}"}, +{"text":"{}","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[]","environment":[]}, +{"text":"[]","environment":[]}, +{"text":" ]\t\r\n[1 , 2null] {","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","environment":["A=1","B=\u00e9","C=\ud83d\ude00"]}, +{"text":" \t\r\n[u1 , 2 ] ","error":"failed to read managed execution environment: {path}"}, +{"text":"{}","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"{}-","error":"failed to read managed execution environment: {path}"}, +{"text":"{}","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"{}","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":",}","error":"failed to read managed execution environment: {path}"}, +{"text":"0{","error":"failed to read managed execution environment: {path}"}, +{"text":"[e","error":"failed to read managed execution environment: {path}"}, +{"text":"{e","error":"failed to read managed execution environment: {path}"}, +{"text":"{}}","error":"failed to read managed execution environment: {path}"}, +{"text":"{d800","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=1\", \"B=\\u00e9\", \"C=\\ud83d\\ude00\"]","environment":["A=1","B=\u00e9","C=\ud83d\ude00"]}, +{"text":"[\"A=1\", \"B=\\u009\", \"C=\\ud83d\\ude00\"]","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\r\n{[1 , 2 ] ","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":" \t+\n[1 +, 2 ] ","error":"failed to read managed execution environment: {path}"}, +{"text":"[]","environment":[]}, +{"text":" .]","error":"failed to read managed execution environment: {path}"}, +{"text":" {\r","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\n1 , \r2 ] ","error":"failed to read managed execution environment: {path}"}, +{"text":"[]","environment":[]}, +{"text":" \t\r\nd8001true, 2 ]0 ","error":"failed to read managed execution environment: {path}"}, +{"text":"{[","error":"failed to read managed execution environment: {path}"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[\"A=1\", \"B=\\u00e\", \"C=\\ud83d\\ude00\"]","error":"failed to read managed execution environment: {path}"}, +{"text":"[]","environment":[]}, +{"text":"{}","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":" \t\r\n[1 , 2 ] ","error":"managed execution environment file must contain a JSON array of KEY=VALUE strings"}, +{"text":"[\"A= \", \"B=\\u00e\", \"C=\\ud83d\\u-e00\"]","error":"failed to read managed execution environment: {path}"}, +{"text":"[\"A=\\u00e9\", \"\\u00c9=\\ud83d\\ude00\", \"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"\\u00c9=1\"]","error":"managed exec environment contains duplicate key: \u00c9"}, +{"text":"[\"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"\u00c9=1\"]","error":"managed exec environment contains duplicate key: B"}, +{"text":"[\"\u00c9=\ud83d\ude00\", \"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"]","environment":["\u00c9=\ud83d\ude00","B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]}, +{"text":"[\"A=\\u00e9\"]","environment":["A=\u00e9"]}, +{"text":"[]","environment":[]}, +{"text":"[\"A=\\u00e9\"]","environment":["A=\u00e9"]}, +{"text":"[\"B=\u00e9\", \"B=\u00e9\", \"\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"]","error":"managed exec environment contains duplicate key: B"}, +{"text":"[\"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"B=\u00e9\"]","error":"managed exec environment contains duplicate key: A"}, +{"text":"[]","environment":[]}, +{"text":"[\"A=\u00e9\"]","environment":["A=\u00e9"]}, +{"text":"[\"\\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"]","environment":["\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]}, +{"text":"[\"\\u00c9=\\ud83d\\ude00\", \"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"A=\\ud83d\\ude00\"]","error":"managed exec environment contains duplicate key: A"}, +{"text":"[\"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"B=\\u00e9\"]","environment":["A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","B=\u00e9"]}, +{"text":"[\"B=1\"]","environment":["B=1"]}, +{"text":"[\"B=\\u00e9\", \"\\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"]","environment":["B=\u00e9","\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]}, +{"text":"[\"\u00c9=\u00e9\", \"B=1\"]","environment":["\u00c9=\u00e9","B=1"]}, +{"text":"[\"\u00c9=\ud83d\ude00\", \"\u00c9=\ud83d\ude00\"]","error":"managed exec environment contains duplicate key: \u00c9"}, +{"text":"[\"A=\\u00e9\", \"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"B=\\ud83d\\ude00\", \"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"]","error":"managed exec environment contains duplicate key: A"}, +{"text":"[\"B=\\u00e9\", \"A=\\u00e9\", \"\\u00c9=\\ud83d\\ude00\", \"A=\\ud83d\\ude00\"]","error":"managed exec environment contains duplicate key: A"}, +{"text":"[\"A=\ud83d\ude00\", \"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"]","environment":["A=\ud83d\ude00","B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]}, +{"text":"[]","environment":[]}, +{"text":"[\"B=\ud83d\ude00\"]","environment":["B=\ud83d\ude00"]}, +{"text":"[\"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"B=\\u00e9\", \"A=\\ud83d\\ude00\", \"\\u00c9=1\"]","error":"managed exec environment contains duplicate key: A"}, +{"text":"[\"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"B=1\", \"B=\\u00e9\"]","error":"managed exec environment contains duplicate key: B"}, +{"text":"[\"A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"]","environment":["A=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","B=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]}, +{"text":"[\"A=\u00e9\", \"\u00c9=\u00e9\"]","environment":["A=\u00e9","\u00c9=\u00e9"]}, +{"text":"[\"A=\\ud83d\\ude00\"]","environment":["A=\ud83d\ude00"]}, +{"text":"[\"\u00c9=\u00e9\", \"A=\ud83d\ude00\"]","environment":["\u00c9=\u00e9","A=\ud83d\ude00"]}, +{"text":"[]","environment":[]}, +{"text":"[\"\\u00c9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"\\u00c9=1\", \"A=\\ud83d\\ude00\"]","error":"managed exec environment contains duplicate key: \u00c9"} +], +"distro_layer": "distro", +"distro_manifests": [ +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"systemd images are unsupported by the sandbox security profile"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"systemd images are unsupported by the sandbox security profile"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":null}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1.0, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"systemd images are unsupported by the sandbox security profile"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":null}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": true, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": \"1\", \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 2, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": \"other.erofs\", \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": []}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": {}}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": 1}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": NaN, \"artifact\": 1, \"artifact_sha256\": \"0\", \"packages\": [{\"name\": \"systemd\"}, 1]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"[]"},"error":"invalid sandbox distro manifest: expected a JSON object"}, +{"manifest":{"text":"{\r\n\"format\": }"},"error":"invalid sandbox distro manifest: Expecting value: line 2 column 11 (char 12)"}, +{"manifest":{"bytes":"7bff7d"},"error":"'utf-8' codec can't decode byte 0xff in position 1: invalid start byte"}, +{"manifest":{"text":"\ufeff{}"},"error":"invalid sandbox distro manifest: Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"format\": 2}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"},"error":"Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6ba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": Infinity3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting ',' delimiter: line 1 column 70 (char 69)"}, +{"manifest":{"text":"{\"format\": 1, \"urtifact\": \"distro.erofs\", \"artifact\u00e9sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sh\t256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 55 (char 54)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbfb10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": ba\"sh\"}]}"},"error":"invalid sandbox distro manifest: Expecting value: line 1 column 151 (char 150)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\":\u00e9 1, \"artif\tact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting value: line 1 column 11 (char 10)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\":ame\": \"eash\"}]}"},"error":"sandbox distro manifest has invalid package metadata"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distr.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\"d800 \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting ',' delimiter: line 1 column 127 (char 126)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"306\\b5a4993b81fc6b4c6eba8321b7cbfbf5\tb10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 99 (char 98)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b\u00e90c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": -bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting value: line 1 column 152 (char 151)"}, +{"manifest":{"text":"{\"format\": 1, \"atruetifact:\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\". [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting ':' delimiter: line 1 column 144 (char 143)"}, +{"manifest":{"text":"{\"formatd800\": 1, \"artifact\": \"distro.erofs\", \"art]ifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packaes\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": , \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bd 82b516\", \"packagese\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting value: line 1 column 12 (char 11)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\",NaN\"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting property name enclosed in double quotes: line 1 column 42 (char 41)"}, +{"manifest":{"text":"{\"format\": 1, \"arti0fact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8[d8004e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{]\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"manifest":{"text":"{\"format\": 11, \"artifact\":\"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069NaNc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs:, \"artfact_sha256\": \"3069c5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting ',' delimiter: line 1 column 44 (char 43)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.eros\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artif{act_sha256\": \"306bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b7\u001fbdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 117 (char 116)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact\u0000_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\"d800, \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 52 (char 51)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifat_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c\teba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\":- \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 84 (char 83)"}, +{"manifest":{"text":"{\"format: 1, \"ar ifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d9\u00e9b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting ':' delimiter: line 1 column 15 (char 14)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81 c6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{d800\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b51-6\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"pa\u001fckages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 133 (char 132)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b8\u00e9fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b51\\6\", \"packages\": null[{\"nam\ne\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid \\escape: line 1 column 126 (char 125)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fcb4c6eb\u00008321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"nanulle\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 86 (char 85)"}, +{"manifest":{"text":"{\"format\": 1, \"artiInfinityact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"\\\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting value: line 1 column 1 (char 0)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifxct_sha25\n\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": .[{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Invalid control character at: line 1 column 58 (char 57)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"nam\"\": \"bah\"}]}"},"error":"invalid sandbox distro manifest: Expecting ':' delimiter: line 1 column 149 (char 148)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4ctrue6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"fo{mat\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash}\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4null7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bas\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a499nullb81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{null\"name\": \"bas\"}]}"},"error":"invalid sandbox distro manifest: Expecting property name enclosed in double quotes: line 1 column 147 (char 146)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \"3069bc5a993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\"1 \"distro.erofs\", \"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"invalid sandbox distro manifest: Expecting ':' delimiter: line 1 column 25 (char 24)"}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\",\"artifact_sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":null}, +{"manifest":{"text":"{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_}sha256\": \"3069bc5a4993b81fc6b4c6eba8321b7cbfbf5b10c8e4e7f83d95b79bdd82b516\", \"packages\": [{\"name\": \"bash\"}]}"},"error":"sandbox distro manifest does not match its artifact"} +] +} diff --git a/scripts/nvx.py b/scripts/nvx.py index 0fb1c94ef..c6cd20178 100644 --- a/scripts/nvx.py +++ b/scripts/nvx.py @@ -87,6 +87,7 @@ from nvx_tools.guest_binaries import ( build_guest_binaries, check_guest_binaries, + guest_binary_source_sha256, test_guest_binaries, ) from nvx_tools.guests import GUEST_NAMES, guest_descriptor @@ -393,6 +394,10 @@ def command_kernel_patch_identity(args: argparse.Namespace) -> None: print(kernel_patch_identity(args.root)) +def command_guest_binary_source_identity(args: argparse.Namespace) -> None: + print(guest_binary_source_sha256(args.root)) + + def command_check_required_ci(args: argparse.Namespace) -> None: results = { job: os.environ.get(environment, "") @@ -1123,6 +1128,20 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: ) patch_identity.set_defaults(handler=command_kernel_patch_identity) + source_identity = subparsers.add_parser( + "guest-binary-source-identity", + help=( + "print the digest of the Rust guest binary source paths and contents " + "that keys the guest image caches" + ), + ) + source_identity.add_argument( + "--root", + type=Path, + default=BuildConstants.REPO_ROOT, + ) + source_identity.set_defaults(handler=command_guest_binary_source_identity) + required_ci = subparsers.add_parser( "check-required-ci", help="validate required GitHub Actions job results", diff --git a/scripts/nvx_rs_fixtures.py b/scripts/nvx_rs_fixtures.py new file mode 100644 index 000000000..6ad90c050 --- /dev/null +++ b/scripts/nvx_rs_fixtures.py @@ -0,0 +1,4034 @@ +"""Generate the golden fixtures and Python-parity tables of nvx-rs. + +nvx-rs ports Python code that nvx.py runs, and must accept the same inputs and +give the same outputs and messages. This script records what that Python code, +and the parts of Python that it relies on, do with fixed cases and seeded random +corpora, so that the Rust tests replay them without Python: + +- nvx-rs/src/host/python/unicode.rs: the Unicode tables of str.isprintable and + int(), from this interpreter's Unicode database. +- nvx-rs/tests/fixtures/python.json: the Python semantics that the ports keep: + str methods, int(), uuid.UUID, pathlib and ntpath, json.loads, and the UTF-8 + codec. +- nvx-rs/tests/fixtures/sandbox.json: the launch profile of + scripts/nvx_tools/sandbox.py, for both pathlib flavors. +- nvx-rs/tests/fixtures/sandbox_options.json: the sandbox helpers of + scripts/nvx.py. +- nvx-rs/tests/fixtures/egress-policy.json: scripts/nvx_tools/egress_policy.py + over every policy that scripts/test_egress_policy.py compiles, hand-written + edge cases, and a seeded random corpus, and how pathlib prints policy paths. +- nvx-rs/tests/fixtures/cli.json: `nvx.py sandbox`, run as a command in fresh + directories with the fake OpenVMM of nvx-rs, nvx-fake-openvmm, and what the + Python functions behind `nvx-rs egress compile` and `nvx-rs artifacts` give: + exit statuses, standard output and error, and the files that each step + leaves, such as the state directory of a sandbox, with temporary paths, + process IDs, and random identifiers replaced by placeholders. Its `posix` and + `windows` sections hold the scenarios of each kind of host, recorded there: + the script rewrites or checks the section of the host that runs it and keeps + the other. It builds nvx-rs and nvx-fake-openvmm with cargo, then also plays + every scenario with nvx-rs, and each interoperability scenario, a sandbox's + lifecycle, with nvx.py and nvx-rs taking turns both ways, and fails if any + step differs from nvx.py. + +The outputs depend on the interpreter, so run the script with CPython 3.14, from +any directory: + + python3 scripts/nvx_rs_fixtures.py # rewrite every generated file + python3 scripts/nvx_rs_fixtures.py --check # fail if any is out of date + +Run it on both Linux and Windows when the CLI changes, so that both sections of +nvx-rs/tests/fixtures/cli.json are current. + +With --output, it writes or checks only an egress policy corpus of +--random-cases random policies at that path, such as a larger corpus for the +NVX_EGRESS_POLICY_FIXTURE variable of the Rust tests. + +Windows paths are recorded with PureWindowsPath, whose ntpath functions are C +functions that index UTF-16 code units on Windows and Python functions that +index code points elsewhere. The corpora have no character beyond the Basic +Multilingual Plane before a colon, on which the two differ, so the script writes +the same files on Windows and Linux. +""" + +# pyright: reportPrivateUsage=false + +from __future__ import annotations + +import argparse +import contextlib +import difflib +import hashlib +import inspect +import io +import ipaddress +import json +import ntpath +import os +import random +import re +import shutil +import signal +import stat +import struct +import subprocess +import sys +import tempfile +import time +import unicodedata +import unittest +import uuid +from collections.abc import Callable, Generator, Iterable, Iterator, Sequence +from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath +from typing import AnyStr, cast +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import nvx # noqa: E402 +import test_egress_policy # noqa: E402 +from nvx_tools import ( # noqa: E402 + build_constants, + common, + control_session, + guests, + sandbox, + sandbox_lifecycle, +) +from nvx_tools.common import ScriptError # noqa: E402 +from nvx_tools.egress_policy import ( # noqa: E402 + MAX_POLICY_FILE_SIZE, + MAX_RULES_PER_ACTION, + CompiledEgressPolicy, + compile_policy_file, +) + +REPO_ROOT = Path(__file__).resolve().parents[1] +UNICODE_TABLES = REPO_ROOT / "nvx-rs" / "src" / "host" / "python" / "unicode.rs" +FIXTURE_DIR = REPO_ROOT / "nvx-rs" / "tests" / "fixtures" +EGRESS_POLICY_FIXTURE = FIXTURE_DIR / "egress-policy.json" +# The interpreter whose behavior the fixtures record. +PYTHON = (3, 14) +MAX_CODE_POINT = 0x10FFFF +SEED = 359 +# The fixed current directories of the two flavors. +CWDS = {"posix": "/fixture/cwd", "windows": "C:\\fixture\\cwd"} +# The contents of the distro layer that the systemd policy cases hash. +DISTRO_LAYER = "distro" +RANDOM_CASES = 200 +# Messages name the policy file; the fixture writes its path as this placeholder. +PATH_PLACEHOLDER = "{path}" +# Runs of one character or byte at least this long are recorded as a repetition. +MIN_REPEAT = 256 +# Tests whose policies are too large to record. Their tests in +# `scripts/test_egress_policy.py`, and the Rust ports of those tests, pin the +# complete outcome of each. +LARGE_VECTORS = frozenset( + ( + "test_address_only_rule_prunes_redundant_port_events_before_sweep", + "test_delays_fragment_conversion_until_after_covering_union", + ) +) + +Entry = dict[str, object] +Rule = dict[str, object] +Policy = dict[str, list[Rule]] +Network = ipaddress.IPv4Network | ipaddress.IPv6Network + +Case = dict[str, object] + + +class _PosixFixturePath(PurePosixPath): + """A POSIX path whose current directory is fixed.""" + + @classmethod + def cwd(cls) -> _PosixFixturePath: + return cls(CWDS["posix"]) + + +class _WindowsFixturePath(PureWindowsPath): + """A Windows path whose current directory is fixed.""" + + @classmethod + def cwd(cls) -> _WindowsFixturePath: + return cls(CWDS["windows"]) + + +FLAVOR_PATHS: dict[str, type[PurePath]] = { + "posix": PurePosixPath, + "windows": PureWindowsPath, +} +FIXTURE_PATHS: dict[str, type[PurePath]] = { + "posix": _PosixFixturePath, + "windows": _WindowsFixturePath, +} + + +@contextlib.contextmanager +def _flavor(name: str) -> Generator[None]: + """Make sandbox.py use the paths of a flavor, with its fixed current directory.""" + with patch.object(sandbox, "Path", FIXTURE_PATHS[name]): + yield + + +def _error(function: Callable[..., object], *arguments: object) -> str | None: + """Return the message of the ScriptError that function raises, if any.""" + try: + function(*arguments) + except common.ScriptError as error: + return str(error) + return None + + +# Unicode tables. + + +def _ranges(predicate: Callable[[int], bool], start: int) -> list[tuple[int, int]]: + """Return the inclusive ranges of code points from start that satisfy predicate.""" + ranges: list[tuple[int, int]] = [] + first: int | None = None + for code in range(start, MAX_CODE_POINT + 1): + if predicate(code): + if first is None: + first = code + elif first is not None: + ranges.append((first, code - 1)) + first = None + if first is not None: + ranges.append((first, MAX_CODE_POINT)) + return ranges + + +def space_ranges() -> list[tuple[int, int]]: + return _ranges(lambda code: chr(code).isspace(), 0) + + +def decimal_starts() -> list[int]: + starts: list[int] = [] + for first, last in _ranges(lambda code: chr(code).isdecimal(), 0): + if (last - first + 1) % 10: + raise SystemExit(f"decimal digits {first:#x}-{last:#x} are not runs of ten") + for start in range(first, last + 1, 10): + values = [unicodedata.decimal(chr(start + value)) for value in range(10)] + if values != list(range(10)): + raise SystemExit(f"decimal digits at {start:#x} are not 0 to 9") + starts.append(start) + return starts + + +def non_printable_ranges() -> list[tuple[int, int]]: + return _ranges(lambda code: not chr(code).isprintable(), 0x80) + + +def unicode_tables_source() -> str: + """Return the Rust source of the Unicode tables.""" + starts = decimal_starts() + non_printable = non_printable_ranges() + lines = [ + "//! Unicode properties of Python's `str`, from the Unicode " + f"{unicodedata.unidata_version} database", + f"//! of Python {sys.version_info.major}.{sys.version_info.minor}.", + "//!", + "//! Generated by `scripts/nvx_rs_fixtures.py`; do not edit.", + "", + "/// The first code point of each run of ten decimal digits, of category Nd, " + "which `int`", + "/// reads as the digits zero to nine.", + "#[rustfmt::skip]", + "pub(crate) const DECIMAL_STARTS: &[u32] = &[", + ] + for index in range(0, len(starts), 8): + row = ", ".join(f"0x{start:05X}" for start in starts[index : index + 8]) + lines.append(f" {row},") + lines.extend( + ( + "];", + "", + "/// The inclusive ranges of the code points from U+0080 for which " + "`str.isprintable` is false,", + "/// in order: `repr` escapes them.", + "#[rustfmt::skip]", + "pub(crate) const NON_PRINTABLE: &[(u32, u32)] = &[", + ) + ) + for index in range(0, len(non_printable), 4): + row = ", ".join( + f"(0x{first:05X}, 0x{last:05X})" + for first, last in non_printable[index : index + 4] + ) + lines.append(f" {row},") + lines.extend(("];", "")) + return "\n".join(lines) + + +# Random inputs. + + +def _random_text( + rng: random.Random, tokens: Sequence[str], minimum: int, maximum: int +) -> str: + return "".join(rng.choice(tokens) for _ in range(rng.randint(minimum, maximum))) + + +def _mutate(rng: random.Random, text: str, tokens: Sequence[str]) -> str: + """Return text with a few random insertions, deletions, and replacements.""" + characters = list(text) + for _ in range(rng.randint(0, 3)): + operation = rng.randrange(3) + position = rng.randint(0, len(characters)) + if operation == 0: + characters.insert(position, rng.choice(tokens)) + elif characters and position < len(characters): + if operation == 1: + del characters[position] + else: + characters[position] = rng.choice(tokens) + return "".join(characters) + + +def _sometimes( + rng: random.Random, valid: Sequence[str], invalid: Sequence[str], rate: float = 0.1 +) -> str: + """Return a valid value, or at the given rate an invalid one.""" + return rng.choice(invalid if rng.random() < rate else valid) + + +def _random_number(rng: random.Random) -> str: + """Return a decimal number, which may have underscores, leading zeros, or Unicode digits.""" + digits = str(rng.choice((rng.randint(0, 99), rng.randint(0, 2**32 + 9), 65534))) + if rng.random() < 0.2: + digits = "0" + digits + if rng.random() < 0.2 and len(digits) > 1: + position = rng.randint(1, len(digits) - 1) + digits = f"{digits[:position]}_{digits[position:]}" + if rng.random() < 0.1: + digits = "".join( + chr(0xFF10 + int(digit)) if digit.isdigit() else digit for digit in digits + ) + return digits + + +TARGET_COMPONENTS = ("work", "a", "srv", "cache", "é", "data-1", "run", "nvx", "etc") +POLICY_KINDS = ("denied", "allowed", "writable") + + +def _target(rng: random.Random) -> str: + """Return a guest target, which one time in seven has a random irregular part.""" + components = [rng.choice(TARGET_COMPONENTS) for _ in range(rng.randint(1, 3))] + if rng.random() < 1 / 7: + position = rng.randrange(len(components)) + components[position] = _random_text(rng, TARGET_TOKENS, 0, 3) + return "/" + "/".join(components) + + +TEXT_TOKENS = ( + "0", + "1", + "7", + "9", + "a", + "F", + "x", + "X", + "_", + "+", + "-", + " ", + "\t", + "\n", + "\x0b", + "\x1c", + "\x1f", + "\x85", + "\xa0", + "\u2003", + "\u3000", + "\u200b", + "\u0663", + "\uff11", + "\U0001d7ce", + "\x00", + "\x7f", + "\xad", + "\u0378", + "\u2028", + "\U0001f600", + "\U000e0001", + "\ufeff", + "'", + '"', + "\\", + "{", + "}", + ":", + ",", + "=", + "é", + "urn:", + "uuid:", + "0x", +) +HEX = "0123456789abcdefABCDEF" +PATH_TOKENS = ( + "a", + "b", + "é", + "C", + "c", + ":", + "/", + "//", + "\\", + "\\\\", + ".", + "..", + "?", + "UNC", + "unc", + " ", + "x,y", + ";", + "\x00", +) +TARGET_TOKENS = ( + "/", + "a", + "work", + "proc", + "etc", + "machine-id", + "run", + "nvx", + "shares", + ".", + "..", + " ", + "\u3000", + "\x1c", + "=", + ",", + "\\", + "\x00", + "é", +) + + +# Python's string, integer, and UUID operations. + + +def _int_outcome(text: str) -> Case: + try: + return {"int": str(int(text, 10))} + except ValueError as error: + return {"int_error": str(error)} + + +def _uuid_outcome(text: str) -> str | None: + try: + return str(uuid.UUID(text)) + except ValueError: + return None + + +def string_cases(rng: random.Random) -> list[Case]: + texts = [ + "", + " ", + "007", + "+5", + "-5", + " 5 ", + "5_000", + "_5", + "5_", + "5__0", + "0x5", + "1e3", + "\u20035\u3000", + "\uff15", + "\u0664\u0662", + "\x1c42\x1f", + "1\x002", + "it's", + 'say "hi"', + "both ' and \"", + "a\\b", + "\t\n\r\x00\x7f\x1b", + "é\xa0\xad", + "\u2028\u200b\u0378", + "\U0001f600\U000e0001", + "1" * 4300, + "0" * 4300 + "1", + "x" * 300, + "12345678-1234-5678-1234-567812345678", + "{12345678-1234-5678-1234-567812345678}", + "urn:uuid:12345678-1234-5678-1234-567812345678", + "12345678123456781234567812345678", + "12345678-1234-5678-1234-567812345678".upper(), + "uuuuid:id:12345678123456781234567812345678", + "{urn:uuid:12345678-1234-5678-1234-567812345678}", + "0x" + "1" * 30, + "+" + "1" * 31, + "-" + "1" * 31, + " " + "a" * 31, + "1_" + "1" * 30, + "\u0661" + "0" * 31, + "0b" + "1" * 30, + ] + for _ in range(300): + texts.append(_random_text(rng, TEXT_TOKENS, 0, 12)) + canonical = "12345678-1234-5678-1234-567812345678" + for _ in range(200): + text = rng.choice( + ( + canonical, + canonical.replace("-", ""), + f"{{{canonical}}}", + f"urn:uuid:{canonical}", + canonical.upper(), + ) + ) + texts.append(_mutate(rng, text, (*HEX, *TEXT_TOKENS))) + for _ in range(150): + texts.append(_mutate(rng, str(rng.randint(-(10**12), 10**12)), TEXT_TOKENS)) + cases: list[Case] = [] + for text in texts: + case: Case = { + "text": text, + "repr": repr(text), + "split": text.split(), + "strip": text.strip(), + "uuid": _uuid_outcome(text), + } + case.update(_int_outcome(text)) + cases.append(case) + return cases + + +# pathlib. + + +def path_cases(rng: random.Random) -> list[Case]: + texts = [ + "", + ".", + "./a", + "a/./b/", + "a//b", + "a/../b", + "/a", + "//a", + "///a", + "a\\b", + "c:x", + "C:/a/b/", + "c:", + "./c:x", + "//server/share", + "//server/share/x", + "\\\\?\\C:\\x", + "//?/UNC/server/share", + "//?/UNC/server", + "\\\\.\\device\\x", + "\\a", + "é:x", + "\\\\\\x", + "//?/", + "x:y:z", + ] + for _ in range(300): + texts.append(_random_text(rng, PATH_TOKENS, 0, 8)) + cases: list[Case] = [] + for flavor, path_class in FLAVOR_PATHS.items(): + for text in texts: + path = path_class(text) + cases.append( + { + "flavor": flavor, + "text": text, + "str": str(path), + "parts": list(path.parts), + "absolute": path.is_absolute(), + "name": path.name, + "parents": [str(parent) for parent in path.parents], + } + ) + return cases + + +def join_cases(rng: random.Random) -> list[Case]: + pairs = [ + ("/a/b", "c"), + ("/a/b", "/c"), + ("C:\\cwd", "a/b"), + ("C:\\cwd", "C:x"), + ("C:\\cwd", "c:x"), + ("C:\\cwd", "D:x"), + ("C:\\cwd", "\\x"), + ("//server/share", "x"), + ("C:", "x"), + ("", "x"), + ("a", ""), + ] + for _ in range(200): + pairs.append( + ( + _random_text(rng, PATH_TOKENS, 0, 6), + _random_text(rng, PATH_TOKENS, 0, 6), + ) + ) + cases: list[Case] = [] + for flavor, path_class in FLAVOR_PATHS.items(): + for left, right in pairs: + cases.append( + { + "flavor": flavor, + "left": left, + "right": right, + "str": str(path_class(left) / right), + } + ) + return cases + + +def equality_cases(rng: random.Random) -> list[Case]: + pairs = [("C:\\A", "c:/a"), ("C:\\a", "C:\\b"), ("\\\\S\\x\\Y", "//s/X/y")] + for _ in range(100): + left = _random_text(rng, (*PATH_TOKENS, "A", "B"), 0, 5) + pairs.append((left, rng.choice((left.lower(), left.upper(), left)))) + return [ + { + "left": left, + "right": right, + "equal": PureWindowsPath(left) == PureWindowsPath(right), + } + for left, right in pairs + ] + + +def normpath_cases(rng: random.Random) -> list[Case]: + texts = [ + "", + ".", + "a", + "a\\", + "A//B", + "A/./B", + "A/foo/../B", + "C:\\", + "C:", + "C:a\\..\\..", + "C:\\..\\a", + "..\\..\\a", + "a\\..\\..\\b", + "\\\\server\\share\\", + "\\\\server\\share\\a\\..", + ".\\a\\.\\b\\", + "\\", + "./..", + "C:./x", + ] + for _ in range(250): + texts.append(_random_text(rng, PATH_TOKENS, 0, 10)) + return [{"text": text, "normpath": ntpath.normpath(text)} for text in texts] + + +# The launch profile. + + +def identity_cases(rng: random.Random) -> list[Case]: + values = [ + "1000:1001", + "0:0", + "1000", + "1:2:3", + "a:1", + "1:", + " +1_000 :\uff11\uff10", + "4294967295:1", + "4294967296:1", + "-1:1", + "1:0", + "1:0x10", + "1" * 4301 + ":1", + ] + tokens = ("0", "1", "5", "9", "_", "+", "-", " ", "\u3000", "\u0661", ":", "x") + for _ in range(100): + values.append(_random_text(rng, tokens, 0, 12)) + spaces = ("", " ", "\t", "\u3000") + for _ in range(100): + fields = [ + rng.choice(spaces) + + rng.choice(("", "", "+", "-")) + + _random_number(rng) + + rng.choice(spaces) + for _ in range(2) + ] + values.append(":".join(fields)) + cases: list[Case] = [] + for value in values: + try: + identity = sandbox.parse_workload_identity(value) + cases.append({"value": value, "identity": list(identity)}) + except common.ScriptError as error: + cases.append({"value": value, "error": str(error)}) + return cases + + +def layer_cases(rng: random.Random) -> list[Case]: + values = [ + "distro,distro.erofs,11111111-1111-1111-1111-111111111111", + "custom,layers//custom.erofs,{22222222-2222-2222-2222-22222222222A}", + "distro,C:/l/d.erofs,urn:uuid:11111111111111111111111111111111", + "unknown,layer.erofs,11111111-1111-1111-1111-111111111111", + "distro,layer.erofs,not-a-uuid", + "distro,,1", + "distro", + "distro,a", + "distro,a,b,c", + "runtime,a\\b/./c,11111111111111111111111111111111", + ] + roles = ("distro", "runtime", "custom") + uuid_forms: tuple[Callable[[uuid.UUID], str], ...] = ( + str, + lambda value: str(value).upper(), + lambda value: value.hex, + lambda value: f"{{{value}}}", + lambda value: value.urn, + ) + for _ in range(150): + role = _sometimes(rng, roles, ("Distro", "", "other")) + path = _random_text(rng, PATH_TOKENS, 1, 5) if rng.random() < 0.3 else "l.erofs" + identifier = rng.choice(uuid_forms)(uuid.UUID(int=rng.getrandbits(128))) + if rng.random() < 0.3: + identifier = _mutate(rng, identifier, (*HEX, "-", "{", "}", " ")) + values.append(f"{role},{path},{identifier}") + cases: list[Case] = [] + for flavor in FIXTURE_PATHS: + with _flavor(flavor): + for value in values: + try: + layer = sandbox.SandboxLayer.parse(value) + cases.append( + { + "flavor": flavor, + "value": value, + "role": layer.role, + "path": str(layer.path), + "uuid": layer.uuid, + } + ) + except common.ScriptError as error: + cases.append( + {"flavor": flavor, "value": value, "error": str(error)} + ) + return cases + + +def mount_value_cases(rng: random.Random) -> list[Case]: + values = [ + "/workspace", + "/workspace,", + "/workspace,,rw", + "/workspace,host,rx", + "/workspace,host,rw,extra", + "/workspace,alpha,beta", + "/data,/srv/data,rw,ro", + "/data,/srv/data,,rw", + "/data,/srv/data,rw", + "/data,/srv/data", + ",", + "", + ] + for _ in range(100): + values.append(_random_text(rng, (*PATH_TOKENS, ",", "ro", "rw"), 0, 8)) + for _ in range(100): + host_path = _random_text(rng, (*PATH_TOKENS, ","), 1, 4) + mode = _sometimes(rng, ("", ",ro", ",rw"), (",", ",rx", ",RW")) + values.append(f"{_target(rng)},{host_path}{mode}") + cases: list[Case] = [] + for value in values: + try: + target, host_path, access = sandbox.parse_mount_value(value) + cases.append( + { + "value": value, + "target": target, + "host_path": host_path, + "access": access, + } + ) + except common.ScriptError as error: + cases.append({"value": value, "error": str(error)}) + return cases + + +def target_cases(rng: random.Random) -> list[Case]: + targets = [ + "", + "/", + "workspace", + "/workspace/", + "/work//space", + "/work/./space", + "/work/../space", + "/work space", + "/work=space", + "/work\\space", + "/" + "x" * 4096, + "/" + "x" * 4095, + "/" + "é" * 2048, + "/proc", + "/proc/self", + "/sys/fs", + "/dev/shm", + "/.nvx-agent/tools", + "/etc", + "/etc/machine-id", + "/etc/machine-id/child", + "/workspace", + "/procfs", + "/etc/app", + "/run", + "/run/nvx", + "/run/nvx/time", + "/run/nvx/shares", + ] + for _ in range(100): + targets.append(_random_text(rng, TARGET_TOKENS, 0, 8)) + for _ in range(100): + targets.append(_target(rng)) + return [ + {"target": target, "error": _error(sandbox.validate_mount_target, target)} + for target in targets + ] + + +def run_target_cases(rng: random.Random) -> list[Case]: + lists = [ + ["/workspace", "/opt/hostedtoolcache", "/srv/cache"], + ["/workspace", "/workspace/cache"], + ["/run/nvx", "/b"], + ["/run/nvx/shares/x"], + ["/a b", "/b"], + ["relative", "/b"], + ["/" + "x" * 5000], + [], + ] + for _ in range(40): + lists.append( + [_random_text(rng, TARGET_TOKENS, 1, 6) for _ in range(rng.randint(1, 3))] + ) + for _ in range(80): + lists.append([_target(rng) for _ in range(rng.randint(1, 4))]) + return [ + { + "targets": targets, + "error": _error(nvx._validate_run_share_targets, targets), + } + for targets in lists + ] + + +def child_name_cases(rng: random.Random) -> list[Case]: + pairs = [(0, "/workspace"), (1, "/workspace"), (999, "/" + "x" * 1000)] + for _ in range(50): + pairs.append((rng.randint(0, 50), _random_text(rng, TARGET_TOKENS, 1, 8))) + return [ + { + "index": index, + "target": target, + "name": sandbox.aggregate_child_name(index, target), + } + for index, target in pairs + ] + + +def _mount_spec(rng: random.Random) -> Case: + """Return a share whose parts are each valid, except one time in ten.""" + target = _sometimes(rng, (_target(rng),), ("/proc", "/a b", "/etc")) + host_path = _sometimes( + rng, + ("work", "tools/cache", "/srv/data", "C:\\data\\x,y", "C:/w", "w//x/."), + ("a/../b", "a\x00b", _random_text(rng, PATH_TOKENS, 1, 5)), + ) + access = _sometimes(rng, ("", ",ro", ",rw"), (",rx",)) + kind = rng.choice(("directory", "directory", "directory", "file")) + denied: list[str] = [] + allowed: list[str] = [] + writable: list[str] = [] + if kind == "directory" or rng.random() < 0.1: + denied = rng.sample( + ("logs", "secrets", "/abs/out", "C:\\abs\\out"), rng.randint(0, 2) + ) + if denied and rng.random() < 0.5: + allowed = ["logs/payloads"] + if access == ",rw" and rng.random() < 0.5: + writable = rng.sample(("out", "cache", "/abs/out"), rng.randint(1, 2)) + if rng.random() < 0.1: + rng.choice((denied, allowed, writable)).append( + rng.choice(("", ".", "a\x00", "logs", "out")) + ) + return { + "value": f"{target},{host_path}{access}", + "denied": denied, + "allowed": allowed, + "writable": writable, + "owner": rng.choice(("vmm", "vmm", "caller")), + "kind": kind, + } + + +def _parse_mount(spec: Case) -> sandbox.SandboxMount: + return sandbox.SandboxMount.parse( + cast(str, spec["value"]), + tuple(cast(list[str], spec["denied"])), + cast(str, spec["owner"]), + allowed_paths=tuple(cast(list[str], spec["allowed"])), + writable_paths=tuple(cast(list[str], spec["writable"])), + kind=cast(str, spec["kind"]), + ) + + +def _mount_fields(mount: sandbox.SandboxMount) -> Case: + return { + "guest_target": mount.guest_target, + "host_path": str(mount.host_path), + "access": mount.access, + "denied": list(mount.denied_paths), + "allowed": list(mount.allowed_paths), + "writable": list(mount.writable_paths), + "owner": mount.owner, + "kind": mount.kind, + } + + +def mount_cases(rng: random.Random) -> list[Case]: + specs: list[Case] = [ + { + "value": "/opt/hostedtoolcache,host-dir,rw", + "denied": ["logs", "secrets"], + "allowed": [], + "writable": [], + "owner": "vmm", + "kind": "directory", + }, + { + "value": "/tmp/mcp-runtime,host-dir,rw", + "denied": ["mcp-logs"], + "allowed": ["mcp-logs/payloads"], + "writable": ["agent", "cache"], + "owner": "caller", + "kind": "directory", + }, + { + "value": "/w,host,ro", + "denied": [], + "allowed": [], + "writable": ["out"], + "owner": "vmm", + "kind": "directory", + }, + { + "value": "/w,host,rw", + "denied": [], + "allowed": [f"path-{index}" for index in range(129)], + "writable": [], + "owner": "vmm", + "kind": "directory", + }, + { + "value": "/etc/app/settings.json,settings.json,ro", + "denied": [], + "allowed": [], + "writable": [], + "owner": "vmm", + "kind": "file", + }, + ] + for _ in range(100): + specs.append(_mount_spec(rng)) + cases: list[Case] = [] + for flavor in FIXTURE_PATHS: + with _flavor(flavor): + for spec in specs: + case: Case = {"flavor": flavor, **spec} + try: + mount = _parse_mount(spec) + except common.ScriptError as error: + case["error"] = str(error) + cases.append(case) + continue + case["mount"] = _mount_fields(mount) + case["arguments"] = sandbox.mounts_openvmm_arguments((mount,)) + case["fragment"] = mount.command_line_fragment() + case["absolute"] = _mount_fields(mount.absolute()) + case["absolute_policy"] = [ + [kind, list(paths)] for kind, paths in mount.absolute_policy_paths() + ] + cases.append(case) + return cases + + +def _launch_spec(rng: random.Random) -> Case: + roles = rng.sample(("distro", "runtime", "custom"), rng.choice((1, 1, 2, 3))) + if rng.random() < 0.05: + roles.append(roles[0]) + layers = [ + f"{role},{rng.choice(('layer.erofs', 'C:/layers/x.erofs', 'a//b.erofs'))}," + f"{uuid.UUID(int=rng.getrandbits(128))}" + for role in roles + ] + argument_tokens = ("a", "--serve", "x" * 40, "é", "=", "-") + arguments = [ + _random_text(rng, argument_tokens, 1, 3) + for _ in range(rng.choice((0, 1, 2, 5, 20))) + ] + if arguments and rng.random() < 0.05: + arguments[rng.randrange(len(arguments))] = rng.choice(("", " a", "a\x1cb")) + mounts: list[Case] = [] + for index in range(rng.choice((0, 0, 1, 2, 3, 6))): + kind = rng.choice(("directory", "directory", "file")) + mounts.append( + { + "value": f"/share/{index:02}{'x' * rng.randint(0, 120)}," + f"{rng.choice(('host', 'C:/host', '/srv'))}{index}" + f"{rng.choice(('', ',ro', ',rw'))}", + "denied": ( + rng.choice(([], ["secrets"], ["/abs/secrets"])) + if kind == "directory" + else [] + ), + "allowed": [], + "writable": [], + "owner": "vmm", + "kind": kind, + } + ) + identity = [rng.choice((1000, 65534)), rng.choice((1001, 65534))] + if rng.random() < 0.05: + identity[rng.randrange(2)] = 0 + # Each setting is invalid one time in twenty, so most launches reach the command line. + rate = 0.05 + return { + "layers": layers, + "scratch": rng.choice(("scratch.ext4", "C:/s/scratch.ext4", "s//x.ext4")), + "entrypoint": _sometimes( + rng, + ("/bin/sh", "/bin/workload", "/usr/bin/python3"), + ("bin/sh", "/bin/s h"), + rate, + ), + "args": arguments, + "hostname": _sometimes( + rng, + ("nvx-sandbox", "example", "a" * 63, "a-1"), + ("Example", "a" * 64, "-a"), + rate, + ), + "identity": identity, + "memory_max": _sometimes( + rng, ("", "", "", "268435456", str(10**30)), ("0", "-1"), rate + ) + or None, + "pids_max": _sometimes( + rng, ("", "", "", "64", str(10**20 - 1)), ("0", "-5"), rate + ) + or None, + "mounts": mounts, + "command_line": rng.choice( + ( + "", + "", + "quiet", + " quiet loglevel=0 ", + "\u3000quiet\x1c", + "nvx_sandbox=0", + "virtfs_dir=/x", + "a\x00b", + "x" * rng.randint(100, 1000), + "é" * rng.randint(50, 500), + "a\x1cnvx_x=1", + ) + ), + } + + +def _launch(flavor: str, spec: Case) -> sandbox.SandboxLaunch: + """Return the launch of a specification, under the flavor's patched paths. + + The launch's layers and shares are valid, so that each case checks the launch + itself. + """ + layers = tuple( + sandbox.SandboxLayer.parse(value) for value in cast(list[str], spec["layers"]) + ) + mounts = tuple(_parse_mount(mount) for mount in cast(list[Case], spec["mounts"])) + identity = cast(list[int], spec["identity"]) + memory_max = cast(str | None, spec["memory_max"]) + pids_max = cast(str | None, spec["pids_max"]) + return sandbox.SandboxLaunch( + layers=layers, + scratch=cast(Path, FIXTURE_PATHS[flavor](cast(str, spec["scratch"]))), + entrypoint=cast(str, spec["entrypoint"]), + args=tuple(cast(list[str], spec["args"])), + hostname=cast(str, spec["hostname"]), + workload_identity=(identity[0], identity[1]), + memory_max=None if memory_max is None else int(memory_max), + pids_max=None if pids_max is None else int(pids_max), + mounts=mounts, + ) + + +def _budget_specs() -> list[Case]: + """Return launches whose kernel command lines fill the sandbox budget exactly, + and by one byte more, with each kind of share set.""" + share_sets = ( + (), + (("/workspace,host,rw", "directory"),), + (("/workspace,host,rw", "directory"), ("/srv/cache,cache", "directory")), + (("/etc/app/settings.json,settings.json", "file"),), + ) + specs: list[Case] = [] + for shares in share_sets: + spec: Case = { + "layers": ["distro,layer.erofs,11111111-1111-1111-1111-111111111111"], + "scratch": "scratch.ext4", + "entrypoint": "/bin/sh", + "args": [], + "hostname": "nvx-sandbox", + "identity": [65534, 65534], + "memory_max": None, + "pids_max": None, + "mounts": [ + { + "value": value, + "denied": [], + "allowed": [], + "writable": [], + "owner": "vmm", + "kind": kind, + } + for value, kind in shares + ], + "command_line": "", + } + with _flavor("posix"): + launch = _launch("posix", spec) + fragment = sandbox.mounts_command_line_fragment(launch.mounts) + used = len(launch.kernel_command_line("").encode()) + len(fragment.encode()) + # The user's command line comes first, with a space after it, and a NUL ends + # the whole command line. + room = sandbox.SANDBOX_COMMAND_LINE_MAX_SIZE - used - 2 + specs.extend({**spec, "command_line": "x" * (room + extra)} for extra in (0, 1)) + return specs + + +def launch_cases(rng: random.Random) -> list[Case]: + specs = [_launch_spec(rng) for _ in range(80)] + specs.extend(_budget_specs()) + cases: list[Case] = [] + for flavor in FIXTURE_PATHS: + with _flavor(flavor): + for spec in specs: + case: Case = {"flavor": flavor, **spec} + try: + launch = _launch(flavor, spec) + except common.ScriptError as error: + case["error"] = str(error) + cases.append(case) + continue + case["arguments"] = launch.openvmm_arguments() + command_line = cast(str, spec["command_line"]) + try: + case["kernel_command_line"] = launch.kernel_command_line( + command_line + ) + except common.ScriptError as error: + case["kernel_error"] = str(error) + cases.append(case) + return cases + + +# The helpers of nvx.py. + + +def sandbox_mounts_cases(rng: random.Random) -> list[Case]: + specs: list[Case] = [ + { + "mounts": ["/workspace,work,rw", "/opt/hostedtoolcache,tools,ro"], + "policy": [ + ["denied", 0, "logs"], + ["allowed", 0, "logs/payloads"], + ["writable", 0, "out"], + ["denied", 1, "credentials"], + ], + "owner": None, + "files": [], + }, + { + "mounts": ["/w,work,rw"], + "policy": [["denied", None, "secrets"]], + "owner": "caller", + "files": [], + }, + { + "mounts": ["/workspace,work,rw", "/opt/hostedtoolcache,tools"], + "policy": [["allowed", None, "logs"], ["denied", None, "secrets"]], + "owner": None, + "files": [], + }, + { + "mounts": ["/etc/app/settings.json,settings.json,rw"], + "policy": [["writable", 0, "settings.json"]], + "owner": None, + "files": ["settings.json"], + }, + {"mounts": [], "policy": [["denied", None, "x"]], "owner": None, "files": []}, + ] + for _ in range(120): + files = ["f.json"] if rng.random() < 0.5 else [] + values: list[str] = [] + policy: list[list[object]] = [] + if rng.random() < 0.1: + policy.append([rng.choice(POLICY_KINDS), None, "early"]) + for index in range(rng.randint(0, 3)): + host_path = rng.choice(("work", "tools", "f.json", "C:/x")) + mode = rng.choice(("", ",ro", ",rw")) + values.append(f"/m{index},{host_path}{mode}") + if host_path in files and rng.random() < 0.9: + continue + denied = rng.sample(("logs", "secrets", "/d"), rng.randint(0, 2)) + policy.extend(["denied", index, path] for path in denied) + if denied and rng.random() < 0.5: + policy.append(["allowed", index, "logs/payloads"]) + if mode == ",rw" and rng.random() < 0.5: + policy.append(["writable", index, "out"]) + if rng.random() < 0.1: + policy.append( + [rng.choice(POLICY_KINDS), index, rng.choice(("", "logs"))] + ) + specs.append( + { + "mounts": values, + "policy": policy, + "owner": rng.choice((None, "vmm", "caller")), + "files": files, + } + ) + destinations = { + "denied": "mount_deny", + "allowed": "mount_allow", + "writable": "mount_write", + } + cases: list[Case] = [] + for flavor in FIXTURE_PATHS: + with _flavor(flavor): + for spec in specs: + files = set(cast(list[str], spec["files"])) + namespace = argparse.Namespace( + mount=list(cast(list[str], spec["mounts"])), + mount_owner=spec["owner"], + mount_deny=[], + mount_allow=[], + mount_write=[], + ) + for kind, preceding, path in cast(list[list[object]], spec["policy"]): + index = -1 if preceding is None else cast(int, preceding) + getattr(namespace, destinations[cast(str, kind)]).append( + (index, path) + ) + + def kind_of(host_path: PurePath, files: set[str] = files) -> str: + return "file" if str(host_path) in files else "directory" + + case: Case = {"flavor": flavor, **spec} + try: + with patch.object(nvx, "mount_kind", kind_of): + mounts = nvx._sandbox_mounts(namespace) + case["result"] = [_mount_fields(mount) for mount in mounts] + except common.ScriptError as error: + case["error"] = str(error) + cases.append(case) + return cases + + +def environment_cases(rng: random.Random) -> list[Case]: + lists: list[list[str]] = [ + [f"KEY{index}=value" for index in range(257)], + [f"KEY{index}=value" for index in range(256)], + ["NO_EQUALS"], + ["=empty-key"], + ["KEY=embedded\x00nul"], + ["KEY=" + "\N{SNOWMAN}" * 1365], + ["K=" + "v" * 4094], + ["DUPLICATE=one", "DUPLICATE=two"], + ["A=1", "B=2"], + [], + ] + tokens = ("A", "B", "=", "", "\x00", "é", "x" * 1000) + for _ in range(30): + lists.append( + [_random_text(rng, tokens, 0, 5) for _ in range(rng.randint(0, 4))] + ) + values = ("", "1", "a=b", "é", "\N{SNOWMAN}" * 300, "x" * 1000, " spaced ") + for _ in range(40): + entries = [ + f"{rng.choice(('A', 'B', 'PATH', 'K_1', 'É'))}={rng.choice(values)}" + for _ in range(rng.randint(0, 5)) + ] + if entries and rng.random() < 0.1: + entries[rng.randrange(len(entries))] = rng.choice( + ("NO_EQUALS", "=x", "A=\0") + ) + lists.append(entries) + cases: list[Case] = [] + for entries in lists: + try: + control_session.encode_exec_environment(tuple(entries)) + cases.append({"entries": entries, "error": None}) + except (TypeError, ValueError) as error: + cases.append({"entries": entries, "error": str(error)}) + return cases + + +def _encode_json_value(value: object) -> object: + """Return a JSON value in the tagged form that the Rust test compares.""" + if value is None: + return ["null"] + if isinstance(value, bool): + return ["bool", value] + if isinstance(value, int): + return ["int", str(value)] + if isinstance(value, float): + return ["float", struct.pack(">d", value).hex()] + if isinstance(value, str): + return _encode_string(value) + if isinstance(value, list): + return [ + "list", + [_encode_json_value(item) for item in cast(list[object], value)], + ] + members = cast(dict[str, object], value) + return [ + "dict", + [ + [_encode_string(key), _encode_json_value(item)] + for key, item in members.items() + ], + ] + + +def _encode_string(value: str) -> object: + try: + value.encode("utf-8") + except UnicodeEncodeError: + return ["code_points", [ord(character) for character in value]] + return ["str", value] + + +JSON_SEEDS = ( + '{"format": 1, "artifact": "a.erofs", "packages": [{"name": "systemd"}]}', + '["A=1", "B=\\u00e9", "C=\\ud83d\\ude00"]', + "[NaN, Infinity, -Infinity, 1e400, -0.0, 1E-5, 10, -3]", + '{"a": [true, false, null], "a": {"b": "\\n\\t\\\\\\"\\/"}}', + '"\\ud800\\udc00\\udc00\\ud800x"', + "[]", + "{}", + " \t\r\n[1 , 2 ] ", +) +JSON_TOKENS = ( + "[", + "]", + "{", + "}", + ",", + ":", + '"', + "\\", + "u", + "d800", + "0", + "1", + "-", + ".", + "e", + "+", + " ", + "\n", + "\r", + "\t", + "\x00", + "\x1f", + "null", + "true", + "NaN", + "Infinity", + "x", + "é", + "\ufeff", +) + + +def json_loads_cases(rng: random.Random) -> list[Case]: + texts: list[str] = list(JSON_SEEDS) + texts.extend( + ( + "", + "[", + "[1", + "[1,]", + '{"a":1,}', + "{1:2}", + '{"a" 1}', + '"abc', + '"a\tb"', + '"\\x"', + '"\\u12"', + '"\\u12x4"', + '"\\ud800\\u12x4"', + "[]\n x", + "\ufeff[]", + "-", + "01", + "1.", + "1e", + "1e+", + "-Infinityx", + "nul", + "[" * 50 + "]" * 50, + "1" * 4301, + "[" + "1" * 4300 + "]", + "1." + "1" * 5000, + ) + ) + for _ in range(400): + texts.append(_mutate(rng, rng.choice(JSON_SEEDS), JSON_TOKENS)) + cases: list[Case] = [] + for text in texts: + try: + value = json.loads(text) + cases.append({"text": text, "value": _encode_json_value(value)}) + except json.JSONDecodeError as error: + cases.append({"text": text, "decode_error": str(error)}) + except ValueError as error: + cases.append({"text": text, "value_error": str(error)}) + return cases + + +def utf8_cases(rng: random.Random) -> list[Case]: + samples = [ + b"ok", + b"\xff", + b"a\xe2\x82", + b"\xe2(", + b"\xf0\x90(", + b"\xed\xa0\x80", + b"\xc0\x80", + b"\xf4\x90\x80\x80", + b"\xe0\x80\x80", + b"\xf0\x80\x80\x80", + b"\xf0\x9f\x98", + ] + pieces = (b"a", b"\xc3\xa9", b"\xe2\x82\xac", b"\xf0\x9f\x98\x80", b"\x80", b"\xff") + for _ in range(150): + data = b"".join(rng.choice(pieces) for _ in range(rng.randint(0, 6))) + cut = rng.randint(0, len(data)) + samples.append(data[:cut] + data[cut + rng.randint(0, 2) :]) + cases: list[Case] = [] + for data in samples: + try: + data.decode("utf-8") + cases.append({"bytes": data.hex(), "error": None}) + except UnicodeDecodeError as error: + cases.append({"bytes": data.hex(), "error": str(error)}) + return cases + + +class _Executed(Exception): + """Raised in place of a managed exec, with the environment that it received.""" + + def __init__(self, environment: tuple[str, ...] | None) -> None: + super().__init__("executed") + self.environment = environment + + +def _document(data: bytes) -> Case: + """Return a document as its text when it is UTF-8, or else as its bytes in hex.""" + try: + return {"text": data.decode("utf-8")} + except UnicodeDecodeError: + return {"bytes": data.hex()} + + +def environment_file_cases(rng: random.Random) -> list[Case]: + # A document is its bytes, or a number of spaces. + documents: list[bytes | int] = [ + b"[]", + b'["A=1", "B=2"]', + b'["DUPLICATE=one", "DUPLICATE=two"]', + 1024 * 1024 + 1, + 1024 * 1024, + b"{}", + b'"A=1"', + b"[1]", + b"[NaN]", + b'[["A=1"]]', + b"", + b"[", + b'["A=1",]', + "\ufeff[]".encode(), + b'["\xff"]', + b'["A=1", "B=\\ud800"]', + b'["B=\\udc00\\ud800x"]', + b"[" + b"1" * 4301 + b"]", + b'["A=\\u00e9"]\r\n', + ] + for _ in range(60): + text = _mutate(rng, rng.choice(JSON_SEEDS[1:2] + JSON_SEEDS[5:]), JSON_TOKENS) + documents.append(text.encode("utf-8", "surrogatepass")) + for _ in range(30): + entries = [ + f"{rng.choice(('A', 'B', 'É'))}={rng.choice(('1', 'é', '😀', 'x' * 100))}" + for _ in range(rng.randint(0, 4)) + ] + documents.append(json.dumps(entries, ensure_ascii=rng.random() < 0.5).encode()) + cases: list[Case] = [] + with tempfile.TemporaryDirectory(prefix="nvx-rs-fixtures-") as directory: + path = Path(directory) / "environment.json" + for document in documents: + if isinstance(document, int): + path.write_bytes(b" " * document) + case: Case = {"spaces": document} + else: + path.write_bytes(document) + case = _document(document) + arguments = nvx.parse_args( + [ + "sandbox", + "exec", + "--state-dir", + "absent-state", + "--environment-file", + str(path), + ] + ) + + def execute(*_: object, **keywords: object) -> object: + raise _Executed(cast(tuple[str, ...] | None, keywords["environment"])) + + try: + with patch.object(sandbox_lifecycle, "exec_workload", execute): + nvx.command_sandbox(arguments) + raise SystemExit("the managed exec did not run") + except _Executed as executed: + case["environment"] = ( + None if executed.environment is None else list(executed.environment) + ) + except (common.ScriptError, ValueError) as error: + case["error"] = str(error).replace(str(path), "{path}") + cases.append(case) + return cases + + +def distro_manifest_cases(rng: random.Random) -> list[Case]: + manifests: list[bytes] = [] + with tempfile.TemporaryDirectory(prefix="nvx-rs-fixtures-") as directory: + root = Path(directory) + layer_path = root / "distro.erofs" + layer_path.write_text(DISTRO_LAYER, encoding="utf-8") + digest = common.sha256_file(layer_path) + template = ( + '{{"format": {format}, "artifact": {artifact}, ' + '"artifact_sha256": {sha256}, "packages": {packages}}}' + ) + for format_value in ("1", "1.0", "true", '"1"', "2", "NaN"): + for artifact in ('"distro.erofs"', '"other.erofs"', "1"): + for sha256 in (f'"{digest}"', '"0"'): + for packages in ( + '[{"name": "systemd"}]', + '[{"name": "bash"}]', + "[]", + "{}", + "[1]", + '[{"name": 1}]', + '[{"name": "systemd"}, 1]', + ): + manifests.append( + template.format( + format=format_value, + artifact=artifact, + sha256=sha256, + packages=packages, + ).encode() + ) + manifests.extend( + ( + b"[]", + b'{\r\n"format": }', + b"{\xff}", + b"\xef\xbb\xbf{}", + b'{"format": 1, "artifact": "distro.erofs", "format": 2}', + b"1" * 4301, + ) + ) + valid = template.format( + format="1", + artifact='"distro.erofs"', + sha256=f'"{digest}"', + packages='[{"name": "bash"}]', + ) + for _ in range(60): + manifests.append( + _mutate(rng, valid, JSON_TOKENS).encode("utf-8", "surrogatepass") + ) + launch = sandbox.SandboxLaunch( + layers=( + sandbox.SandboxLayer( + role="distro", + path=layer_path, + uuid="11111111-1111-1111-1111-111111111111", + ), + ), + scratch=root / "scratch.ext4", + entrypoint="/sbin/init", + ) + manifest_path = root / "distro.erofs.manifest.json" + cases: list[Case] = [] + for manifest in manifests: + manifest_path.write_bytes(manifest) + try: + nvx._validate_sandbox_systemd_policy(launch) + error = None + except (common.ScriptError, ValueError) as raised: + error = str(raised) + cases.append({"manifest": _document(manifest), "error": error}) + return cases + + +def python_fixture() -> dict[str, object]: + """Return the cases of the Python semantics that the port reproduces.""" + rng = random.Random(SEED) + return { + **_header(), + "unicode": unicodedata.unidata_version, + "space": space_ranges(), + "decimal_starts": decimal_starts(), + "non_printable": non_printable_ranges(), + "strings": string_cases(rng), + "paths": path_cases(rng), + "joins": join_cases(rng), + "equality": equality_cases(rng), + "normpath": normpath_cases(rng), + "json": json_loads_cases(rng), + "utf8": utf8_cases(rng), + } + + +def sandbox_fixture() -> dict[str, object]: + """Return the cases of the launch profile of scripts/nvx_tools/sandbox.py.""" + rng = random.Random(SEED + 1) + return { + **_header(), + "cwds": CWDS, + "identities": identity_cases(rng), + "layers": layer_cases(rng), + "mount_values": mount_value_cases(rng), + "mount_targets": target_cases(rng), + "child_names": child_name_cases(rng), + "mounts": mount_cases(rng), + "launches": launch_cases(rng), + } + + +def sandbox_options_fixture() -> dict[str, object]: + """Return the cases of the sandbox helpers of scripts/nvx.py.""" + rng = random.Random(SEED + 2) + return { + **_header(), + "cwds": CWDS, + "run_share_targets": run_target_cases(rng), + "sandbox_mounts": sandbox_mounts_cases(rng), + "environments": environment_cases(rng), + "environment_files": environment_file_cases(rng), + "distro_layer": DISTRO_LAYER, + "distro_manifests": distro_manifest_cases(rng), + } + + +def _header() -> dict[str, object]: + return { + "generator": "scripts/nvx_rs_fixtures.py", + "python": f"{sys.version_info.major}.{sys.version_info.minor}", + } + + +FIXTURES: dict[str, Callable[[], dict[str, object]]] = { + "python.json": python_fixture, + "sandbox.json": sandbox_fixture, + "sandbox_options.json": sandbox_options_fixture, +} + + +def fixture_text(document: dict[str, object]) -> str: + """Return the fixture with one case per line, in ASCII.""" + lines = ["{"] + items = list(document.items()) + for position, (key, value) in enumerate(items): + comma = "," if position < len(items) - 1 else "" + if isinstance(value, list) and value and isinstance(value[0], dict): + cases = cast(list[object], value) + lines.append(f"{json.dumps(key)}: [") + for index, case in enumerate(cases): + separator = "," if index < len(cases) - 1 else "" + lines.append(json.dumps(case, separators=(",", ":")) + separator) + lines.append(f"]{comma}") + else: + lines.append(f"{json.dumps(key)}: {json.dumps(value)}{comma}") + lines.append("}") + return "\n".join(lines) + "\n" + + +# The egress policy compiler. + + +def printable(text: str) -> str: + """Return `text` as Python prints it to UTF-8, with surrogates as `\\udXXX`.""" + return text.encode("utf-8", "backslashreplace").decode("utf-8") + + +def segments(data: bytes) -> list[Entry]: + """Describe `data` as text, or else as hexadecimal, with long runs repeated.""" + try: + text = data.decode("utf-8") + except UnicodeDecodeError: + return [ + {"hex": run.hex(), **({"repeat": count} if count > 1 else {})} + for run, count in runs(data) + ] + return [ + {"text": run, **({"repeat": count} if count > 1 else {})} + for run, count in runs(text) + ] + + +def runs(items: AnyStr) -> list[tuple[AnyStr, int]]: + """Split a string or bytes into literal stretches and long runs of one item.""" + result: list[tuple[AnyStr, int]] = [] + start = 0 + index = 0 + while index < len(items): + end = index + while end < len(items) and items[end] == items[index]: + end += 1 + if end - index >= MIN_REPEAT: + if start < index: + result.append((items[start:index], 1)) + result.append((items[index : index + 1], end - index)) + start = end + index = end + if start < len(items) or not result: + result.append((items[start:], 1)) + return result + + +def outcome(path: Path) -> Entry: + """Compile the policy file at `path` and describe the result.""" + try: + compiled = compile_policy_file(path) + except ScriptError as error: + message = str(error).replace(str(path), PATH_PLACEHOLDER) + return {"error": printable(message)} + return {"allow": list(compiled.allow), "deny": list(compiled.deny)} + + +def test_vectors() -> list[tuple[str, bytes]]: + """Return every policy file that `scripts/test_egress_policy.py` compiles.""" + captured: list[tuple[str, bytes]] = [] + original = test_egress_policy.compile_policy_file + + def capture(path: Path) -> CompiledEgressPolicy: + test = next( + frame.function + for frame in inspect.stack() + if frame.function.startswith("test_") + ) + captured.append((test, path.read_bytes())) + return original(path) + + test_egress_policy.compile_policy_file = capture + try: + suite = unittest.defaultTestLoader.loadTestsFromModule(test_egress_policy) + result = unittest.TextTestRunner(stream=io.StringIO()).run(suite) + finally: + test_egress_policy.compile_policy_file = original + if not result.wasSuccessful(): + raise SystemExit("scripts/test_egress_policy.py failed") + seen: set[bytes] = set() + counts: dict[str, int] = {} + vectors: list[tuple[str, bytes]] = [] + for test, data in captured: + if data not in seen and test not in LARGE_VECTORS: + seen.add(data) + counts[test] = counts.get(test, 0) + 1 + vectors.append((f"{test} #{counts[test]}", data)) + return vectors + + +def policy(value: object) -> bytes: + return json.dumps(value).encode() + + +def rules(category: str, items: Iterable[object]) -> bytes: + return policy({category: list(items)}) + + +def json_cases() -> Iterator[tuple[str, bytes]]: + """Yield cases of the JSON reader: syntax, numbers, hooks, and strings.""" + cidr = "192.0.2.0/24" + port = f'{{"allow": [{{"cidr": "{cidr}", "protocol": "tcp", "port": %s}}]}}' + for name, text in ( + ("empty file", ""), + ("whitespace only", " \t\r\n"), + ("null", "null"), + ("array root", "[]"), + ("number root", "1"), + ("string root", '"allow"'), + ("empty object", "{}"), + ("surrounding whitespace", ' \t\n\r{"allow": []}\r\n '), + ("form feed after", '{"allow": []}\f'), + ("vertical tab before", '\v{"allow": []}'), + ("no-break space after", '{"allow": []}\u00a0'), + ("trailing comma in object", '{"allow": [],}'), + ("trailing comma in array", f'{{"allow": [{{"cidr": "{cidr}"}},]}}'), + ("comment", '{"allow": []} // comment'), + ("single quotes", "{'allow': []}"), + ("unquoted name", "{allow: []}"), + ("missing colon", '{"allow" []}'), + ("missing comma", '{"allow": [] "deny": []}'), + ("unclosed object", '{"allow": []'), + ("unclosed string", '{"allow'), + ("extra value", '{"allow": []} {}'), + ("two roots", "{}{}"), + ("constant prefix", '{"allow": nullx}'), + ("raw tab in string", f'{{"allow": [{{"cidr": "{cidr}\t"}}]}}'), + ("escaped tab in string", f'{{"allow": [{{"cidr": "{cidr}\\t"}}]}}'), + ("raw newline in name", '{"al\nlow": []}'), + ("invalid escape", '{"allow": [{"cidr": "\\x41"}]}'), + ("short unicode escape", '{"allow": [{"cidr": "\\u41"}]}'), + ("solidus escape", '{"allow": [{"cidr": "192.0.2.0\\/24"}]}'), + ("escaped names", '{"\\u0061llow": [{"\\u0063idr": "192.0.2.7"}]}'), + ("escaped duplicate", '{"allow": [], "\\u0061llow": []}'), + ("deep arrays in a rule list", '{"allow": ' + "[" * 100 + "]" * 100 + "}"), + ("deep arrays", "[" * 500 + "]" * 500), + ("deep objects", '{"allow": [' + '{"a": ' * 200 + "1" + "}" * 200 + "]}"), + ): + yield f"json: {name}", text.encode() + for value in ( + "01", + "1.", + ".5", + "+80", + "0x50", + "8e", + "8e+", + "nan", + "-NaN", + "Inf", + "NaN", + "Infinity", + "-Infinity", + "1e400", + "-1e400", + "80.0", + "8e1", + "1E2", + "1e-400", + "-0", + "0", + "true", + "false", + "null", + '"80"', + "[80]", + "{}", + "65535", + "65536", + "-1", + "9" * 64, + "-" + "9" * 64, + "9" * 65, + "-" + "9" * 65, + "1" + "0" * 4300, + ): + yield f"json: port {value[:16]}", (port % value).encode() + yield ( + f"json: endPort {value[:16]}", + f'{{"deny": [{{"ports": [{{"port": 1, "endPort": {value}}}]}}]}}'.encode(), + ) + long_digits = "9" * 65 + for name, text in ( + ("duplicate root field", '{"deny": [{"cidr": "0.0.0.0/0"}], "deny": []}'), + ("duplicate rule field", '{"allow": [{"cidr": "10.0.0.0/8", "cidr": "::/0"}]}'), + ("duplicate port field", '{"allow": [{"ports": [{"port": 1, "port": 2}]}]}'), + ("duplicate peer field", '{"allow": [{"to": [{"except": [], "except": []}]}]}'), + ("first duplicate in order", '{"b": 1, "a": 2, "a": 3, "b": 4}'), + ("duplicate before long integer", f'{{"a": 1, "a": 2, "b": {long_digits}}}'), + ("inner duplicate first", f'{{"a": {{"x": 1, "x": 2}}, "b": {long_digits}}}'), + ("long integer first", f'{{"b": {long_digits}, "a": {{"x": 1, "x": 2}}}}'), + ("duplicate before syntax error", '{"a": 1, "a": 2,}'), + ("duplicate before extra data", '{"a": 1, "a": 2} x'), + ("long integer before syntax error", f"[{long_digits}x]"), + ("long integer in unknown field", f'{{"unknown": [{long_digits}]}}'), + ("duplicate in unknown field", '{"unknown": {"x": 1, "x": 2}}'), + ("duplicate surrogate names", '{"\\ud800": 1, "\\ud800": 2}'), + ("distinct surrogate names", '{"\\ud800": 1, "\\udc00": 2}'), + ("surrogate root name", '{"\\ud800": []}'), + ("surrogate rule name", '{"allow": [{"\\udfff": 1}]}'), + ("surrogate cidr", '{"allow": [{"cidr": "\\ud800"}]}'), + ("surrogate scope", '{"allow": [{"cidr": "fe80::1%\\ud800/64"}]}'), + ( + "surrogate protocol", + f'{{"allow": [{{"cidr": "{cidr}", "protocol": "\\udc00"}}]}}', + ), + ("paired surrogates", '{"allow": [{"cidr": "fe80::1%\\ud83d\\ude00/64"}]}'), + ("reversed surrogates", '{"allow": [{"cidr": "fe80::1%\\ude00\\ud83d/64"}]}'), + ("high surrogate before letter", '{"allow": [{"cidr": "\\ud800A"}]}'), + ("high surrogate before bad escape", '{"allow": [{"cidr": "\\ud800\\u12g4"}]}'), + ("surrogate exception", '{"allow": [{"cidr": "::/0", "except": ["\\udbff"]}]}'), + ): + yield f"json: {name}", text.encode() + for name, data in ( + ("byte-order mark", b'\xef\xbb\xbf{"allow": []}'), + ("invalid byte", b'{"allow": [\xff]}'), + ("invalid byte in string", b'{"allow": [{"cidr": "192.0.2.0/24\xfe"}]}'), + ("overlong encoding", b'{"allow": [{"cidr": "\xc0\xaf"}]}'), + ("encoded surrogate", b'{"allow": [{"cidr": "\xed\xa0\x80"}]}'), + ("truncated sequence", b'{"\xe2\x82": []}'), + ("utf-16", '{"allow": []}'.encode("utf-16")), + ("non-ascii text", '{"allow": [{"cidr": "192.0.2.0/24\u00e9"}]}'.encode()), + ): + yield f"utf-8: {name}", data + # Unknown fields are named in code-point order. + for name, fields in ( + ("ascii", ("b", "a", "B")), + ("non-ascii", ("\u00e9", "z", "\u00ff")), + ("astral and surrogate", ("\\ud83d\\ude00", "\\ud800", "\\uffff")), + ): + members = ", ".join(f'"{field}": 1' for field in fields) + yield f"unknown fields: {name}", ("{" + members + "}").encode() + yield ( + f"unknown rule fields: {name}", + ('{"allow": [{"cidr": "192.0.2.0/24", ' + members + "}]}").encode(), + ) + + +def format_items(cidr: str) -> Iterator[tuple[str, list[object]]]: + """Yield rule lists that reach each message of the format, in check order.""" + tcp: Rule = {"cidr": cidr, "protocol": "tcp"} + items: tuple[tuple[str, object], ...] = ( + ("rule string", cidr), + ("rule number", 7), + ("rule null", None), + ("rule array", [{"cidr": cidr}]), + ("empty rule", Rule()), + ("mixed forms", {"to": [{"cidr": cidr}], "cidr": cidr}), + ("mixed ports", {"ports": list[Rule](), "port": 80}), + ("legacy unknown field", {"cidr": cidr, "unknown": 1}), + ("legacy unknown before missing cidr", {"port": 80, "unknown": 1}), + ("legacy missing cidr", {"protocol": "tcp", "port": 80}), + ("legacy except only", {"except": list[str]()}), + ("legacy cidr number", {"cidr": 7}), + ("legacy cidr null", {"cidr": None}), + ("legacy cidr invalid", {"cidr": "192.0.2.0/33"}), + ("legacy cidr scoped", {"cidr": "fe80::1%eth0/64"}), + ("legacy except string", {"cidr": cidr, "except": "192.0.2.1"}), + ("legacy except null", {"cidr": cidr, "except": None}), + ("legacy except number", {"cidr": cidr, "except": [7]}), + ("legacy except outside", {"cidr": cidr, "except": ["198.51.100.0/24"]}), + ("legacy except family", {"cidr": cidr, "except": ["2001:db8::/32"]}), + ("legacy except wider", {"cidr": cidr, "except": ["192.0.0.0/16"]}), + ("legacy except whole", {"cidr": cidr, "except": [cidr]}), + ("legacy except invalid", {"cidr": cidr, "except": ["192.0.2.0/24", "x"]}), + ("legacy endPort without port", {"cidr": cidr, "endPort": 80}), + ("legacy endPort with protocol", {**tcp, "endPort": 80}), + ("legacy port without protocol", {"cidr": cidr, "port": 80}), + ("legacy protocol upper", {"cidr": cidr, "protocol": "TCP"}), + ("legacy protocol unknown", {"cidr": cidr, "protocol": "sctp"}), + ("legacy protocol empty", {"cidr": cidr, "protocol": ""}), + ("legacy protocol null", {"cidr": cidr, "protocol": None}), + ("legacy protocol number", {"cidr": cidr, "protocol": 6}), + ("legacy icmp port", {"cidr": cidr, "protocol": "icmp", "port": 8}), + ("legacy icmp endPort", {"cidr": cidr, "protocol": "icmp", "endPort": 8}), + ("legacy port zero", {**tcp, "port": 0}), + ("legacy port bool", {**tcp, "port": True}), + ("legacy port string", {**tcp, "port": "80"}), + ("legacy endPort zero", {**tcp, "port": 1, "endPort": 0}), + ("legacy endPort bool", {**tcp, "port": 1, "endPort": False}), + ("legacy endPort below", {**tcp, "port": 10, "endPort": 5}), + ( + "legacy any range", + {"cidr": cidr, "protocol": "any", "port": 1, "endPort": 9}, + ), + ("legacy any", {"cidr": cidr, "protocol": "any"}), + ("legacy icmp", {"cidr": cidr, "protocol": "icmp"}), + ("mxc unknown field", {"to": [{"cidr": cidr}], "unknown": 1}), + ("mxc unknown only", {"unknown": 1}), + ("mxc to object", {"to": {"cidr": cidr}}), + ("mxc to null", {"to": None}), + ("mxc to empty", {"to": list[Rule]()}), + ("mxc peer string", {"to": [cidr]}), + ("mxc peer unknown", {"to": [{"cidr": cidr, "unknown": 1}]}), + ("mxc peer empty", {"to": [Rule()]}), + ("mxc peer except only", {"to": [{"except": list[str]()}]}), + ("mxc peer cidr number", {"to": [{"cidr": 7}]}), + ("mxc peer second invalid", {"to": [{"cidr": cidr}, {"cidr": "x"}]}), + ("mxc peer except null", {"to": [{"cidr": cidr, "except": None}]}), + ("mxc peer except outside", {"to": [{"cidr": cidr, "except": ["10.0.0.0/8"]}]}), + ("mxc ports object", {"ports": {"port": 80}}), + ("mxc ports empty", {"ports": list[Rule]()}), + ("mxc port string", {"ports": ["tcp"]}), + ("mxc port unknown", {"ports": [{"protocol": "tcp", "unknown": 1}]}), + ("mxc port empty", {"ports": [Rule()]}), + ("mxc port protocol upper", {"ports": [{"protocol": "Udp"}]}), + ("mxc port protocol null", {"ports": [{"protocol": None}]}), + ("mxc port endPort only", {"ports": [{"endPort": 80}]}), + ("mxc port icmp endPort", {"ports": [{"protocol": "icmp", "endPort": 80}]}), + ("mxc port icmp port", {"ports": [{"protocol": "icmp", "port": 8}]}), + ("mxc port null", {"ports": [{"port": None}]}), + ("mxc port float", {"ports": [{"port": 80.0}]}), + ("mxc port large", {"ports": [{"port": 70000}]}), + ("mxc endPort large", {"ports": [{"port": 80, "endPort": 70000}]}), + ( + "mxc endPort below", + {"ports": [{"protocol": "udp", "port": 9, "endPort": 8}]}, + ), + ("mxc second port invalid", {"ports": [{"port": 1}, {"port": 0}]}), + ("mxc to before ports", {"to": [{"cidr": "x"}], "ports": [{"port": 0}]}), + ("mxc every destination", {"ports": [{"protocol": "udp", "port": 53}]}), + ("mxc every port", {"to": [{"cidr": cidr}]}), + ) + for name, item in items: + yield name, [item] + yield "second rule invalid", [{"cidr": cidr}, {"cidr": cidr, "protocol": "TCP"}] + + +CIDR_FORMS = ( + "192.0.2.7", + "192.0.2.7/32", + "192.0.2.7/24", + "10.0.0.5/255.255.255.0", + "10.0.0.5/0.0.0.255", + "10.0.0.5/255.255.255.255", + "10.0.0.5/0.0.0.0", + "10.0.0.5/0", + "10.0.0.5/08", + "10.0.0.5/0008", + "10.0.0.5/" + "0" * 4299 + "8", + "10.0.0.5/" + "0" * 4300 + "8", + "10.0.0.5/255.0.255.0", + "10.0.0.5/255.255.255.000", + "10.0.0.5/+8", + "10.0.0.5/-8", + "10.0.0.5/8.0", + "10.0.0.5/0x8", + "10.0.0.5/", + "10.0.0.5//8", + "10.0.0.5/8/8", + "10.0.0.5/33", + " 10.0.0.5", + "10.0.0.5 ", + "010.0.0.5", + "10.0.0.05", + "256.0.0.0", + "10.0.0", + "10.0.0.0.5", + "10.0.0.\u0665", + "", + "/8", + "::", + "::/0", + "::/000128", + "::/129", + "2001:DB8::/32", + "2001:0db8:0000:0000::/32", + "2001:db8::7/64", + "2001:db8:0:0:1:0:0:1", + "1:0:0:2:0:0:0:3", + "fe80::1", + "fe80::1%eth0", + "fe80::1%eth0/64", + "fe80::%1/10", + "fe80::1%", + "fe80::1%a%b", + "fe80::1%eth0/129", + "::ffff:192.0.2.1", + "::ffff:192.0.2.1/120", + "::ffff:c000:201/120", + "::FFFF:192.0.2.1/96", + "::ffff:0:0/95", + "::192.0.2.1", + "::c000:200/120", + "64:ff9b::192.0.2.1/120", + "1:2:3:4:5:6:1.2.3.4", + "1:2:3:4:5::1.2.3.4", + "1:2:3:4:5:6:7:1.2.3.4", + "1:2:3:4:5:6::1.2.3.4", + "::1.2.3", + "::ffff:1.2.3.04", + "1:2:3:4:5:6:7:8", + "1:2:3:4:5:6:7::", + "::2:3:4:5:6:7:8", + "1:2:3:4:5:6:7:8:9", + "::1:2:3:4:5:6:7:8", + "1::2::3", + ":1::2", + "1::2:", + ":::", + "00000::", + "0000::", + "[::1]", + "::1/0x8", + "2001:db8::g", + "2001:db8::\uff11", +) + +EXCEPTION_FORMS = ( + "10.0.0.0/8", + "10.1.2.3/16", + "10.255.255.255", + "10.0.0.0/255.255.0.0", + "10.0.0.0/7", + "11.0.0.0/8", + "0.0.0.0/0", + "::/96", + "::ffff:10.0.0.0/104", + "fe80::1%eth0", + "10.0.0.0/+16", +) + + +def format_cases() -> Iterator[tuple[str, bytes]]: + """Yield cases of the policy format: every message, networks, and limits.""" + for category in ("allow", "deny"): + for name, items in format_items("192.0.2.0/24"): + yield f"{category}: {name}", rules(category, items) + hosts: list[Rule] = [{"cidr": f"10.0.{i}.1"} for i in range(300)] + policies: tuple[tuple[str, object], ...] = ( + ("allow object", {"allow": {}}), + ("allow null", {"allow": None}), + ("deny string", {"deny": "192.0.2.0/24"}), + ("deny number", {"deny": 1}), + ("unknown root field", {"allow": [], "denied": []}), + ("allow before deny errors", {"allow": [{"cidr": 7}], "deny": [{"cidr": 8}]}), + ("later rule error before lowering", {"allow": [*hosts, {"cidr": 7}]}), + ("allow limit before deny errors", {"allow": hosts[:257], "deny": [7]}), + ) + for name, value in policies: + yield f"policy: {name}", policy(value) + for value in CIDR_FORMS: + yield f"cidr: {value[:40]}", rules("allow", [{"cidr": value}]) + for value in EXCEPTION_FORMS: + peer = {"cidr": "10.0.0.0/8", "except": [value]} + yield f"exception: {value}", rules("deny", [{"to": [peer]}]) + limit = MAX_RULES_PER_ACTION + many: list[Rule] = [ + {"cidr": f"10.{i // 256}.{i % 256}.1"} for i in range(limit + 1) + ] + yield "limit: address-only at the limit", rules("allow", many[:limit]) + yield "limit: address-only over the limit", rules("deny", many) + for protocol in ("tcp", "udp", "icmp"): + wide: list[Rule] = [ + {"cidr": "192.0.2.0/24", "protocol": protocol}, + {"cidr": "198.51.100.0/24", "protocol": protocol}, + ] + yield ( + f"limit: {protocol} for every port over the limit", + rules("allow", [*many[: limit - 1], *wide]), + ) + for count in (limit, limit + 1): + selectors = [{"port": 2 * i + 1, "protocol": "udp"} for i in range(count)] + yield ( + f"limit: {count} separate ports", + rules("allow", [{"to": [{"cidr": "192.0.2.1"}], "ports": selectors}]), + ) + for ipv4, ipv6 in ((128, 128), (129, 128), (128, 129), (200, 56), (0, 257)): + yield ( + f"limit: {ipv4} IPv4 and {ipv6} IPv6 ports", + rules( + "deny", + ( + { + "to": [{"cidr": peer}], + "ports": [{"port": 2 * i + 1} for i in range(n)], + } + for peer, n in (("192.0.2.1", ipv4), ("2001:db8::1", ipv6)) + if n + ), + ), + ) + yield ( + "limit: ranges of any protocol over the limit", + rules( + "allow", + ( + {"cidr": "192.0.2.1", "protocol": "any", "port": p, "endPort": p + 1} + for p in range(1, 3 * 129, 3) + ), + ), + ) + yield ( + "limit: fragmented exclusions", + rules( + "allow", + [{"cidr": "0.0.0.0/0", "except": [f"{2 * i}.0.0.1" for i in range(64)]}], + ), + ) + yield ( + "limit: fragments covered by a wider rule", + rules( + "allow", + [ + {"cidr": "0.0.0.0/0", "except": [f"{i}.0.0.1" for i in range(256)]}, + {"cidr": "0.0.0.0/0"}, + ], + ), + ) + # The size limit applies to the bytes read. + valid = b'{"allow": [{"cidr": "192.0.2.0/24"}]}' + padding = MAX_POLICY_FILE_SIZE - len(valid) + yield "size: exactly the limit", valid + b" " * padding + yield "size: one byte over the limit", valid + b" " * (padding + 1) + yield "size: invalid and over the limit", b"\xff" * (MAX_POLICY_FILE_SIZE + 1) + yield "size: spaces up to the limit", b" " * MAX_POLICY_FILE_SIZE + + +class Corpus: + """A seeded generator of random policies that exercise the lowering.""" + + def __init__(self, seed: int) -> None: + self.random = random.Random(seed) + + def chance(self, probability: float) -> bool: + return self.random.random() < probability + + def network(self) -> Network: + pick = self.random.random() + if pick < 0.45: + # A dense IPv4 neighborhood, so that rules overlap and merge. + address = (10 << 24) + self.random.randrange(1024) + return ipaddress.IPv4Network((address, self.random.randint(22, 32)), False) + if pick < 0.75: + address = (0x20010DB8 << 96) + self.random.randrange(1024) + prefix = self.random.randint(118, 128) + return ipaddress.IPv6Network((address, prefix), False) + if pick < 0.88: + prefix = self.random.choice((0, 1, 2, 4, 8, 12, 16, 24, 31, 32)) + address = self.random.getrandbits(32) + return ipaddress.IPv4Network((address, prefix), False) + prefix = self.random.choice((0, 1, 3, 32, 64, 96, 104, 120, 127, 128)) + address = self.random.getrandbits(128) + if self.chance(0.3): + # An IPv4-mapped address. + address = (0xFFFF << 32) | (address & 0xFFFFFFFF) + return ipaddress.IPv6Network((address, prefix), False) + + def spell(self, network: Network) -> str: + """Write a network as a policy may: with host bits, masks, or other forms.""" + address = int(network.network_address) + if network.num_addresses > 1 and self.chance(0.3): + address += self.random.randrange(network.num_addresses) + prefix = str(network.prefixlen) + if self.chance(0.1): + prefix = "0" * self.random.randint(1, 3) + prefix + if isinstance(network, ipaddress.IPv4Network): + text = str(ipaddress.IPv4Address(address)) + if self.chance(0.1): + prefix = str(network.netmask) + elif self.chance(0.05): + prefix = str(network.hostmask) + if network.prefixlen == 32 and self.chance(0.5): + return text + return f"{text}/{prefix}" + ipv6 = ipaddress.IPv6Address(address) + pick = self.random.random() + if pick < 0.15: + text = ipv6.exploded + elif pick < 0.25: + text = str(ipv6).upper() + elif pick < 0.3: + embedded = ipaddress.IPv4Address(address & 0xFFFFFFFF) + text = f"{ipv6.exploded[:30]}{embedded}" + else: + text = str(ipv6) + if network.prefixlen == 128 and self.chance(0.5): + return text + return f"{text}/{prefix}" + + def exception(self, parent: Network) -> str: + if self.chance(0.01): + return self.random.choice(("x", "10.0.0.0/33", "fe80::1%eth0", "::/0")) + if self.chance(0.01): + return self.spell(self.network()) + maximum = parent.max_prefixlen + prefix = self.random.randint( + parent.prefixlen, min(maximum, parent.prefixlen + 8) + ) + size = 1 << (maximum - prefix) + offset = self.random.randrange(parent.num_addresses // size) * size + address = int(parent.network_address) + offset + if isinstance(parent, ipaddress.IPv4Network): + return self.spell(ipaddress.IPv4Network((address, prefix))) + return self.spell(ipaddress.IPv6Network((address, prefix))) + + def destination(self, rule: Rule) -> None: + network = self.network() + rule["cidr"] = self.spell(network) + if self.chance(0.3): + count = self.random.randint(0, 3) + rule["except"] = [self.exception(network) for _ in range(count)] + + def selector(self, rule: Rule, *, mxc: bool) -> None: + pick = self.random.random() + protocol = ( + "tcp" + if pick < 0.35 + else "udp" + if pick < 0.6 + else "any" + if pick < 0.8 + else "icmp" + if pick < 0.9 + else None + ) + if protocol is None and not mxc: + return + if protocol is not None: + rule["protocol"] = protocol + if self.chance(0.3) or (protocol == "icmp" and self.chance(0.98)): + return + start = self.random.choice( + ( + self.random.randint(1, 12), + self.random.randint(78, 82), + self.random.randint(440, 445), + self.random.randint(65530, 65535), + ) + ) + rule["port"] = start + if self.chance(0.5): + if self.chance(0.1): + rule["endPort"] = self.random.randint(start, 65535) + else: + rule["endPort"] = min(65535, start + self.random.randint(0, 6)) + + def rule(self) -> Rule: + rule: Rule = {} + if self.chance(0.35): + self.destination(rule) + self.selector(rule, mxc=False) + return rule + if self.chance(0.85): + peers: list[Rule] = [] + for _ in range(self.random.randint(1, 4)): + peer: Rule = {} + self.destination(peer) + peers.append(peer) + rule["to"] = peers + if self.chance(0.75): + selectors: list[Rule] = [] + for _ in range(self.random.randint(1, 4)): + selector: Rule = {} + self.selector(selector, mxc=True) + selectors.append(selector) + rule["ports"] = selectors + return rule + + def policy(self) -> Policy: + value: Policy = {} + for category in ("allow", "deny"): + if self.chance(0.85): + count = self.random.choice((0, 1, 2, 3, 4, 6, 8)) + if self.chance(0.03): + count = self.random.randint(20, 60) + value[category] = [self.rule() for _ in range(count)] + if self.chance(0.03): + value[category].extend(self.crowd()) + return value + + def crowd(self) -> list[Rule]: + """Return many rules on scattered ports, which approach the rule limit.""" + crowd: list[Rule] = [] + for _ in range(self.random.randint(80, 300)): + if self.chance(0.7): + address = (10 << 24) + self.random.randrange(1024) + cidr = str( + ipaddress.IPv4Network( + (address, 32 - self.random.randint(0, 2)), False + ) + ) + else: + address = (0x20010DB8 << 96) + self.random.randrange(1024) + cidr = str(ipaddress.IPv6Network((address, 128), False)) + port = 2 * self.random.randrange(200) + 1 + rule: Rule = { + "cidr": cidr, + "protocol": self.random.choice(("tcp", "udp", "any")), + "port": port, + } + if self.chance(0.3): + rule["endPort"] = port + self.random.randint(0, 4) + crowd.append(rule) + return crowd + + def mutate(self, value: Policy) -> object: + """Break one part of a policy, as an invalid file would.""" + candidates = [rule for rules in value.values() for rule in rules] + if not candidates: + return self.random.choice(([value], {**value, "extra": []}, None)) + rule = self.random.choice(candidates) + broken: tuple[tuple[str, object], ...] = ( + ("unknown", 1), + ("cidr", "192.0.2.0/24"), + ("to", []), + ("ports", []), + ("port", True), + ("protocol", "TCP"), + ("endPort", 0), + ("port", 99999), + ("to", "192.0.2.0/24"), + ("ports", [{"protocol": "icmp", "port": 1}]), + ("ports", [{"port": 9, "endPort": 8}]), + ("to", [{"cidr": "192.0.2.0/24", "except": ["10.0.0.0/8"]}]), + ) + index = self.random.randrange(len(broken) + 1) + if index == len(broken): + rule.pop("cidr", None) + else: + field, replacement = broken[index] + rule[field] = replacement + return value + + def text(self, value: object) -> bytes: + """Write a policy as JSON with random formatting, and sometimes break it.""" + indent = self.random.choice((None, None, None, 0, 1, "\t")) + separators = self.random.choice((None, (",", ":"), (", ", ": "))) + text = json.dumps( + value, + indent=indent, + separators=separators, + ensure_ascii=self.chance(0.8), + ) + if self.chance(0.04): + replacement = self.random.choice( + ("NaN", "Infinity", "1e400", "80.0", "9" * 65, "9" * 64, "true") + ) + text = re.sub( + r'("(?:port|endPort)"\s*:\s*)\d+', rf"\g<1>{replacement}", text, count=1 + ) + if self.chance(0.03): + text = text.replace("{", '{"allow": [], ', 1) + data = text.encode() + if self.chance(0.03): + data = data[: self.random.randrange(len(data) + 1)] + return data + + def cases(self, count: int) -> Iterator[tuple[str, bytes]]: + for index in range(count): + value = self.policy() + item = self.mutate(value) if self.chance(0.12) else value + yield f"random #{index}", self.text(item) + + +PATHS = ( + "", + ".", + "./", + "a", + "a/", + "a//b", + "a/./b/.", + "./a", + "../a", + "a/../b", + "/", + "//", + "///", + "////a", + "//a/b", + "///a/b", + "a\\b", + "c:", + "c:a", + "c:/a", + "c:\\a\\", + "C:/", + "/a", + "\\a", + "\\\\server", + "\\\\server\\share", + "\\\\server\\share\\x", + "//server/share/x/", + "\\\\?\\c:\\x", + "\\\\?\\UNC\\server\\share\\x", + "\\\\?\\unc\\server\\share", + "\\\\.\\device\\x", + "\\\\\\share", + "\\\\?.\\x\\y", + "./c:foo", + "c:foo/./bar", + "1:x", + "::x", + "a:b:c", + "\\\\server\\share\\", + "//?/UNC/a", + "//?/UNC/a/b", + "//?/UNC/a/b/c", + "\\\\?\\", + "\\\\.\\", + "\\\\?\\x", + "a/.", + ".\\.\\", + "x\\.\\y", + "\\\\a\\b\\c\\d\\e", + " a ", + "a /b", + "\u00e9:x", + "dir/\u00e9.json", + "policy.json", +) + + +def egress_policy_fixture(random_cases: int) -> dict[str, list[Entry]]: + """Return the fixture: compiled policies, unreadable files, and paths.""" + if str(ipaddress.IPv6Address("::ffff:192.0.2.1")) != "::ffff:192.0.2.1": + raise SystemExit( + "generating the fixture needs Python 3.13 or later, whose ipaddress " + "module prints IPv4-mapped IPv6 addresses in dotted form" + ) + named = [ + *((f"vector: {name}", data) for name, data in test_vectors()), + *json_cases(), + *format_cases(), + *Corpus(SEED).cases(random_cases), + ] + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) + path = directory / "policy.json" + cases: list[Entry] = [] + for name, data in named: + path.write_bytes(data) + cases.append( + {"name": name, "input": segments(data), "expected": outcome(path)} + ) + files: list[Entry] = [ + {"name": "missing", "expected": outcome(directory / "missing.json")}, + {"name": "directory", "expected": outcome(directory)}, + ] + paths: list[Entry] = [ + { + "path": value, + "posix": str(PurePosixPath(value)), + "windows": str(PureWindowsPath(value)), + } + for value in PATHS + ] + return {"cases": cases, "files": files, "paths": paths} + + +def egress_policy_text(fixture: dict[str, list[Entry]]) -> str: + """Write the fixture as ASCII JSON with one entry per line.""" + lines = [ + "{", + ' "comment": "Generated by scripts/nvx_rs_fixtures.py; do not edit.",', + ] + sections = list(fixture.items()) + for position, (name, entries) in enumerate(sections): + lines.append(f' "{name}": [') + for index, entry in enumerate(entries): + comma = "," if index + 1 < len(entries) else "" + lines.append(f" {json.dumps(entry, separators=(',', ':'))}{comma}") + lines.append(" ]," if position + 1 < len(sections) else " ]") + lines.append("}") + return "\n".join(lines) + "\n" + + +# The nvx-rs CLI. + +CLI_FIXTURE = FIXTURE_DIR / "cli.json" +CLI_PLATFORM = "windows" if os.name == "nt" else "posix" +CLI_PLATFORMS = ("posix", "windows") +NVX_RS_DIR = REPO_ROOT / "nvx-rs" +EXE = ".exe" if os.name == "nt" else "" +# The top-level usage of nvx-rs, which replaces that of nvx.py in the usage +# errors that the top-level parser reports, such as unrecognized arguments. +NVX_RS_USAGE = "usage: nvx-rs [-h] [--root PATH] {artifacts,egress,sandbox} ..." +CLI_STEP_TIMEOUT = 120 +LAYER_UUIDS = { + "distro": "11111111-1111-1111-1111-111111111111", + "runtime": "22222222-2222-2222-2222-222222222222", + "custom": "33333333-3333-3333-3333-333333333333", +} +# The tree of every scenario, below its root: files with their text, and +# directories with None. The OpenVMM executable of the root is a copy of +# nvx-fake-openvmm. +CLI_TREE: tuple[tuple[str, str | None], ...] = ( + ("build/vmlinux", "kernel\n"), + ("build/initramfs.cpio.gz", "initramfs\n"), + ("distro.erofs", "distro\n"), + ("runtime.erofs", "runtime\n"), + ("custom.erofs", "custom\n"), + ("scratch.ext4", "scratch\n"), + ("share/file.txt", "file\n"), + ("share/sub/data.txt", "data\n"), + ("share/hidden/shown/data.txt", "data\n"), + ("share/writable/data.txt", "data\n"), + ("share2/data.txt", "data\n"), + ("single.txt", "single\n"), + ("reports", None), +) +CLI_OPENVMM = "openvmm/target/release/openvmm{exe}" +# The files whose permissions the CLI sets, rather than the umask or OpenVMM. +CLI_MODE_NAMES = frozenset( + ( + "state", + sandbox_lifecycle.CONFIG_NAME, + sandbox_lifecycle.RUNTIME_NAME, + sandbox_lifecycle.CAPABILITY_NAME, + sandbox_lifecycle.LOCK_NAME, + "exec.json", + ) +) +VOLATILE = ( + (re.compile(r'("(?:pid|start_time)": )\d+'), r"\1{int}"), + (re.compile(r"openvmm-microvm-[0-9a-f]{32}"), "openvmm-microvm-{hex32}"), + (re.compile(r'("(?:operation_id|instance_id)": ")[0-9a-f]{32}"'), r'\1{hex32}"'), +) +# Runs `nvx.py sandbox` with the artifacts of NVX_FIXTURE_ROOT and the OpenVMM +# executable NVX_FIXTURE_OPENVMM. +PYTHON_SANDBOX = """\ +import os +import sys +from pathlib import Path + +sys.path.insert(0, os.environ["NVX_FIXTURE_SCRIPTS"]) +import nvx +from nvx_tools import sandbox_lifecycle + +root = Path(os.environ["NVX_FIXTURE_ROOT"]) +openvmm = Path(os.environ["NVX_FIXTURE_OPENVMM"]) +for module in (nvx, sandbox_lifecycle): + module.artifact_path = lambda name: root / "build" / name + module.openvmm_binary_path = lambda: openvmm +sys.argv[0] = "nvx.py" +raise SystemExit(nvx.main(sys.argv[1:])) +""" + +Step = dict[str, object] +Scenario = dict[str, object] + + +def _r(path: str = "") -> str: + """Return the native path of `path` below the scenario root.""" + return "{root}" + (os.sep + path.replace("/", os.sep) if path else "") + + +def _layer(role: str = "distro", path: str | None = None) -> list[str]: + image = _r(path if path is not None else f"{role}.erofs") + return ["--layer", f"{role},{image},{LAYER_UUIDS[role]}"] + + +BASE = [*_layer(), "--scratch", _r("scratch.ext4")] +STATE = ["--state-dir", _r("state")] +# What a lifecycle step shows of the state directory. +STATE_FILES = ( + "state", + "state/config.json", + "state/runtime.json", + "state/control.capability", + "state/outcome.json", + "state/openvmm.log", +) +START_FILES = (*STATE_FILES, "build/fake-openvmm-state.json") + + +def _step( + *argv: str, + write: dict[str, object] | None = None, + remove: Sequence[str] = (), + check: Sequence[str] = (), + env: dict[str, str] | None = None, +) -> Step: + """Return a step that runs `nvx-rs ARGV` after writing and removing files. + + A written file takes text, or a dict with `hex` bytes, or with `text`, if + any, then `fill` repeated to `size` bytes. After the step, the files of + `check` are recorded.""" + step: Step = {"argv": list(argv)} + if write: + step["write"] = write + if remove: + step["remove"] = list(remove) + if check: + step["check"] = list(check) + if env: + step["env"] = env + return step + + +def _sandbox( + *argv: str, + write: dict[str, object] | None = None, + remove: Sequence[str] = (), + check: Sequence[str] = (), +) -> Step: + return _step("sandbox", *argv, write=write, remove=remove, check=check) + + +def _scenario( + name: str, + *steps: Step, + platforms: Sequence[str] = CLI_PLATFORMS, + interop: bool = False, +) -> Scenario | None: + """Return a scenario of `steps` in a fresh root, if it runs on this platform. + + An interoperability scenario also runs with nvx.py and nvx-rs taking turns, + as the checks of this script do, so that a state directory that either + leaves is valid for the other.""" + if CLI_PLATFORM not in platforms: + return None + scenario: Scenario = {"name": name, "steps": list(steps)} + if interop: + scenario["interop"] = True + return scenario + + +def _distro_manifest(packages: list[str] | None, digest: str | None = None) -> str: + """Return a manifest of the distro layer of the tree, `distro.erofs`.""" + value: dict[str, object] = { + "format": 1, + "artifact": "distro.erofs", + "artifact_sha256": digest or hashlib.sha256(b"distro\n").hexdigest(), + } + if packages is not None: + value["packages"] = [{"name": name, "version": "1"} for name in packages] + return json.dumps(value) + + +def _validation_scenarios() -> list[Scenario | None]: + """Return the option checks of `command_sandbox`, each in its own root.""" + environment = {"env.json": '["A=1"]'} + cases: list[tuple[str, list[str], dict[str, object] | None]] = [ + ("dry run outside run", ["provision", "--dry-run"], None), + ( + "environment and environment file", + ["exec", "--environment", "A=1", "--environment-file", _r("env.json")], + environment, + ), + ("cwd outside exec", ["run", "--cwd", "/"], None), + ("environment outside exec", ["start", "--environment", "A=1"], None), + ( + "environment file outside exec", + ["provision", "--environment-file", _r("env.json")], + environment, + ), + ( + "inherited environment outside exec", + ["stop", "--inherit-default-environment"], + None, + ), + ( + "environment file too large", + ["exec", "--environment-file", _r("env.json")], + {"env.json": {"fill": " ", "size": nvx.MAX_ENVIRONMENT_FILE_BYTES + 1}}, + ), + ( + "environment file at the limit", + ["exec", "--environment-file", _r("env.json")], + { + "env.json": { + "text": '["A=1"]', + "fill": " ", + "size": nvx.MAX_ENVIRONMENT_FILE_BYTES, + } + }, + ), + ( + "environment file missing", + ["exec", "--environment-file", _r("missing.json")], + None, + ), + ( + "environment file directory", + ["exec", "--environment-file", _r("share")], + None, + ), + ( + "environment file not UTF-8", + ["exec", "--environment-file", _r("env.json")], + {"env.json": {"hex": "5b22ff225d"}}, + ), + ( + "environment file not JSON", + ["exec", "--environment-file", _r("env.json")], + {"env.json": '["A=1"'}, + ), + ( + "environment file object", + ["exec", "--environment-file", _r("env.json")], + {"env.json": '{"A": "1"}'}, + ), + ( + "environment file non-string", + ["exec", "--environment-file", _r("env.json")], + {"env.json": '["A=1", 2]'}, + ), + ( + "environment file entry without equals", + ["exec", "--environment-file", _r("env.json")], + {"env.json": '["A"]'}, + ), + ( + "environment file entry with NUL", + ["exec", "--environment-file", _r("env.json")], + {"env.json": '["A=\\u0000"]'}, + ), + ( + "environment file duplicate entry", + ["exec", "--environment-file", _r("env.json")], + {"env.json": '["A=1", "A=2"]'}, + ), + ("environment entry without name", ["exec", "--environment", "=x"], None), + ("environment entry with space", ["exec", "--environment", "A B=x"], None), + ("network outside run", ["start", "--net", "10.0.0.2/24"], None), + ( + "egress rule outside run", + ["exec", "--network-egress-allow", "10.0.0.0/8"], + None, + ), + ("forward outside run", ["stop", "--host-loopback-forward", "80"], None), + ( + "policy file outside run", + ["deprovision", "--network-egress-policy-file", _r("policy.json")], + None, + ), + ( + "systemd entrypoint of run", + ["run", *BASE, "--entrypoint", "/usr/lib/systemd/systemd"], + None, + ), + ( + "systemd entrypoint of exec", + ["exec", "--entrypoint", "/lib/systemd/systemd"], + None, + ), + ( + "outcome report outside run", + ["provision", "--outcome-report", _r("reports/exec.json")], + None, + ), + ("deny without mount", ["run", "--mount-deny", _r("share/hidden")], None), + ("allow without mount", ["run", "--mount-allow", _r("share/hidden")], None), + ("write without mount", ["run", "--mount-write", _r("share/writable")], None), + ("owner without mount", ["run", "--mount-owner", "vmm"], None), + ("mount outside run", ["start", "--mount", "/data," + _r("share")], None), + ("net without profile", ["run", *BASE, "--net", "10.0.0.2/24"], None), + ("profile without net", ["run", *BASE, "--network-profile", "portable"], None), + ("run without layers", ["run"], None), + ("provision without scratch", ["provision", *_layer()], None), + ("run without layer", ["run", "--scratch", _r("scratch.ext4")], None), + ( + "policy file with rules", + [ + "run", + *BASE, + "--network-egress", + "allow", + "--network-egress-policy-file", + _r("policy.json"), + "--network-egress-deny", + "10.0.0.0/8", + ], + None, + ), + ( + "policy file without egress", + ["run", *BASE, "--network-egress-policy-file", _r("policy.json")], + None, + ), + ( + "policy file missing", + [ + "run", + *BASE, + "--network-egress", + "deny", + "--network-egress-policy-file", + _r("missing.json"), + ], + None, + ), + ( + "policy file invalid", + [ + "run", + *BASE, + "--network-egress", + "deny", + "--network-egress-policy-file", + _r("policy.json"), + ], + {"policy.json": '{"allow": [{"cidr": "10.0.0.0/8", "port": 0}]}'}, + ), + ("provision without state directory", ["provision", *BASE], None), + ("start without state directory", ["start"], None), + ("exec without state directory", ["exec"], None), + ("stop without state directory", ["stop"], None), + ("deprovision without state directory", ["deprovision"], None), + ("run with state directory", ["run", *BASE, *STATE], None), + ( + "invalid layer UUID", + [ + "run", + "--layer", + f"distro,{_r('distro.erofs')},nope", + "--scratch", + _r("scratch.ext4"), + ], + None, + ), + ( + "invalid layer role", + ["run", "--layer", f"base,{_r('distro.erofs')},{LAYER_UUIDS['distro']}"], + None, + ), + ( + "missing layer", + [ + "run", + *_layer("distro", "missing.erofs"), + "--scratch", + _r("scratch.ext4"), + ], + None, + ), + ( + "duplicate layer", + ["run", *_layer(), *_layer(), "--scratch", _r("scratch.ext4")], + None, + ), + ("missing scratch", ["run", *_layer(), "--scratch", _r("missing.ext4")], None), + ("relative entrypoint", ["run", *BASE, "--entrypoint", "bin/sh"], None), + ("invalid hostname", ["run", *BASE, "--hostname", "bad host"], None), + ("argument with space", ["run", *BASE, "--arg", "a b"], None), + ("root workload", ["run", *BASE, "--workload-user", "0:0"], None), + ("invalid workload user", ["run", *BASE, "--workload-user", "x"], None), + ("invalid memory limit", ["run", *BASE, "--memory-max", "x"], None), + ("invalid process limit", ["run", *BASE, "--pids-max", "0"], None), + ( + "relative mount target", + ["run", *BASE, "--mount", "data," + _r("share")], + None, + ), + ( + "missing mount host", + ["run", *BASE, "--mount", "/data," + _r("missing")], + None, + ), + ( + "invalid mount mode", + ["run", *BASE, "--mount", "/data," + _r("share") + ",rx"], + None, + ), + ( + "deny outside share", + [ + "run", + *BASE, + "--mount", + "/data," + _r("share"), + "--mount-deny", + _r("share2"), + ], + None, + ), + ( + "allow outside denied path", + [ + "run", + *BASE, + "--mount", + "/data," + _r("share"), + "--mount-allow", + _r("share/hidden/shown"), + ], + None, + ), + ( + "write in read-only share", + [ + "run", + *BASE, + "--mount", + "/data," + _r("share") + ",ro", + "--mount-write", + _r("share/writable"), + ], + None, + ), + ( + "reserved command-line token", + ["run", *BASE, "--cmdline", "nvx_sandbox=0"], + None, + ), + ( + "systemd distro image", + ["run", *BASE], + {"distro.erofs.manifest.json": _distro_manifest(["busybox", "systemd"])}, + ), + ( + "mismatched distro manifest", + ["run", *BASE], + {"distro.erofs.manifest.json": _distro_manifest([], "0" * 64)}, + ), + ( + "invalid distro manifest", + ["run", *BASE], + {"distro.erofs.manifest.json": "{"}, + ), + ( + "distro manifest without packages", + ["run", *BASE], + {"distro.erofs.manifest.json": _distro_manifest(None)}, + ), + ] + scenarios: list[Scenario | None] = [] + for name, argv, write in cases: + scenarios.append(_scenario(f"validation: {name}", _sandbox(*argv, write=write))) + scenarios.append( + _scenario( + "validation: caller-owned share on Windows", + _sandbox( + "run", + *BASE, + "--mount", + "/data," + _r("share"), + "--mount-owner", + "caller", + ), + platforms=("windows",), + ) + ) + return scenarios + + +def _usage_scenarios() -> list[Scenario | None]: + """Return the usage errors of the sandbox parser, and its help.""" + cases: list[tuple[str, list[str]]] = [ + ("help", ["-h"]), + ("help after operation", ["exec", "--help"]), + ("timeout not a number", ["--timeout", "x"]), + ("zero timeout", ["--timeout", "0"]), + ("negative timeout", ["--timeout", "-1"]), + ("infinite timeout", ["--timeout", "inf"]), + ("timeout not finite", ["--timeout=1e400"]), + ("timeout not a number either", ["--timeout", "nan"]), + ("exec timeout not an integer", ["--exec-timeout-ms", "1.5"]), + ("memory not an integer", ["--memory-mib", "x"]), + ("invalid hypervisor", ["--hypervisor", "hyperv"]), + ("invalid operation", ["restart"]), + ("second operation", ["run", "start"]), + ("invalid mount owner", ["--mount-owner", "root"]), + ("invalid network profile", ["--network-profile", "open"]), + ("invalid egress default", ["--network-egress", "block"]), + ("invalid host loopback", ["--host-loopback", "yes"]), + ("missing value", ["--layer"]), + ("option as value", ["--layer", "--scratch"]), + ("dash value", ["--arg", "-x"]), + ("flag with value", ["--dry-run=1"]), + ("unrecognized option", ["--bogus"]), + ("unrecognized short option", ["-x"]), + ("unrecognized after separator", ["run", "--", "extra"]), + ] + return [_scenario(f"usage: {name}", _sandbox(*argv)) for name, argv in cases] + + +def _dry_run_scenarios() -> list[Scenario | None]: + """Return one-shot commands, which --dry-run prints without running.""" + policy: dict[str, object] = { + "policy.json": json.dumps( + { + "allow": [ + {"cidr": "192.0.2.0/24", "except": ["192.0.2.128/25"]}, + {"cidr": "2001:db8::/32", "protocol": "tcp", "port": 443}, + ], + "deny": [{"cidr": "198.51.100.0/24", "protocol": "udp"}], + } + ) + } + cases: list[tuple[str, list[str], dict[str, object] | None]] = [ + ("minimal", [*BASE], None), + ( + "every layer and option", + [ + *_layer("custom"), + *_layer("runtime"), + *_layer("distro"), + "--scratch", + _r("scratch.ext4"), + "--entrypoint", + "/bin/server", + "--arg=--port", + "--arg", + "8080", + "--hostname", + "worker", + "--workload-user", + "1000:1000", + "--memory-max", + "268435456", + "--pids-max", + "64", + "--memory-mib", + "512", + "--hypervisor", + "mshv", + "--cmdline", + "quiet loglevel=3", + "--outcome-report", + _r("reports/openvmm-run.json"), + ], + None, + ), + ("explicit hypervisor", [*BASE, "--hypervisor", "kvm"], None), + ("one share", [*BASE, "--mount", "/data," + _r("share")], None), + ("writable share", [*BASE, "--mount", "/data," + _r("share") + ",rw"], None), + ( + "file share", + [*BASE, "--mount", "/etc/single.txt," + _r("single.txt") + ",ro"], + None, + ), + ( + "several shares with policies", + [ + *BASE, + "--mount", + "/data," + _r("share") + ",rw", + "--mount-deny", + _r("share/hidden"), + "--mount-allow", + _r("share/hidden/shown"), + "--mount-write", + _r("share/writable"), + "--mount", + "/other," + _r("share2"), + ], + None, + ), + ( + "network", + [ + *BASE, + "--net", + "10.0.0.2/24", + "--network-profile", + "portable", + "--network-egress", + "deny", + "--network-ingress", + "allow", + "--network-egress-allow", + "10.0.0.0/8:tcp:443", + "--network-egress-deny", + "10.1.0.0/16", + "--host-loopback", + "allow", + "--network-proxy", + "10.0.0.1:3128", + "--host-loopback-forward", + "8080", + ], + None, + ), + ( + "egress policy file", + [ + *BASE, + "--network-egress", + "deny", + "--network-egress-policy-file", + _r("policy.json"), + ], + policy, + ), + ("quoted arguments", [*BASE, "--cmdline", 'a="b c" d'], None), + ] + scenarios = [ + _scenario(f"dry run: {name}", _sandbox("run", *argv, "--dry-run", write=write)) + for name, argv, write in cases + ] + scenarios.append( + _scenario( + "dry run: caller-owned share", + _sandbox( + "run", + *BASE, + "--mount", + "/data," + _r("share"), + "--mount-owner", + "caller", + "--dry-run", + ), + platforms=("posix",), + ) + ) + return scenarios + + +def _run_scenarios() -> list[Scenario | None]: + """Return one-shot runs of the fake OpenVMM.""" + echo = ["--entrypoint", "/bin/echo", "--arg", "hello", "--arg", "world"] + return [ + _scenario("run: echo", _sandbox("run", *BASE, *echo)), + _scenario("run: default operation", _sandbox(*BASE, *echo)), + _scenario("run: failure", _sandbox("run", *BASE, "--entrypoint", "/bin/false")), + _scenario( + "run: missing program", + _sandbox("run", *BASE, "--entrypoint", "/bin/missing"), + ), + _scenario( + "run: exit status", + _sandbox( + "run", + *BASE, + "--entrypoint", + "/bin/true", + "--cmdline", + "fake_exit_status=7", + ), + ), + _scenario( + "run: outcome report", + _sandbox( + "run", + *BASE, + *echo, + "--outcome-report", + _r("reports/openvmm-run.json"), + check=("reports/openvmm-run.json", "build/fake-openvmm-reports.json"), + ), + ), + _scenario( + "run: network outcome report", + _sandbox( + "run", + *BASE, + "--entrypoint", + "/bin/true", + "--net", + "10.0.0.2/24", + "--network-profile", + "portable", + "--network-egress", + "deny", + "--network-egress-allow", + "10.0.0.0/8:tcp:443", + "--outcome-report", + _r("reports/openvmm-run.json"), + check=("reports/openvmm-run.json",), + ), + ), + _scenario( + "run: OpenVMM refuses its options", + _sandbox("run", *BASE, "--host-loopback-forward", "8080"), + ), + _scenario( + "run: missing OpenVMM", + _sandbox("run", *BASE, remove=(CLI_OPENVMM,)), + ), + _scenario( + "run: missing kernel", + _sandbox("run", *BASE, remove=("build/vmlinux",)), + ), + _scenario( + "run: missing initramfs", + _sandbox("run", *BASE, remove=("build/initramfs.cpio.gz",)), + ), + ] + + +def _exec( + *argv: str, + write: dict[str, object] | None = None, + check: Sequence[str] = (), +) -> Step: + return _sandbox("exec", *STATE, *argv, write=write, check=check) + + +def _lifecycle( + name: str, + provision: Sequence[str], + *execs: Step, + write: dict[str, object] | None = None, + platforms: Sequence[str] = CLI_PLATFORMS, +) -> Scenario | None: + """Return a sandbox's lifecycle: provision, start, `execs`, stop, start, stop, + and deprovision.""" + return _scenario( + f"lifecycle: {name}", + _sandbox( + "provision", *STATE, *BASE, *provision, write=write, check=STATE_FILES + ), + _sandbox("start", *STATE, check=START_FILES), + *execs, + _sandbox("stop", *STATE, check=STATE_FILES), + _sandbox("start", *STATE, check=STATE_FILES), + _sandbox("stop", *STATE, check=STATE_FILES), + _sandbox("deprovision", *STATE, check=("state",)), + platforms=platforms, + interop=True, + ) + + +def _lifecycle_scenarios() -> list[Scenario | None]: + """Return managed sandboxes from provisioning to deprovisioning.""" + report = _r("reports/exec.json") + return [ + _lifecycle( + "minimal", + (), + _exec("--entrypoint", "/bin/echo", "--arg", "hello", "--arg", "world"), + _exec("--entrypoint", "/bin/pwd", "--cwd", "/tmp"), + _exec( + "--entrypoint", + "/bin/sh", + "--arg=-c", + "--arg", + "echo out; echoerr err; exit 3", + "--outcome-report", + report, + check=("reports/exec.json",), + ), + _exec( + "--entrypoint", + "/usr/bin/env", + "--environment", + "A=1", + "--environment", + "B=two", + ), + _exec( + "--entrypoint", + "/usr/bin/printenv", + "--arg", + "PATH", + "--inherit-default-environment", + "--environment", + "A=1", + ), + _exec( + "--entrypoint", + "/usr/bin/env", + "--environment-file", + _r("env.json"), + write={"env.json": '["X=1", "Y=two words"]'}, + ), + _exec( + "--entrypoint", + "/usr/bin/env", + "--environment-file", + _r("empty.json"), + write={"empty.json": "[]"}, + ), + _exec( + "--entrypoint", + "/bin/sh", + "--arg=-c", + "--arg", + "sleep 5000", + "--exec-timeout-ms", + "50", + "--outcome-report", + _r("reports/exec-timeout.json"), + check=("reports/exec-timeout.json",), + ), + _exec("--entrypoint", "/bin/sh", "--arg=-c", "--arg", "signal 9"), + _exec("--entrypoint", "/bin/missing"), + _exec("--entrypoint", "/bin/pwd", "--cwd", "/missing"), + _exec("--entrypoint", "/bin/pwd", "--cwd", "relative"), + _exec( + "--entrypoint", + "/bin/echo", + "--outcome-report", + report, + ), + _exec( + "--entrypoint", "/bin/echo", "--outcome-report", _r("missing/exec.json") + ), + ), + _lifecycle( + "one share", + ("--mount", "/data," + _r("share")), + _exec("--entrypoint", "/bin/pwd", "--cwd", "/data"), + ), + _lifecycle( + "caller-owned share", + ("--mount", "/data," + _r("share") + ",rw", "--mount-owner", "caller"), + platforms=("posix",), + ), + _lifecycle( + "several shares", + ( + "--mount", + "/data," + _r("share"), + "--mount", + "/other," + _r("share2") + ",rw", + ), + _exec("--entrypoint", "/bin/pwd", "--cwd", "/other"), + ), + _lifecycle( + "share policies", + ( + "--mount", + "/data," + _r("share") + ",rw", + "--mount-deny", + _r("share/hidden"), + "--mount-allow", + _r("share/hidden/shown"), + "--mount-write", + _r("share/writable"), + ), + ), + _lifecycle( + "file share", + ("--mount", "/etc/single.txt," + _r("single.txt") + ",ro"), + ), + _lifecycle( + "network and launch options", + ( + *_layer("runtime"), + "--entrypoint", + "/bin/server", + "--arg=--serve", + "--hostname", + "worker", + "--workload-user", + "1000:1000", + "--memory-max", + "268435456", + "--pids-max", + "64", + "--memory-mib", + "384", + "--hypervisor", + "auto", + "--cmdline", + "quiet", + "--net", + "10.0.0.2/24", + "--network-profile", + "portable", + "--network-egress", + "deny", + "--network-ingress", + "deny", + "--network-egress-policy-file", + _r("policy.json"), + "--host-loopback", + "allow", + "--network-proxy", + "10.0.0.1:3128", + "--host-loopback-forward", + "8080", + "--host-loopback-forward", + "9090", + ), + write={"policy.json": LIFECYCLE_POLICY}, + ), + ] + + +LIFECYCLE_POLICY = json.dumps( + { + "allow": [{"cidr": "192.0.2.0/24", "protocol": "tcp", "port": 443}], + "deny": [{"cidr": "192.0.2.128/25"}], + } +) + + +def _state_scenarios() -> list[Scenario | None]: + """Return the refusals and failures of the lifecycle operations.""" + provision = _sandbox("provision", *STATE, *BASE) + return [ + _scenario( + "state: operations without a sandbox", + _sandbox("start", *STATE), + _sandbox("exec", *STATE), + _sandbox("stop", *STATE), + _sandbox("deprovision", *STATE), + ), + _scenario( + "state: refusals of a running sandbox", + provision, + _sandbox("provision", *STATE, *BASE), + _sandbox("start", *STATE), + _sandbox("start", *STATE), + _sandbox("deprovision", *STATE), + _sandbox("stop", *STATE), + _sandbox("stop", *STATE), + _sandbox("exec", *STATE), + _sandbox("deprovision", *STATE, check=("state",)), + interop=True, + ), + _scenario( + "state: relative and nested state directory", + _sandbox( + "provision", + "--state-dir", + "nested/state", + *BASE, + check=("nested/state", "nested/state/config.json"), + ), + _sandbox( + "deprovision", "--state-dir", "nested/state", check=("nested/state",) + ), + ), + _scenario( + "state: failed start", + _sandbox("provision", *STATE, *BASE, "--cmdline", "fake_exit_on_start=5"), + # Like nvx.py, nvx-rs waits until --timeout for the control endpoint + # of an OpenVMM that exits at once. + _sandbox("start", *STATE, "--timeout", "0.5", check=STATE_FILES), + _sandbox("start", *STATE, "--timeout", "0.5", check=STATE_FILES), + _sandbox("deprovision", *STATE, check=("state",)), + interop=True, + ), + _scenario( + "state: start without OpenVMM", + provision, + _sandbox("start", *STATE, remove=(CLI_OPENVMM,), check=STATE_FILES), + _sandbox("deprovision", *STATE), + ), + _scenario( + "state: malformed configuration", + provision, + _sandbox("start", *STATE, write={"state/config.json": '{"format": 7}'}), + _sandbox("start", *STATE, write={"state/config.json": "[]"}), + _sandbox("start", *STATE, write={"state/config.json": '{"format": 1}'}), + _sandbox("deprovision", *STATE, check=("state",)), + ), + _scenario( + "state: not a directory", + _sandbox("start", "--state-dir", _r("single.txt")), + _sandbox("provision", "--state-dir", _r("single.txt"), *BASE), + _sandbox("deprovision", "--state-dir", _r("single.txt")), + ), + _scenario( + "state: foreign file", + provision, + _sandbox( + "deprovision", + *STATE, + write={"state/foreign.txt": "x"}, + check=("state",), + ), + ), + ] + + +def _artifact_expectation(root: str, openvmm: str | None) -> str: + """Return what `nvx-rs artifacts` prints for the checkout at `root`, from the + paths of scripts/nvx_tools/common.py and guests.py.""" + + def path(original: Path) -> str: + return str(Path(root) / original.relative_to(REPO_ROOT)) + + constants = { + "alpine": build_constants.AlpineBuildConstants, + "ubuntu": build_constants.UbuntuBuildConstants, + "azurelinux": build_constants.AzureLinuxBuildConstants, + } + entries: list[dict[str, object]] = [] + for name, descriptor in guests.GUEST_DESCRIPTORS.items(): + distro = cast(str | None, getattr(constants[name], "DISTRO_NAME", None)) + manifest = cast( + str | None, getattr(constants[name], "DISTRO_MANIFEST_NAME", None) + ) + entries.append( + { + "name": name, + "distribution": descriptor.distribution, + "initramfs": path(common.artifact_path(descriptor.initramfs_name)), + "package_manifest": path( + common.artifact_path(descriptor.package_manifest_name) + ), + "distro_layer": None + if distro is None + else path(common.artifact_path(distro)), + "distro_manifest": None + if manifest is None + else path(common.artifact_path(manifest)), + } + ) + value = { + "layout": "repository", + "root": root, + "openvmm": openvmm + if openvmm is not None + else path(common.openvmm_binary_path()), + "kernel": path( + common.artifact_path(build_constants.KernelBuildConstants.BINARY_NAME) + ), + "guests": entries, + } + return json.dumps(value, indent=2, sort_keys=True) + + +def _command_scenarios() -> list[Scenario | None]: + """Return the other commands: artifacts and egress compile.""" + policies: list[tuple[str, str]] = [ + ("empty", "{}"), + ("allow", '{"allow": [{"cidr": "192.0.2.0/24"}]}'), + ( + "both", + json.dumps( + { + "allow": [ + {"cidr": "192.0.2.0/24", "except": ["192.0.2.128/25"]}, + { + "to": [{"cidr": "2001:db8::/32"}], + "ports": [{"protocol": "tcp", "port": 80, "endPort": 81}], + }, + ], + "deny": [{"cidr": "198.51.100.0/24", "protocol": "icmp"}], + } + ), + ), + ("invalid JSON", "{"), + ("invalid rule", '{"allow": [{"cidr": "192.0.2.0/24", "port": 70000}]}'), + ("unknown field", '{"allow": [], "other": []}'), + ] + scenarios: list[Scenario | None] = [ + _scenario( + f"egress compile: {name}", + _step("egress", "compile", _r("policy.json"), write={"policy.json": text}), + ) + for name, text in policies + ] + scenarios += [ + _scenario( + "egress compile: missing file", + _step("egress", "compile", _r("missing.json")), + ), + _scenario("egress compile: directory", _step("egress", "compile", _r("share"))), + _scenario("artifacts: checkout", _step("artifacts")), + _scenario( + "artifacts: OpenVMM override", + _step("artifacts", env={"NVX_OPENVMM": _r("alt/openvmm{exe}")}), + ), + ] + return scenarios + + +def cli_scenarios() -> list[Scenario]: + """Return the scenarios of this platform.""" + scenarios = [ + *_command_scenarios(), + *_usage_scenarios(), + *_validation_scenarios(), + *_dry_run_scenarios(), + *_run_scenarios(), + *_lifecycle_scenarios(), + *_state_scenarios(), + ] + return [scenario for scenario in scenarios if scenario is not None] + + +def _expand(text: str, root: Path) -> str: + return text.replace("{root}", str(root)).replace("{exe}", EXE) + + +def _below(root: Path, relative: str) -> Path: + return root.joinpath(*_expand(relative, root).split("/")) + + +def _make_tree(root: Path, fake: Path) -> None: + for relative, text in CLI_TREE: + path = _below(root, relative) + if text is None: + path.mkdir(parents=True, exist_ok=True) + else: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(text.encode()) + openvmm = _below(root, CLI_OPENVMM) + openvmm.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(fake, openvmm) + + +def _write_file(path: Path, content: object) -> None: + if isinstance(content, str): + data = content.encode() + else: + spec = cast(dict[str, object], content) + if "hex" in spec: + data = bytes.fromhex(cast(str, spec["hex"])) + else: + data = cast(str, spec.get("text", "")).encode() + data += cast(str, spec["fill"]).encode() * ( + cast(int, spec["size"]) - len(data) + ) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + + +def _child_environment() -> dict[str, str]: + """Return the environment of a recorded command: no terminal size, color, + or OpenVMM override, and UTF-8 standard streams.""" + variables = ("COLUMNS", "LINES", "FORCE_COLOR", "PYTHON_COLORS", "NVX_OPENVMM") + environment = { + name: value for name, value in os.environ.items() if name not in variables + } + environment["NO_COLOR"] = "1" + environment["PYTHONIOENCODING"] = "utf-8" + return environment + + +def _lines(text: str) -> str: + """Return `text` as `print` writes it to a standard stream.""" + return text.replace("\n", os.linesep) + os.linesep + + +def _nvx_rs_prog(stdout: str, stderr: str) -> tuple[str, str]: + """Return the output of nvx.py with the program names of nvx-rs. + + The sandbox parser's name, `nvx.py sandbox`, has the length of + `nvx-rs sandbox`, so its usage wraps the same way. Errors that the + top-level parser reports show its usage, which nvx-rs replaces with its + own.""" + stdout = stdout.replace("usage: nvx.py sandbox", "usage: nvx-rs sandbox", 1) + stderr = stderr.replace("usage: nvx.py sandbox", "usage: nvx-rs sandbox", 1) + stderr = stderr.replace("nvx.py sandbox: error: ", "nvx-rs sandbox: error: ") + lines = stderr.splitlines(keepends=True) + if ( + lines + and lines[-1].startswith("nvx.py: error: ") + and lines[0].startswith("usage: nvx.py ") + ): + last = lines[-1].replace("nvx.py: error: ", "nvx-rs: error: ", 1) + stderr = _lines(NVX_RS_USAGE) + last + return stdout, stderr + + +def _normalize(text: str, root: Path) -> str: + """Replace the root, as printed and as JSON writes it, and volatile values, + such as process IDs and random identifiers, with placeholders.""" + raw = str(root) + escaped = json.dumps(raw)[1:-1] + if escaped != raw: + text = text.replace(escaped, "{root_json}") + text = text.replace(raw, "{root}") + for pattern, replacement in VOLATILE: + text = pattern.sub(replacement, text) + return text + + +def _decode(data: bytes) -> str: + return data.decode("utf-8", "backslashreplace") + + +def _snapshot(root: Path, relative: str) -> object: + """Return what a step shows of a file or directory below the root.""" + path = _below(root, relative) + try: + info = path.lstat() + except FileNotFoundError: + return None + entry: Entry = {} + if stat.S_ISDIR(info.st_mode): + entry["entries"] = sorted(os.listdir(path)) + elif path.name == sandbox_lifecycle.CAPABILITY_NAME: + entry["size"] = info.st_size + else: + entry["text"] = _normalize(_decode(path.read_bytes()), root) + if os.name != "nt" and ( + path.name in CLI_MODE_NAMES or path.name.startswith("exec") + ): + entry["mode"] = stat.S_IMODE(info.st_mode) + return entry + + +def _python_command(argv: list[str], root: Path, step: Step) -> tuple[int, str, str]: + """Return what nvx.py, or the Python functions that nvx-rs ports for the + commands that nvx.py lacks, give for `nvx-rs ARGV`.""" + environment = { + name: _expand(value, root) + for name, value in cast(dict[str, str], step.get("env", {})).items() + } + if argv[0] == "egress": + try: + compiled = compile_policy_file(Path(argv[2])) + except (ScriptError, OSError, ValueError) as error: + return 1, "", _lines(f"error: {error}") + value = {"allow": list(compiled.allow), "deny": list(compiled.deny)} + return 0, _lines(json.dumps(value, indent=2, sort_keys=True)), "" + if argv[0] == "artifacts": + text = _artifact_expectation(str(root), environment.get("NVX_OPENVMM")) + return 0, _lines(text), "" + child = _child_environment() + child.update( + NVX_FIXTURE_SCRIPTS=str(REPO_ROOT / "scripts"), + NVX_FIXTURE_ROOT=str(root), + NVX_FIXTURE_OPENVMM=str(_below(root, CLI_OPENVMM)), + ) + result = subprocess.run( + [sys.executable, "-u", "-c", PYTHON_SANDBOX, *argv], + cwd=root, + env=child, + stdin=subprocess.DEVNULL, + capture_output=True, + timeout=CLI_STEP_TIMEOUT, + check=False, + ) + stdout, stderr = _nvx_rs_prog(_decode(result.stdout), _decode(result.stderr)) + return result.returncode, stdout, stderr + + +def _rust_command( + nvx_rs: Path, argv: list[str], root: Path, step: Step +) -> tuple[int, str, str]: + """Return what `nvx-rs --root ROOT ARGV` gives.""" + child = _child_environment() + child.update( + { + name: _expand(value, root) + for name, value in cast(dict[str, str], step.get("env", {})).items() + } + ) + result = subprocess.run( + [str(nvx_rs), "--root", str(root), *argv], + cwd=root, + env=child, + stdin=subprocess.DEVNULL, + capture_output=True, + timeout=CLI_STEP_TIMEOUT, + check=False, + ) + return result.returncode, _decode(result.stdout), _decode(result.stderr) + + +def _end_leftovers(root: Path) -> None: + """Kill the fake OpenVMM processes that a failed scenario leaves running.""" + for record in root.rglob(sandbox_lifecycle.RUNTIME_NAME): + with contextlib.suppress(OSError, ValueError, KeyError, TypeError): + pid = int(json.loads(record.read_text(encoding="utf-8"))["pid"]) + os.kill(pid, signal.SIGTERM if os.name == "nt" else signal.SIGKILL) + time.sleep(0.2) + + +def play( + scenario: Scenario, binaries: tuple[Path, Path], implementations: Sequence[str] +) -> list[Entry]: + """Play a scenario in a fresh root, with its steps taking turns among + `implementations`, `python` or `rust`, and return what each step gave.""" + nvx_rs, fake = binaries + with tempfile.TemporaryDirectory( + prefix="nvx-cli-", ignore_cleanup_errors=True + ) as temporary: + root = Path(temporary).resolve() + _make_tree(root, fake) + results: list[Entry] = [] + try: + for index, step in enumerate(cast(list[Step], scenario["steps"])): + for relative in cast(list[str], step.get("remove", [])): + _below(root, relative).unlink() + for relative, content in cast( + dict[str, object], step.get("write", {}) + ).items(): + _write_file(_below(root, relative), content) + argv = [_expand(value, root) for value in cast(list[str], step["argv"])] + implementation = implementations[index % len(implementations)] + if implementation == "python": + status, stdout, stderr = _python_command(argv, root, step) + else: + status, stdout, stderr = _rust_command(nvx_rs, argv, root, step) + result: Entry = { + "status": status, + "stdout": _normalize(stdout, root), + "stderr": _normalize(stderr, root), + } + checks = cast(list[str], step.get("check", [])) + if checks: + result["files"] = { + relative: _snapshot(root, relative) for relative in checks + } + results.append(result) + finally: + _end_leftovers(root) + return results + + +def cli_binaries() -> tuple[Path, Path]: + """Build nvx-rs and nvx-fake-openvmm, and return their paths.""" + subprocess.run( + [ + "cargo", + "build", + "--locked", + "--quiet", + "--features", + "cli,testing", + "--bin", + "nvx-rs", + "--bin", + "nvx-fake-openvmm", + ], + cwd=NVX_RS_DIR, + env=dict(os.environ, NVX_BUNDLE="skip"), + check=True, + ) + target = Path(os.environ.get("CARGO_TARGET_DIR", "target")) + directory = (target if target.is_absolute() else NVX_RS_DIR / target) / "debug" + return directory / f"nvx-rs{EXE}", directory / f"nvx-fake-openvmm{EXE}" + + +def cli_section(binaries: tuple[Path, Path]) -> list[Entry]: + """Record the scenarios of this platform with nvx.py.""" + if sys.version_info < (3, 14, 5): + raise SystemExit( + "recording nvx-rs/tests/fixtures/cli.json needs CPython 3.14.5 or later, " + "whose argparse quotes the choices of an invalid choice, as nvx-rs does" + ) + section: list[Entry] = [] + for scenario in cli_scenarios(): + results = play(scenario, binaries, ("python",)) + steps = [ + {**step, **result} + for step, result in zip( + cast(list[Step], scenario["steps"]), results, strict=True + ) + ] + section.append({**scenario, "steps": steps}) + return section + + +def cli_fixture(section: list[Entry]) -> dict[str, object]: + """Return the fixture with this platform's section and the other's.""" + sections: dict[str, object] = {} + if CLI_FIXTURE.is_file(): + current = cast(dict[str, object], json.loads(CLI_FIXTURE.read_text("utf-8"))) + sections = {name: current.get(name, []) for name in CLI_PLATFORMS} + sections[CLI_PLATFORM] = section + tree: list[Entry] = [ + {"path": relative, "text": text} if text is not None else {"path": relative} + for relative, text in CLI_TREE + ] + tree.append({"path": CLI_OPENVMM, "openvmm": True}) + return { + **_header(), + "tree": tree, + **{name: sections.get(name, []) for name in CLI_PLATFORMS}, + } + + +def _differences( + name: str, mode: str, expected: list[Entry], actual: list[Entry] +) -> list[str]: + problems: list[str] = [] + for index, (want, got) in enumerate(zip(expected, actual, strict=True)): + for field in ("status", "stdout", "stderr", "files"): + if want.get(field) != got.get(field): + diff = "\n".join( + difflib.unified_diff( + json.dumps(want.get(field), indent=1).splitlines(), + json.dumps(got.get(field), indent=1).splitlines(), + "nvx.py", + mode, + lineterm="", + ) + ) + problems.append(f"{name}, step {index + 1}, {field}:\n{diff}") + return problems + + +def check_cli_parity(section: list[Entry], binaries: tuple[Path, Path]) -> list[str]: + """Play every scenario with nvx-rs, and interoperability scenarios with + nvx.py and nvx-rs taking turns both ways, and return how any differs from + nvx.py.""" + problems: list[str] = [] + for scenario in section: + name = cast(str, scenario["name"]) + expected = cast(list[Entry], scenario["steps"]) + modes = [("rust",)] + if scenario.get("interop"): + modes += [("python", "rust"), ("rust", "python")] + for implementations in modes: + actual = play(scenario, binaries, implementations) + problems += _differences(name, "-".join(implementations), expected, actual) + return problems + + +def _write(path: Path, contents: str, check: bool) -> bool: + """Write contents to path, or with check, report whether it would change.""" + current = path.read_text(encoding="utf-8") if path.is_file() else None + if current == contents: + return False + if check: + print(f"{path} is out of date; regenerate it", file=sys.stderr) + return True + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(contents, encoding="utf-8", newline="\n") + print(f"wrote {path}") + return False + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="Generate the golden fixtures and Python-parity tables of nvx-rs." + ) + parser.add_argument( + "--check", + action="store_true", + help="fail instead of writing when a generated file would change", + ) + parser.add_argument( + "--random-cases", + type=int, + help="with --output, the number of random egress policies " + f"(default: {RANDOM_CASES})", + ) + parser.add_argument( + "--output", + type=Path, + help="write or check only an egress policy fixture at this path", + ) + args = parser.parse_args(argv) + if args.random_cases is not None and args.output is None: + parser.error("--random-cases needs --output") + if (sys.implementation.name, sys.version_info[:2]) != ("cpython", PYTHON): + version = f"{sys.version_info.major}.{sys.version_info.minor}" + raise SystemExit( + f"nvx_rs_fixtures.py records CPython {PYTHON[0]}.{PYTHON[1]}, " + f"not {sys.implementation.name} {version}" + ) + if args.output is not None: + random_cases = RANDOM_CASES if args.random_cases is None else args.random_cases + fixture = egress_policy_text(egress_policy_fixture(random_cases)) + return 1 if _write(args.output, fixture, args.check) else 0 + stale = _write(UNICODE_TABLES, unicode_tables_source(), args.check) + for name, document in FIXTURES.items(): + stale |= _write(FIXTURE_DIR / name, fixture_text(document()), args.check) + stale |= _write( + EGRESS_POLICY_FIXTURE, + egress_policy_text(egress_policy_fixture(RANDOM_CASES)), + args.check, + ) + binaries = cli_binaries() + section = cli_section(binaries) + stale |= _write(CLI_FIXTURE, fixture_text(cli_fixture(section)), args.check) + problems = check_cli_parity(section, binaries) + for problem in problems: + print(f"nvx-rs differs from nvx.py: {problem}", file=sys.stderr) + return 1 if stale or problems else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/nvx_tools/archive.py b/scripts/nvx_tools/archive.py index 18163800b..da08cfe9d 100644 --- a/scripts/nvx_tools/archive.py +++ b/scripts/nvx_tools/archive.py @@ -22,6 +22,13 @@ def _normalize_member(member: tarfile.TarInfo) -> tarfile.TarInfo | None: path = PurePosixPath(member.name) if "__pycache__" in path.parts or path.suffix in (".pyc", ".pyo"): return None + for excluded in ReleaseBuildConstants.PROJECT_SOURCE_EXCLUDES: + parts = PurePosixPath(excluded.as_posix()).parts + if any( + path.parts[index : index + len(parts)] == parts + for index in range(len(path.parts) - len(parts) + 1) + ): + return None member.uid = 0 member.gid = 0 member.uname = "" diff --git a/scripts/nvx_tools/azurelinux.py b/scripts/nvx_tools/azurelinux.py index 81e247eb9..549528f33 100644 --- a/scripts/nvx_tools/azurelinux.py +++ b/scripts/nvx_tools/azurelinux.py @@ -17,8 +17,8 @@ ScriptError, download_verified, normalized_sha256_file, - sha256_file, ) +from .guest_binaries import guest_binary_source_files _RPM_FIELD = re.compile(r"[A-Za-z0-9][A-Za-z0-9._+~^-]*") _SHA256 = re.compile(r"[0-9a-f]{64}") @@ -129,8 +129,15 @@ def input_files() -> tuple[Path, ...]: # this digest, so changes to them must also rebuild the artifact. *( root / "scripts" / "nvx_tools" / name - for name in ("azurelinux.py", "build_constants.py", "common.py") + for name in ( + "azurelinux.py", + "build_constants.py", + "common.py", + "elf.py", + "guest_binaries.py", + ) ), + *guest_binary_source_files(), ] for directory in AzureLinuxBuildConstants.GUEST_SOURCE_DIRECTORIES: files.extend(path for path in (root / directory).rglob("*") if path.is_file()) @@ -153,7 +160,7 @@ def input_sha256( "files": [ { "path": path.relative_to(root).as_posix(), - "sha256": sha256_file(path), + "sha256": normalized_sha256_file(path), } for path in input_files() ], diff --git a/scripts/nvx_tools/benchmark.py b/scripts/nvx_tools/benchmark.py index 3c7bcf310..1d801cea1 100644 --- a/scripts/nvx_tools/benchmark.py +++ b/scripts/nvx_tools/benchmark.py @@ -38,10 +38,12 @@ from .build_constants import ( AlpineBuildConstants, BuildConstants, + GuestRustBuildConstants, KernelBuildConstants, OpenVMMBuildConstants, ) from .common import bytes_to_mib, sha256_file +from .guest_binaries import guest_binary_source_sha256, pinned_guest_rust_toolchain from .time_abi import TimeAbiFailure, TimeAbiMonitor, status_script BOOT_MARKER = b"ALPINE-MICROVM-BOOT-OK" @@ -4440,10 +4442,8 @@ def _git_status(repository: Path) -> list[str] | None: def _device_io_helper_provenance( args: argparse.Namespace, initrd: Path ) -> dict[str, str]: - source = require_file( - args.nvx_dir.resolve() / "guest" / "common" / "nvx-device-io.c", - "device I/O helper source", - ) + checkout = args.nvx_dir.resolve() + source = checkout / "nvx-rs" manifest_path = require_file( initrd.with_name(f"{initrd.name}{BuildConstants.PACKAGE_MANIFEST_SUFFIX}"), "initramfs package manifest", @@ -4457,10 +4457,11 @@ def _device_io_helper_provenance( raise ValueError( f"invalid nvx-device-io provenance in {manifest_path}" ) from error - actual_source_sha256 = sha256_file(source) + toolchain = pinned_guest_rust_toolchain(checkout) + actual_source_sha256 = guest_binary_source_sha256(checkout) if source_sha256 != actual_source_sha256: raise ValueError( - "initramfs device I/O helper source does not match the current checkout" + "initramfs Rust guest binary sources do not match the current checkout" ) if not isinstance(binary_sha256, str) or not re.fullmatch( r"[0-9a-f]{64}", binary_sha256 @@ -4470,6 +4471,8 @@ def _device_io_helper_provenance( "source": str(source), "source_sha256": actual_source_sha256, "binary_sha256": binary_sha256, + "toolchain": toolchain, + "target": GuestRustBuildConstants.TARGET, } diff --git a/scripts/nvx_tools/build.py b/scripts/nvx_tools/build.py index 36dbc6238..6dfd9cb72 100644 --- a/scripts/nvx_tools/build.py +++ b/scripts/nvx_tools/build.py @@ -29,6 +29,7 @@ AzureLinuxBuildConstants, BuildConstants, DockerBuildConstants, + GuestRustBuildConstants, InitramfsBuildConstants, KernelBuildConstants, OpenVMMBuildConstants, @@ -39,6 +40,7 @@ artifact_path, download_verified, format_size, + normalized_sha256_file, openvmm_git_state, require_file, require_success, @@ -47,6 +49,11 @@ run_checked, sha256_file, ) +from .guest_binaries import ( + build_guest_binaries, + guest_binary_source_files, + inspect_guest_binaries, +) from .guests import GuestDescriptor, guest_descriptor @@ -347,20 +354,23 @@ def merge_kernel_config_fragment(base: str, fragment: str) -> str: def _initramfs_source_files() -> tuple[Path, ...]: - sources = [ + sources = { BuildConstants.REPO_ROOT / DockerBuildConstants.DOCKERFILE, BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "build.py", BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "build_config.py", BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "build_constants.py", BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "common.py", + BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "elf.py", + BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "guest_binaries.py", BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "guests.py", + *guest_binary_source_files(), *( path for directory in AlpineBuildConstants.GUEST_SOURCE_DIRECTORIES for path in (BuildConstants.REPO_ROOT / directory).rglob("*") if path.is_file() ), - ] + } return tuple( sorted( (require_file(path, "initramfs provenance input") for path in sources), @@ -380,7 +390,7 @@ def initramfs_provenance_inputs() -> dict[str, object]: "source_files": [ { "path": path.relative_to(BuildConstants.REPO_ROOT).as_posix(), - "sha256": sha256_file(path), + "sha256": normalized_sha256_file(path), } for path in _initramfs_source_files() ], @@ -600,53 +610,6 @@ def _install(source: Path, destination: Path) -> dict[str, str]: } -def _build_static_helper( - work: Path, - source: Path, - destination: Path, - cflags: tuple[str, ...] = InitramfsBuildConstants.STATIC_HELPER_CFLAGS, - compiler_name: str = "cc", -) -> dict[str, str]: - compiler = require_tool(compiler_name) - output = work / source.stem - run_checked( - [ - compiler, - *cflags, - "-o", - output, - source, - ] - ) - shutil.copyfile(output, destination) - destination.chmod(0o755) - return { - "source_sha256": sha256_file(source), - "binary_sha256": sha256_file(output), - } - - -def _build_device_io_helper(work: Path, destination: Path) -> dict[str, str]: - compiler = require_tool("cc") - source = BuildConstants.REPO_ROOT / "guest" / "common" / "nvx-device-io.c" - output = work / "nvx-device-io" - run_checked( - [ - compiler, - *InitramfsBuildConstants.DEVICE_IO_CFLAGS, - "-o", - output, - source, - ] - ) - shutil.copyfile(output, destination) - destination.chmod(0o755) - return { - "source_sha256": sha256_file(source), - "binary_sha256": sha256_file(output), - } - - def _apk_add(root: Path, *packages: str) -> None: loader = root / "lib" / "ld-musl-x86_64.so.1" environment = os.environ.copy() @@ -863,31 +826,25 @@ def _install_guest_files( if name == "nvx-bashrc": destination.chmod(0o644) - for name in InitramfsBuildConstants.STATIC_HELPERS: - helpers[name] = _build_static_helper( - config.work, - common / f"{name}.c", - root / "sbin" / name, - ) - for name in InitramfsBuildConstants.MUSL_STATIC_HELPERS: - helpers[name] = _build_static_helper( - config.work, - common / f"{name}.c", - root / "sbin" / name, - compiler_name=InitramfsBuildConstants.MUSL_COMPILER, - ) - probe = InitramfsBuildConstants.TIME_PROBE_NAME - helpers[probe] = _build_static_helper( - config.work, - common / f"{probe}.c", - root / "sbin" / probe, - InitramfsBuildConstants.TIME_PROBE_CFLAGS, - compiler_name=InitramfsBuildConstants.MUSL_COMPILER, - ) - helpers["nvx-device-io"] = _build_device_io_helper( - config.work, - root / "sbin" / "nvx-device-io", - ) + prebuilt = os.environ.get(GuestRustBuildConstants.BINARIES_VARIABLE) + if prebuilt: + binary_directory = Path(prebuilt) + result = inspect_guest_binaries(binary_directory) + else: + binary_directory = config.work / GuestRustBuildConstants.OUTPUT_DIRECTORY_NAME + result = build_guest_binaries(binary_directory) + for name in GuestRustBuildConstants.BINARIES: + source = binary_directory / name + destination = root / "sbin" / name + shutil.copyfile(source, destination) + destination.chmod(0o755) + binary_sha256 = sha256_file(destination) + if binary_sha256 != result.binaries[name]: + raise ScriptError(f"installed Rust guest binary changed: {name}") + helpers[name] = { + "source_sha256": result.source_sha256, + "binary_sha256": binary_sha256, + } return helpers @@ -942,7 +899,7 @@ def _guest_customization_files(descriptor: GuestDescriptor) -> tuple[Path, ...]: ) if path.is_file() ) - return (*common, *alpine) + return (*common, *alpine, *guest_binary_source_files()) def build_initramfs(config: InitramfsBuildConfig) -> None: diff --git a/scripts/nvx_tools/build_constants.py b/scripts/nvx_tools/build_constants.py index 13841b9cb..3419e31ba 100644 --- a/scripts/nvx_tools/build_constants.py +++ b/scripts/nvx_tools/build_constants.py @@ -176,6 +176,8 @@ class GuestRustBuildConstants: BUILD_MANIFEST_NAME: Final = "manifest.json" BUILD_MANIFEST_FORMAT: Final = 1 BUILD_MANIFEST_DOMAIN: Final = "nvx-rust-guest-binary-build" + OUTPUT_DIRECTORY_NAME: Final = "guest-binaries" + SOURCE_PATHS: Final = ("nvx-rs", "rust-toolchain.toml") BINARIES: Final = ( "nvx-console-pending", "nvx-mmio-write", @@ -358,53 +360,14 @@ class InitramfsBuildConstants: TIMESTAMP: Final = 0 CPIO_FORMAT: Final = "newc" GZIP_COMPRESSION_LEVEL: Final = 9 - STATIC_HELPER_CFLAGS: Final = ( - "-static", - "-Os", - "-s", - "-Wall", - "-Wextra", - "-Werror", - ) - DEVICE_IO_CFLAGS: Final = ( - "-nostdlib", - "-static", - "-Os", - "-fno-builtin", - "-fno-pie", - "-fno-stack-protector", - "-no-pie", - "-Wl,--build-id=none", - "-Wl,-z,noexecstack", - "-s", - ) - STATIC_HELPERS: Final = ( - "nvx-reseed", - "nvx-mmio-write", - "nvx-console-pending", - "nvx-managed-agent", - ) - # Time tools, nvx-exit, and nvx-ingress are linked against musl: a static - # glibc binary adds about 700 KB to the initramfs, and every unpacked - # kilobyte delays cold boot. nvx-exit is also a restored guest's usual - # first exec. - MUSL_COMPILER: Final = "musl-gcc" - MUSL_STATIC_HELPERS: Final = ("nvx-time", "nvx-exit", "nvx-ingress") - TIME_PROBE_NAME: Final = "nvx-time-probe" - TIME_PROBE_CFLAGS: Final = ( - "-std=gnu11", - "-static", - "-O2", - "-pthread", - "-s", - "-Wall", - "-Wextra", - "-Werror", - ) class DockerBuildConstants: DOCKERFILE: Final = Path("docker") / "Dockerfile" + RUST_BUILDER_IMAGE: Final = ( + f"rust:{GuestRustBuildConstants.TOOLCHAIN}-slim-bookworm@" + "sha256:d7482085ff5b415f84dba5647ae71606650bdef00db7aeb69f4b3d170c3e4082" + ) ALPINE_TARGET: Final = "artifacts" UBUNTU_TARGET: Final = "ubuntu-guest-artifacts" AZURELINUX_TARGET: Final = "azurelinux-guest-artifacts" @@ -450,6 +413,7 @@ class ReleaseBuildConstants: PROJECT_SOURCE_PATHS: Final = ( ".github/agents/nvx-adversary.md", "guest", + "nvx-rs", "ubuntu", "azurelinux", "data/linux-kvm-virtual-machine.csv", @@ -471,6 +435,7 @@ class ReleaseBuildConstants: "requirements-dev.txt", "rust-toolchain.toml", ) + PROJECT_SOURCE_EXCLUDES: Final = (Path("nvx-rs") / "target",) GUEST_ARTIFACT_NAMES: Final = ( KernelBuildConstants.BINARY_NAME, KernelBuildConstants.CONFIG_NAME, diff --git a/scripts/nvx_tools/ci.py b/scripts/nvx_tools/ci.py index c354a6cf1..0b89a29e3 100644 --- a/scripts/nvx_tools/ci.py +++ b/scripts/nvx_tools/ci.py @@ -131,13 +131,14 @@ # The OpenVMM VMM tests also boot the NVX kernel and Alpine initramfs, so they # add the inputs of those artifacts' cache keys and of the job that builds them. OPENVMM_VMM_TEST_INPUT_PATHS = re.compile( - r"^(openvmm($|/)|rust-toolchain\.toml$|scripts/setup/|" + r"^(openvmm($|/)|nvx-rs/|rust-toolchain\.toml$|scripts/setup/|" r"\.github/workflows/ci\.yml$|" r"\.github/actions/(build-guest-artifacts|checkout-openvmm|sccache|" r"setup-curl|validate-runner)/|" r"scripts/nvx\.py$|" r"scripts/nvx_tools/" - r"(__init__|build|build_config|build_constants|ci|common|guests)\.py$|" + r"(__init__|build|build_config|build_constants|ci|common|elf|" + r"guest_binaries|guests)\.py$|" r"SOURCE-MANIFEST\.json$|docker/Dockerfile$|\.dockerignore$|kernel/|guest/(alpine|common)/)" ) diff --git a/scripts/nvx_tools/guest_binaries.py b/scripts/nvx_tools/guest_binaries.py index b06c4919d..3b7c4fd1b 100644 --- a/scripts/nvx_tools/guest_binaries.py +++ b/scripts/nvx_tools/guest_binaries.py @@ -14,7 +14,14 @@ from typing import cast from .build_constants import BuildConstants, GuestRustBuildConstants -from .common import ScriptError, require_file, require_tool, run_checked, sha256_file +from .common import ( + ScriptError, + normalized_sha256_file, + require_file, + require_tool, + run_checked, + sha256_file, +) from .elf import check_static_executables @@ -26,58 +33,61 @@ class GuestBinaryBuildResult: binaries: dict[str, str] -def guest_binary_source_files() -> tuple[Path, ...]: +def guest_binary_source_files(root: Path | None = None) -> tuple[Path, ...]: """Return the complete ordered input set of the PR 1 guest executables.""" + root = Path(root or BuildConstants.REPO_ROOT) + crate = root / "nvx-rs" files = { - GuestRustBuildConstants.MANIFEST, - GuestRustBuildConstants.LOCKFILE, - GuestRustBuildConstants.BUILD_SCRIPT, - GuestRustBuildConstants.TOOLCHAIN_FILE, - *GuestRustBuildConstants.BUILD_RECIPE_FILES, + crate / "Cargo.toml", + crate / "Cargo.lock", + crate / "build.rs", + root / "rust-toolchain.toml", + root / "scripts" / "nvx_tools" / "build_constants.py", + root / "scripts" / "nvx_tools" / "guest_binaries.py", } - for directory in GuestRustBuildConstants.SOURCE_DIRECTORIES: - if not directory.is_dir(): - raise ScriptError(f"nvx-rs source directory not found: {directory}") - files.update( - path - for path in directory.rglob("*") - if path.is_file() and "target" not in path.relative_to(directory).parts - ) + source = crate / "src" + if not source.is_dir(): + raise ScriptError(f"nvx-rs source directory not found: {source}") + files.update( + path + for path in source.rglob("*") + if path.is_file() and "target" not in path.relative_to(source).parts + ) return tuple( sorted( (require_file(path, "Rust guest binary source input") for path in files), - key=lambda path: path.relative_to(BuildConstants.REPO_ROOT).as_posix(), + key=lambda path: path.relative_to(root).as_posix(), ) ) -def guest_binary_source_sha256() -> str: +def guest_binary_source_sha256(root: Path | None = None) -> str: """Hash the paths and contents that determine every Rust guest executable.""" + root = Path(root or BuildConstants.REPO_ROOT) inventory = { "domain": GuestRustBuildConstants.SOURCE_DIGEST_DOMAIN, "format": GuestRustBuildConstants.SOURCE_DIGEST_FORMAT, "files": [ { - "path": path.relative_to(BuildConstants.REPO_ROOT).as_posix(), - "sha256": sha256_file(path), + "path": path.relative_to(root).as_posix(), + "sha256": normalized_sha256_file(path), } - for path in guest_binary_source_files() + for path in guest_binary_source_files(root) ], } encoded = json.dumps(inventory, sort_keys=True, separators=(",", ":")).encode() return hashlib.sha256(encoded).hexdigest() -def pinned_guest_rust_toolchain() -> str: +def pinned_guest_rust_toolchain(root: Path | None = None) -> str: """Read and validate the repository's exact Rust release pin.""" - contents = require_file( - GuestRustBuildConstants.TOOLCHAIN_FILE, "Rust toolchain pin" - ).read_text(encoding="utf-8") + toolchain_file = (root or BuildConstants.REPO_ROOT) / "rust-toolchain.toml" + contents = require_file(toolchain_file, "Rust toolchain pin").read_text( + encoding="utf-8" + ) matches = re.findall(r'^channel = "([^"]+)"$', contents, re.MULTILINE) if len(matches) != 1: - raise ScriptError( - f"{GuestRustBuildConstants.TOOLCHAIN_FILE} must define one toolchain channel" - ) + raise ScriptError(f"{toolchain_file} must define one toolchain channel") [toolchain] = matches if toolchain != GuestRustBuildConstants.TOOLCHAIN: raise ScriptError( @@ -289,6 +299,44 @@ def check_guest_binaries(toolchain: str | None = None) -> None: ) +def inspect_guest_binaries(directory: Path) -> GuestBinaryBuildResult: + """Validate and identify a current, previously staged guest build.""" + directory = directory.resolve() + retired = _validate_owned_output(directory) + missing = [ + binary + for binary in GuestRustBuildConstants.BINARIES + if not (directory / binary).is_file() + ] + if retired or missing: + raise ScriptError( + f"guest binary output is not a current build of every executable: " + f"{directory}" + ) + paths = [directory / binary for binary in GuestRustBuildConstants.BINARIES] + check_static_executables(paths) + result = GuestBinaryBuildResult( + source_sha256=guest_binary_source_sha256(), + binaries={ + binary: sha256_file(path) + for binary, path in zip( + GuestRustBuildConstants.BINARIES, paths, strict=True + ) + }, + ) + manifest_path = directory / GuestRustBuildConstants.BUILD_MANIFEST_NAME + manifest = cast( + dict[str, object], + json.loads(manifest_path.read_text(encoding="utf-8")), + ) + if manifest != _manifest(result): + raise ScriptError( + "Rust guest binary build manifest does not match the current " + "sources, build pins, and executables" + ) + return result + + def build_guest_binaries(output: Path) -> GuestBinaryBuildResult: """Build, statically check, digest, and stage a fresh set of executables.""" if sys.platform != "linux": diff --git a/scripts/nvx_tools/release.py b/scripts/nvx_tools/release.py index 26095eebe..4024260cb 100644 --- a/scripts/nvx_tools/release.py +++ b/scripts/nvx_tools/release.py @@ -36,6 +36,7 @@ AlpineBuildConstants, AzureLinuxBuildConstants, BuildConstants, + GuestRustBuildConstants, InitramfsBuildConstants, KernelBuildConstants, OpenVMMBuildConstants, @@ -59,6 +60,10 @@ write_sha256_sums, ) from .development_release import development_release_tag +from .guest_binaries import ( + guest_binary_source_files, + pinned_guest_rust_toolchain, +) from .ubuntu import converter_input_sha256, customization_files, package_lock_sha256 GITHUB_API_VERSION = "2022-11-28" @@ -1231,6 +1236,7 @@ def _validate_source_manifest_metadata( alpine_value = manifest.get("alpine") ubuntu_value = manifest.get("ubuntu") azurelinux_value = manifest.get("azurelinux") + guest_rust_value = manifest.get("guest_rust") source_value = kernel_inputs.get("source") config_value = kernel_inputs.get("input_config") if not all( @@ -1240,6 +1246,7 @@ def _validate_source_manifest_metadata( alpine_value, ubuntu_value, azurelinux_value, + guest_rust_value, source_value, config_value, ) @@ -1249,8 +1256,17 @@ def _validate_source_manifest_metadata( alpine = cast(dict[str, object], alpine_value) ubuntu = cast(dict[str, object], ubuntu_value) azurelinux = cast(dict[str, object], azurelinux_value) + guest_rust = cast(dict[str, object], guest_rust_value) source = cast(dict[str, object], source_value) input_config = cast(dict[str, object], config_value) + expected_guest_rust = { + "toolchain": pinned_guest_rust_toolchain(), + "target": GuestRustBuildConstants.TARGET, + } + if guest_rust != expected_guest_rust: + raise ScriptError( + "SOURCE-MANIFEST.json Rust guest toolchain does not match the build pin" + ) source_patches = source.get("patches") if not isinstance(source_patches, list): raise ScriptError("kernel provenance source patches must be a list") @@ -1295,7 +1311,11 @@ def _validate_source_manifest_metadata( "minirootfs_url": AlpineBuildConstants.MINIROOTFS_URL, "minirootfs_sha256": AlpineBuildConstants.MINIROOTFS_SHA256, "guest_sources": [ - path.as_posix() for path in AlpineBuildConstants.GUEST_SOURCE_DIRECTORIES + *( + path.as_posix() + for path in AlpineBuildConstants.GUEST_SOURCE_DIRECTORIES + ), + *GuestRustBuildConstants.SOURCE_PATHS, ], "package_manifests": [ ( @@ -1329,6 +1349,7 @@ def _validate_source_manifest_metadata( for path in UbuntuBuildConstants.GUEST_SOURCE_DIRECTORIES ), UbuntuBuildConstants.PACKAGE_LOCK_RELATIVE_PATH.as_posix(), + *GuestRustBuildConstants.SOURCE_PATHS, ], "package_manifests": [ ( @@ -1363,6 +1384,7 @@ def _validate_source_manifest_metadata( for path in AzureLinuxBuildConstants.GUEST_SOURCE_DIRECTORIES ), AzureLinuxBuildConstants.PACKAGE_LOCK_RELATIVE_PATH.as_posix(), + *GuestRustBuildConstants.SOURCE_PATHS, ], "package_manifests": [ ( @@ -1699,6 +1721,8 @@ def verify_source_tree() -> None: manifest, kernel_provenance_inputs(), ) + guest_binary_source_files() + pinned_guest_rust_toolchain() required = { BuildConstants.REPO_ROOT / "kernel" diff --git a/scripts/nvx_tools/ubuntu.py b/scripts/nvx_tools/ubuntu.py index 8c3a53cdb..5b9cbe6a6 100644 --- a/scripts/nvx_tools/ubuntu.py +++ b/scripts/nvx_tools/ubuntu.py @@ -33,6 +33,7 @@ require_tool, sha256_file, ) +from .guest_binaries import guest_binary_source_files _SHA256 = re.compile(r"[0-9a-f]{64}") _PACKAGE_NAME = re.compile(r"[a-z0-9][a-z0-9+.-]*") @@ -1009,6 +1010,7 @@ def customization_files() -> tuple[Path, ...]: if path.is_file() ), UbuntuBuildConstants.PACKAGE_LOCK, + *guest_binary_source_files(), ) @@ -1026,7 +1028,7 @@ def converter_input_sha256(customization_files: Sequence[Path]) -> str: "customization_files": [ { "path": path.relative_to(BuildConstants.REPO_ROOT).as_posix(), - "sha256": sha256_file(path), + "sha256": normalized_sha256_file(path), } for path in sorted(customization_files) ], diff --git a/scripts/setup/setup-linux-mshv.sh b/scripts/setup/setup-linux-mshv.sh index b3cef1ad1..15c95b60c 100644 --- a/scripts/setup/setup-linux-mshv.sh +++ b/scripts/setup/setup-linux-mshv.sh @@ -227,9 +227,14 @@ install_rust_tools() { fi rustup toolchain install "$RUST_TOOLCHAIN" --profile minimal + rustup target add --toolchain "$RUST_TOOLCHAIN" \ + x86_64-unknown-linux-musl rust_version=$(RUSTUP_TOOLCHAIN=$RUST_TOOLCHAIN rustc --version | awk '{print $2}') [ "$rust_version" = "$RUST_TOOLCHAIN" ] || die "Rust ${RUST_TOOLCHAIN} is required, found ${rust_version}" + rustup target list --installed --toolchain "$RUST_TOOLCHAIN" | + grep -Fxq x86_64-unknown-linux-musl || + die "Rust target x86_64-unknown-linux-musl is not installed" if ! command -v cargo-nextest >/dev/null 2>&1 || ! cargo nextest --version | grep -Fq "cargo-nextest ${CARGO_NEXTEST_VERSION}"; then cargo +"$RUST_TOOLCHAIN" install --locked cargo-nextest \ diff --git a/scripts/setup/setup-linux-runner.sh b/scripts/setup/setup-linux-runner.sh index a55a00b2e..4080c150e 100644 --- a/scripts/setup/setup-linux-runner.sh +++ b/scripts/setup/setup-linux-runner.sh @@ -596,8 +596,7 @@ install_rust_tools() { run_as_root env \ CARGO_HOME="$trusted_cargo_home" \ RUSTUP_HOME="$trusted_rustup_home" \ - RUSTUP_TOOLCHAIN="$RUST_TOOLCHAIN" \ - "$rustup" target add \ + "$rustup" target add --toolchain "$RUST_TOOLCHAIN" \ x86_64-unknown-none \ x86_64-unknown-linux-musl \ x86_64-unknown-uefi diff --git a/scripts/test_guest_binaries.py b/scripts/test_guest_binaries.py index dc71815c8..f667b76f8 100644 --- a/scripts/test_guest_binaries.py +++ b/scripts/test_guest_binaries.py @@ -100,6 +100,89 @@ def test_source_identity_covers_the_crate_and_the_toolchain(self): GuestRustBuildConstants.TOOLCHAIN, ) + def test_toolchain_pin_must_match_the_guest_build_constants(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + (root / "rust-toolchain.toml").write_text( + '[toolchain]\nchannel = "9.9.9"\n', + encoding="utf-8", + ) + with self.assertRaisesRegex( + guest_binaries.ScriptError, + "Rust guest build toolchain mismatch", + ): + guest_binaries.pinned_guest_rust_toolchain(root) + + def test_source_inventory_preserves_the_callers_root_spelling(self): + with tempfile.TemporaryDirectory( + prefix=".test-guest-binary-root-", + dir=BuildConstants.REPO_ROOT, + ) as temporary: + root = Path(temporary) + relative_root = Path(os.path.relpath(root, Path.cwd())) + crate = root / "nvx-rs" + source = crate / "src" + tools = root / "scripts" / "nvx_tools" + source.mkdir(parents=True) + tools.mkdir(parents=True) + for path in ( + crate / "Cargo.toml", + crate / "Cargo.lock", + crate / "build.rs", + source / "lib.rs", + tools / "build_constants.py", + tools / "guest_binaries.py", + root / "rust-toolchain.toml", + ): + path.write_text(f"{path.name}\n", encoding="utf-8") + + inventory = guest_binaries.guest_binary_source_files(relative_root) + + self.assertTrue(all(not path.is_absolute() for path in inventory)) + self.assertEqual( + guest_binaries.guest_binary_source_sha256(relative_root), + guest_binaries.guest_binary_source_sha256(root), + ) + + def test_source_identity_follows_a_content_preserving_module_move(self): + # The guest image cache keys append this identity because hashFiles + # hashes contents only: moving a module into its own mod.rs keeps every + # file's bytes but changes the source that guest provenance records. + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + crate = root / "nvx-rs" + module = crate / "src" / "guest" / "ingress" + tools = root / "scripts" / "nvx_tools" + module.mkdir(parents=True) + tools.mkdir(parents=True) + for path in ( + crate / "Cargo.toml", + crate / "Cargo.lock", + crate / "build.rs", + crate / "src" / "lib.rs", + module / "encode.rs", + tools / "build_constants.py", + tools / "guest_binaries.py", + root / "rust-toolchain.toml", + ): + path.write_text(f"{path.name}\n", encoding="utf-8") + + def contents() -> list[bytes]: + return [ + path.read_bytes() + for path in guest_binaries.guest_binary_source_files(root) + ] + + original_contents = contents() + original_identity = guest_binaries.guest_binary_source_sha256(root) + (module / "encode").mkdir() + (module / "encode.rs").rename(module / "encode" / "mod.rs") + + self.assertEqual(contents(), original_contents) + self.assertNotEqual( + guest_binaries.guest_binary_source_sha256(root), original_identity + ) + def test_build_uses_fresh_remapped_target_and_writes_manifest(self): with tempfile.TemporaryDirectory( prefix=".test-guest-binaries-", @@ -207,9 +290,16 @@ def run_cargo( guest_binaries.guest_binary_source_sha256(), first_source ) result = guest_binaries.build_guest_binaries(output) + inspected = guest_binaries.inspect_guest_binaries(output) + (source / "lib.rs").write_bytes(b"pub fn fixture() {}\r\n") + self.assertEqual( + guest_binaries.guest_binary_source_sha256(), + result.source_sha256, + ) # One Cargo build produces every binary from the canonical recipe. self.assertEqual(len(commands), 1) + self.assertEqual(inspected, result) [command] = commands self.assertEqual(command[:3], ["cargo", "+1.95.0", "build"]) self.assertEqual( @@ -246,6 +336,7 @@ def run_cargo( [path.name for path in check_static.call_args.args[0]], list(GuestRustBuildConstants.BINARIES), ) + self.assertEqual(check_static.call_count, 2) self.assertEqual( set(result.binaries), set(GuestRustBuildConstants.BINARIES) ) @@ -437,6 +528,37 @@ def test_rebuild_refuses_unrecorded_or_unrelated_files(self): cargo.assert_not_called() self.assertEqual(_contents(output), before) + def test_inspection_refuses_an_earlier_build(self): + # A build upgrades these outputs in place, but none is a current build. + earlier = tuple( + binary + for binary in GuestRustBuildConstants.BINARIES + if binary not in GuestRustBuildConstants.ADDED_BINARIES + ) + retired = (*GuestRustBuildConstants.BINARIES, "nvx-port-io") + for case, binaries in ( + ("build without the added executables", earlier), + ("build from before nvx-port-io retired", retired), + ): + with ( + self.subTest(case), + tempfile.TemporaryDirectory( + prefix=".test-guest-binary-inspect-", + dir=BuildConstants.REPO_ROOT, + ) as temporary, + ): + output = Path(temporary) / "output" + _write_build_output(output, binaries, recorded=binaries) + before = _contents(output) + with ( + _fake_guest_build(), + self.assertRaisesRegex( + guest_binaries.ScriptError, "not a current build" + ), + ): + guest_binaries.inspect_guest_binaries(output) + self.assertEqual(_contents(output), before) + def test_initialization_failure_removes_allocated_work_directory(self): with tempfile.TemporaryDirectory( prefix=".test-guest-binary-initialization-", @@ -608,6 +730,29 @@ def test_test_command_uses_the_selected_directory(self): args.handler(args) test.assert_called_once_with(Path("build/guest-binaries")) + def test_source_identity_command_prints_the_recorded_source_digest(self): + # The guest image cache keys end with this output, so it must be the + # digest that a build records as the binaries' source. + for argv, root in ( + (["guest-binary-source-identity"], BuildConstants.REPO_ROOT), + ( + ["guest-binary-source-identity", "--root", "checkout"], + Path("checkout"), + ), + ): + with self.subTest(argv=argv): + args = nvx.parse_args(argv) + stdout = io.StringIO() + with ( + patch.object( + nvx, "guest_binary_source_sha256", return_value="c" * 64 + ) as identity, + contextlib.redirect_stdout(stdout), + ): + args.handler(args) + identity.assert_called_once_with(root) + self.assertEqual(stdout.getvalue(), "c" * 64 + "\n") + if __name__ == "__main__": unittest.main() diff --git a/scripts/test_guest_ingress.py b/scripts/test_guest_ingress.py deleted file mode 100644 index 0ddd33d5d..000000000 --- a/scripts/test_guest_ingress.py +++ /dev/null @@ -1,416 +0,0 @@ -#!/usr/bin/env python3 -"""Tests for the guest listener announcer, guest/common/nvx-ingress.c. - -The helper's test mode runs synthetic snapshots of the kernel's socket tables -and socket diagnostics through the scans that a guest runs, so these tests -check what it announces without a guest or privileges. -""" - -from __future__ import annotations - -import ipaddress -import shutil -import struct -import subprocess -import sys -import tempfile -import unittest -from dataclasses import dataclass -from pathlib import Path - -REPO_ROOT = Path(__file__).resolve().parents[1] -SOURCE = REPO_ROOT / "guest" / "common" / "nvx-ingress.c" - -TCP = 6 -UDP = 17 -IPV4 = 1 -IPV6 = 2 -BOTH = IPV4 | IPV6 -TCP_LISTEN = 0x0A -TCP_ESTABLISHED = 0x01 -UDP_CONNECTED = 0x01 -UDP_UNCONNECTED = 0x07 -UDP_SETTLE_MS = 1000 -# The helper's first source port, which its test mode excludes like its own. -OWN_PORT = 1023 -TABLE_HEADER = ( - " sl local_address rem_address st tx_queue rx_queue tr tm->when " - "retrnsmt uid timeout inode\n" -) -NETLINK_HEADER = struct.Struct("=IHHII") -SOCK_DIAG_BY_FAMILY = 20 -NETLINK_DONE = 3 -INET_DIAG_SKV6ONLY = 11 -AF_INET6 = 10 - - -def kernel_address(address: str) -> str: - """Render an address as the kernel's socket tables print it on x86.""" - packed = ipaddress.ip_address(address).packed - return "".join( - f"{int.from_bytes(packed[offset : offset + 4], 'little'):08X}" - for offset in range(0, len(packed), 4) - ) - - -@dataclass(frozen=True) -class Socket: - protocol: int - address: str - port: int - inode: int - state: int - peer: tuple[str, int] | None = None - - @property - def ipv6(self) -> bool: - return ipaddress.ip_address(self.address).version == 6 - - @property - def table(self) -> str: - return ("tcp" if self.protocol == TCP else "udp") + ("6" if self.ipv6 else "") - - def line(self, index: int) -> str: - remote, remote_port = self.peer or ("::" if self.ipv6 else "0.0.0.0", 0) - return ( - f"{index:4}: {kernel_address(self.address)}:{self.port:04X} " - f"{kernel_address(remote)}:{remote_port:04X} {self.state:02X} " - "00000000:00000000 00:00000000 00000000 0 0 " - f"{self.inode} 2 0000000000000000\n" - ) - - -def tcp_listener(address: str, port: int, inode: int = 1) -> Socket: - return Socket(TCP, address, port, inode, TCP_LISTEN) - - -def udp_socket( - address: str, port: int, inode: int, peer: tuple[str, int] | None = None -) -> Socket: - state = UDP_UNCONNECTED if peer is None else UDP_CONNECTED - return Socket(UDP, address, port, inode, state, peer) - - -def diag_listener(port: int, ipv6_only: bool, address: str = "::") -> bytes: - """Encode a socket diagnostics message for an IPv6 TCP listener.""" - message = struct.pack("=BBBB", AF_INET6, TCP_LISTEN, 0, 0) - message += struct.pack("!HH", port, 0) - message += ipaddress.IPv6Address(address).packed + bytes(16) - message += struct.pack("=III", 0, 0, 0) + struct.pack("=IIIII", 0, 0, 0, 0, 0) - message += struct.pack("=HH", 5, INET_DIAG_SKV6ONLY) + bytes( - (int(ipv6_only), 0, 0, 0) - ) - return ( - NETLINK_HEADER.pack( - NETLINK_HEADER.size + len(message), SOCK_DIAG_BY_FAMILY, 2, 1, 0 - ) - + message - ) - - -def diag_done() -> bytes: - return NETLINK_HEADER.pack(NETLINK_HEADER.size + 4, NETLINK_DONE, 2, 1, 0) + bytes( - 4 - ) - - -@dataclass(frozen=True) -class Snapshot: - time_ms: int - sockets: tuple[Socket, ...] = () - bindv6only: int | None = None - diag: bytes | None = None - - def write(self, root: Path) -> None: - (root / "net").mkdir(parents=True) - for table in ("tcp", "tcp6", "udp", "udp6"): - sockets = [socket for socket in self.sockets if socket.table == table] - (root / "net" / table).write_text( - TABLE_HEADER - + "".join(socket.line(index) for index, socket in enumerate(sockets)), - encoding="utf-8", - ) - if self.bindv6only is not None: - sysctl = root / "sys" / "net" / "ipv6" - sysctl.mkdir(parents=True) - (sysctl / "bindv6only").write_text(f"{self.bindv6only}\n", encoding="utf-8") - if self.diag is not None: - (root / "diag").write_bytes(self.diag) - (root / "time").write_text(f"{self.time_ms}\n", encoding="utf-8") - - -@dataclass(frozen=True) -class Announcement: - payload: bytes - truncated: bool - listeners: dict[tuple[int, int], int] - order: tuple[tuple[int, int], ...] - - @classmethod - def decode(cls, payload: bytes) -> Announcement: - magic, version, flags, count = struct.unpack("!4sBBH", payload[:8]) - if magic != b"NVXL" or version != 1 or len(payload) != 8 + 4 * count: - raise AssertionError(f"malformed announcement {payload.hex()}") - entries = [ - struct.unpack("!BBH", payload[8 + 4 * index : 12 + 4 * index]) - for index in range(count) - ] - return cls( - payload, - bool(flags & 1), - {(protocol, port): families for protocol, families, port in entries}, - tuple((protocol, port) for protocol, _families, port in entries), - ) - - -@unittest.skipUnless(sys.platform.startswith("linux"), "nvx-ingress builds on Linux") -class GuestIngressTests(unittest.TestCase): - binary: Path - directory: tempfile.TemporaryDirectory[str] - - @classmethod - def setUpClass(cls) -> None: - compiler = shutil.which("cc") or shutil.which("gcc") - if compiler is None: - raise unittest.SkipTest("a C compiler is unavailable") - cls.directory = tempfile.TemporaryDirectory() - cls.binary = Path(cls.directory.name) / "nvx-ingress" - subprocess.run( - [ - compiler, - "-O1", - "-Wall", - "-Wextra", - "-Werror", - "-o", - str(cls.binary), - str(SOURCE), - ], - check=True, - capture_output=True, - timeout=120, - ) - - @classmethod - def tearDownClass(cls) -> None: - cls.directory.cleanup() - - def announce(self, *snapshots: Snapshot) -> list[Announcement]: - with tempfile.TemporaryDirectory() as temporary: - for index, snapshot in enumerate(snapshots): - snapshot.write(Path(temporary) / str(index)) - result = subprocess.run( - [str(self.binary), "test", temporary], - capture_output=True, - text=True, - timeout=30, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - lines = result.stdout.splitlines() - self.assertEqual( - [int(line.split()[0]) for line in lines], - [snapshot.time_ms for snapshot in snapshots], - ) - return [Announcement.decode(bytes.fromhex(line.split()[1])) for line in lines] - - def test_announcement_golden_vector(self): - (announcement,) = self.announce( - Snapshot( - 0, - (tcp_listener("0.0.0.0", 8080), tcp_listener("fd00::a00:2", 8443)), - ) - ) - self.assertEqual(announcement.payload.hex(), "4e56584c0100000206011f90060220fb") - - def test_only_listeners_reachable_through_the_gateway_are_announced(self): - sockets = ( - tcp_listener("0.0.0.0", 8080), - tcp_listener("192.0.2.2", 8081), - tcp_listener("127.0.0.1", 8082), - tcp_listener("127.0.0.53", 8083), - tcp_listener("::", 8084), - tcp_listener("fd00::a00:2", 8085), - tcp_listener("::1", 8086), - tcp_listener("fe80::1", 8087), - tcp_listener("::ffff:127.0.0.1", 8088), - tcp_listener("::ffff:192.0.2.2", 8089), - # One port's IPv4 and IPv6 listeners merge their families. - tcp_listener("0.0.0.0", 8090), - tcp_listener("fd00::a00:2", 8090, inode=2), - # Established connections are not listeners. - Socket(TCP, "192.0.2.2", 8091, 3, TCP_ESTABLISHED, ("192.0.2.1", 40000)), - ) - (announcement,) = self.announce(Snapshot(0, sockets)) - self.assertEqual( - announcement.listeners, - { - (TCP, 8080): IPV4, - (TCP, 8081): IPV4, - (TCP, 8084): BOTH, - (TCP, 8085): IPV6, - (TCP, 8089): IPV4, - (TCP, 8090): BOTH, - }, - ) - self.assertFalse(announcement.truncated) - - def test_ipv6_only_tcp_listeners_follow_socket_diagnostics(self): - sockets = tuple( - tcp_listener("::", port, inode=port) for port in range(7001, 7006) - ) - diag = ( - diag_listener(7001, ipv6_only=True) - + diag_listener(7002, ipv6_only=False) - # A port that IPv6-only and dual-stack sockets share takes IPv4. - + diag_listener(7003, ipv6_only=True) - + diag_listener(7003, ipv6_only=False) - # Only listeners on the unspecified address are classified. - + diag_listener(7004, ipv6_only=True, address="fd00::a00:2") - + diag_done() - # Messages after the end of the dump are ignored. - + diag_listener(7005, ipv6_only=True) - ) - default, ipv6_only_default = self.announce( - Snapshot(0, sockets, diag=diag), - Snapshot(100, sockets, bindv6only=1, diag=diag_listener(7002, False)), - ) - self.assertEqual( - default.listeners, - { - (TCP, 7001): IPV6, - (TCP, 7002): BOTH, - (TCP, 7003): BOTH, - (TCP, 7004): BOTH, - (TCP, 7005): BOTH, - }, - ) - # Without diagnostics for a socket, net.ipv6.bindv6only decides. - self.assertEqual( - ipv6_only_default.listeners, - { - (TCP, 7001): IPV6, - (TCP, 7002): BOTH, - (TCP, 7003): IPV6, - (TCP, 7004): IPV6, - (TCP, 7005): IPV6, - }, - ) - - def test_malformed_socket_diagnostics_are_ignored(self): - sockets = (tcp_listener("::", 7101),) - valid = diag_listener(7101, ipv6_only=True) - short_attribute = bytearray(valid) - struct.pack_into("=H", short_attribute, NETLINK_HEADER.size + 72, 2) - long_attribute = bytearray(valid) - struct.pack_into("=H", long_attribute, NETLINK_HEADER.size + 72, 200) - for name, diag in ( - ("garbage", bytes(range(37))), - ("short header", NETLINK_HEADER.pack(8, SOCK_DIAG_BY_FAMILY, 0, 0, 0)), - ( - "overlong message", - NETLINK_HEADER.pack(4096, SOCK_DIAG_BY_FAMILY, 0, 0, 0), - ), - ("truncated message", valid[:40]), - ("short attribute", bytes(short_attribute)), - ("overlong attribute", bytes(long_attribute)), - ): - with self.subTest(name): - (announcement,) = self.announce(Snapshot(0, sockets, diag=diag)) - self.assertEqual(announcement.listeners, {(TCP, 7101): BOTH}) - - def test_udp_sockets_settle_before_they_are_announced(self): - sockets = ( - udp_socket("0.0.0.0", 7201, 10), - udp_socket("::", 7202, 11), - # The helper's own source port is never announced. - udp_socket("0.0.0.0", OWN_PORT, 12), - # Nor is a UDP socket bound to loopback. - udp_socket("127.0.0.1", 7203, 13), - ) - announcements = self.announce( - Snapshot(0, sockets), - Snapshot(UDP_SETTLE_MS - 1, sockets), - Snapshot(UDP_SETTLE_MS, sockets), - Snapshot(UDP_SETTLE_MS + 100, sockets, bindv6only=1), - ) - self.assertEqual(announcements[0].listeners, {}) - self.assertEqual(announcements[1].listeners, {}) - self.assertEqual( - announcements[2].listeners, {(UDP, 7201): IPV4, (UDP, 7202): BOTH} - ) - self.assertEqual( - announcements[3].listeners, {(UDP, 7201): IPV4, (UDP, 7202): IPV6} - ) - - def test_udp_ports_follow_the_identity_of_their_sockets(self): - server = udp_socket("0.0.0.0", 7301, 20) - replacement = udp_socket("0.0.0.0", 7301, 21) - connected = udp_socket("192.0.2.2", 7301, 21, ("198.51.100.7", 5353)) - client = udp_socket("192.0.2.2", 7301, 22, ("192.0.2.1", 53)) - timeline = ( - (0, (server,), None), - (UDP_SETTLE_MS, (server,), IPV4), - # Another socket on the port settles anew. - (UDP_SETTLE_MS + 100, (replacement,), None), - (2 * UDP_SETTLE_MS + 99, (replacement,), None), - (2 * UDP_SETTLE_MS + 100, (replacement,), IPV4), - # A server that connects its announced socket keeps the port. - (2 * UDP_SETTLE_MS + 200, (connected,), IPV4), - (5 * UDP_SETTLE_MS, (connected,), IPV4), - # A connected client that reuses the port never takes it over. - (5 * UDP_SETTLE_MS + 100, (client,), None), - (9 * UDP_SETTLE_MS, (client,), None), - ) - announcements = self.announce( - *(Snapshot(time_ms, sockets) for time_ms, sockets, _ in timeline) - ) - for (time_ms, _sockets, families), announcement in zip( - timeline, announcements, strict=True - ): - with self.subTest(time_ms=time_ms): - expected = {} if families is None else {(UDP, 7301): families} - self.assertEqual(announcement.listeners, expected) - - def test_udp_ports_announce_the_families_of_their_sockets(self): - ipv4 = udp_socket("0.0.0.0", 7401, 30) - ipv6 = udp_socket("::", 7401, 31) - ipv4_connected = udp_socket("192.0.2.2", 7401, 30, ("198.51.100.7", 4000)) - ipv6_connected = udp_socket("fd00::a00:2", 7401, 31, ("fd12::7", 4000)) - timeline = ( - (0, (ipv4, ipv6), None), - (UDP_SETTLE_MS, (ipv4, ipv6), BOTH), - (UDP_SETTLE_MS + 100, (ipv4_connected, ipv6), BOTH), - (UDP_SETTLE_MS + 200, (ipv4_connected, ipv6_connected), BOTH), - (UDP_SETTLE_MS + 300, (ipv6_connected,), IPV6), - (UDP_SETTLE_MS + 400, (), None), - ) - announcements = self.announce( - *( - Snapshot(time_ms, sockets, bindv6only=1) - for time_ms, sockets, _ in timeline - ) - ) - for (time_ms, _sockets, families), announcement in zip( - timeline, announcements, strict=True - ): - with self.subTest(time_ms=time_ms): - expected = {} if families is None else {(UDP, 7401): families} - self.assertEqual(announcement.listeners, expected) - - def test_announcements_carry_the_lowest_64_listeners(self): - sockets = tuple( - tcp_listener("0.0.0.0", port, inode=port) for port in range(2069, 1999, -1) - ) - (announcement,) = self.announce(Snapshot(0, sockets)) - self.assertTrue(announcement.truncated) - self.assertEqual( - announcement.order, tuple((TCP, port) for port in range(2000, 2064)) - ) - (complete,) = self.announce(Snapshot(0, sockets[-64:])) - self.assertFalse(complete.truncated) - self.assertEqual(len(complete.order), 64) - - -if __name__ == "__main__": - unittest.main() diff --git a/scripts/test_guest_time.py b/scripts/test_guest_time.py deleted file mode 100644 index 56ef24a53..000000000 --- a/scripts/test_guest_time.py +++ /dev/null @@ -1,943 +0,0 @@ -#!/usr/bin/env python3 -"""Tests for the guest time component, guest/common/nvx-time.c. - -The fixtures encode the guest-visible formats independently of the C decoders. -The restore packet (doc/design/5.5-restore-protocol.md) and the time sample -(doc/design/3.2-clock-and-time.md) follow the guide; the other fixtures, such -as the exhaustive check's lines and the state file, pin the formats that -nvx-time.c defines. -""" - -from __future__ import annotations - -import os -import re -import shutil -import struct -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path - -REPO_ROOT = Path(__file__).resolve().parents[1] -SOURCE = REPO_ROOT / "guest" / "common" / "nvx-time.c" - -DOWNTIME_UTC = 0x01 -MEMORY_TARGET = 0x02 -ACK_REQUIRED = 0x04 -TEST_HOOKS = 0x08 -UTC_NS = 1_790_841_600_123_456_789 -ENTROPY = bytes(range(64)) -PREVIOUS_ID = "ff" * 16 -THIRTY_DAYS_NS = 2_592_000 * 1_000_000_000 - - -def encode_packet( - *, - flags: int = 0, - online: int = 0, - ranges: tuple[tuple[int, int], ...] = (), - range_count: int | None = None, - generation: int = 1, - rate_deviation: int = 0, - downtime_ns: int = 0, - utc_ns: int = UTC_NS, - magic: bytes = b"OVR", - version: int = 4, - reserved: int = 0, - entropy: bytes = ENTROPY, -) -> bytes: - """Encode a restore packet v4 (guide: 5.5.5 "Restore Packet").""" - count = len(ranges) if range_count is None else range_count - header = magic + bytes((version, flags, online, count, reserved)) - header += struct.pack(" bytes: - """Encode a time sample (guide: 3.2.9 "Time Samples").""" - return struct.pack(" bool: - """Whether this process may switch from SCHED_IDLE back to SCHED_OTHER. - - That needs CAP_SYS_NICE, which the guest has, or an RLIMIT_NICE of at - least 20; containers and CI runners usually have neither. - """ - status = Path("/proc/self/status").read_text(encoding="utf-8") - capabilities = re.search(r"^CapEff:\s*([0-9a-f]+)$", status, re.MULTILINE) - if capabilities is not None and int(capabilities.group(1), 16) & (1 << 23): - return True - limits = Path("/proc/self/limits").read_text(encoding="utf-8") - nice = re.search(r"^Max nice priority\s+(\S+)", limits, re.MULTILINE) - return nice is not None and ( - nice.group(1) == "unlimited" or int(nice.group(1)) >= 20 - ) - - -TIMER_LIST_CPU = """\ -Tick Device: mode: 1 -Per CPU device: {cpu} -Clock Event Device: {device} - max_delta_ns: 1374389534634 - min_delta_ns: 1000 - mult: 6710886 - shift: 26 - mode: {state} - next_event: 1342000000 nsecs - set_next_event: lapic_next_event - shutdown: lapic_timer_shutdown - periodic: lapic_timer_set_periodic - oneshot: lapic_timer_set_oneshot - oneshot stopped: lapic_timer_shutdown - event_handler: {handler} - retries: 0 - -""" - -TIMER_LIST_BROADCAST = """\ -Tick Device: mode: 1 -Broadcast device -Clock Event Device: {device} -tick_broadcast_mask: 00 -tick_broadcast_oneshot_mask: 00 - -""" - -CPUINFO_CPU = """\ -processor\t: {cpu} -vendor_id\t: GenuineIntel -cpu family\t: 6 -model\t\t: 85 -cpu MHz\t\t: 2194.843 -flags\t\t: fpu vme {flags} -bugs\t\t: spectre_v1 - -""" - -GOOD_FLAGS = ( - "tsc msr pae constant_tsc rdtscp nonstop_tsc tsc_known_freq tsc_reliable " - "hypervisor arat x2apic" -) - - -@unittest.skipUnless(sys.platform.startswith("linux"), "nvx-time builds on Linux") -class GuestTimeTests(unittest.TestCase): - binary: Path - directory: tempfile.TemporaryDirectory[str] - - @classmethod - def setUpClass(cls) -> None: - compiler = shutil.which("cc") or shutil.which("gcc") - if compiler is None: - raise unittest.SkipTest("a C compiler is unavailable") - cls.directory = tempfile.TemporaryDirectory() - cls.binary = Path(cls.directory.name) / "nvx-time" - subprocess.run( - [ - compiler, - "-O1", - "-Wall", - "-Wextra", - "-Werror", - "-o", - str(cls.binary), - str(SOURCE), - ], - check=True, - capture_output=True, - timeout=120, - ) - - @classmethod - def tearDownClass(cls) -> None: - cls.directory.cleanup() - - def run_test(self, *arguments: str) -> str: - result = subprocess.run( - [str(self.binary), "test", *arguments], - capture_output=True, - text=True, - timeout=10, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - return result.stdout - - def fixture(self, name: str, content: bytes | str) -> str: - path = Path(self.directory.name) / name - if isinstance(content, str): - path.write_text(content, encoding="utf-8") - else: - path.write_bytes(content) - return str(path) - - def packet(self, packet: bytes, recorded: int = 0, previous: str = PREVIOUS_ID): - return self.run_test( - "packet", self.fixture("packet.bin", packet), str(recorded), previous - ).strip() - - def test_packet_v4_golden_vector(self): - packet = encode_packet( - flags=DOWNTIME_UTC | ACK_REQUIRED | TEST_HOOKS, - online=4, - generation=3, - rate_deviation=-13_107_200, - downtime_ns=30_000_000_000, - ) - - self.assertEqual(len(packet), 96) - self.assertEqual( - self.packet(packet, recorded=2), - "ok flags=13 online=4 ranges=0 generation=3 " - "rate_deviation=-13107200 frequency=13107200 " - "downtime_ns=30000000000 " - f"utc_ns={UTC_NS} generation_id={ENTROPY[:16].hex()}", - ) - - def test_musl_build_matches_the_reference_build(self): - musl = shutil.which("musl-gcc") - if musl is None: - self.skipTest("musl-gcc is unavailable") - binary = Path(self.directory.name) / "nvx-time-musl" - subprocess.run( - [musl, "-static", "-Os", "-s", "-Wall", "-Wextra", "-Werror"] - + ["-o", str(binary), str(SOURCE)], - check=True, - capture_output=True, - timeout=120, - ) - packet = self.fixture( - "musl-packet.bin", encode_packet(flags=ACK_REQUIRED, generation=2) - ) - arguments = ["test", "packet", packet, "1", PREVIOUS_ID] - result = subprocess.run( - [str(binary), *arguments], - capture_output=True, - text=True, - timeout=10, - check=False, - ) - - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, self.run_test(*arguments[1:])) - # musl has no sched_setscheduler(); the daemon must still reach - # SCHED_IDLE after a restore and leave it after the bound. - policy = 0 if can_leave_idle_priority() else 5 - for name, expected in ( - ("idle-priority", f"idle=5 normal={policy}\n"), - ("promote", f"promoted caller={policy} thread={policy}\n"), - ): - result = subprocess.run( - [str(binary), "test", name], - capture_output=True, - text=True, - timeout=10, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertTrue(result.stdout.startswith(expected), result.stdout) - self.assertTrue(self.run_test(name).startswith(expected)) - - def test_packet_memory_ranges_follow_the_header(self): - packet = encode_packet( - flags=MEMORY_TARGET, - ranges=((0x8000_0000, 0x0800_0000), (0x1_0000_0000, 0x1000_0000)), - ) - - self.assertEqual(len(packet), 32 + 2 * 16 + 64) - self.assertTrue( - self.packet(packet).endswith( - " range=2147483648:134217728 range=4294967296:268435456" - ) - ) - self.assertIn(" ranges=0 ", self.packet(encode_packet(flags=MEMORY_TARGET))) - - def test_packet_rejections_name_the_repair_code(self): - cases = { - "magic": encode_packet(magic=b"OVX"), - "version 3": encode_packet(version=3), - "reserved flag": encode_packet(flags=0x10), - "reserved byte": encode_packet(reserved=1), - "online count": encode_packet(online=3), - "ranges without target": encode_packet(range_count=1), - "generation zero": encode_packet(generation=0), - "rate above": encode_packet(rate_deviation=16_384_001), - "rate below": encode_packet(rate_deviation=-16_384_001), - "downtime": encode_packet(downtime_ns=THIRTY_DAYS_NS + 1), - "utc zero": encode_packet(utc_ns=0), - "empty range": encode_packet(flags=MEMORY_TARGET, ranges=((0, 0),)), - "range overflow": encode_packet( - flags=MEMORY_TARGET, ranges=((2**64 - 4096, 8192),) - ), - } - for name, packet in cases.items(): - with self.subTest(case=name): - self.assertTrue( - self.packet(packet).startswith("error G_REPAIR_PACKET "), - name, - ) - - def test_packet_bounds_are_inclusive(self): - for packet in ( - encode_packet(rate_deviation=16_384_000), - encode_packet(rate_deviation=-16_384_000), - encode_packet(downtime_ns=THIRTY_DAYS_NS), - encode_packet(online=8), - ): - with self.subTest(packet=packet[:32].hex()): - self.assertTrue(self.packet(packet).startswith("ok ")) - - def test_packet_generation_must_follow_the_recorded_generation(self): - self.assertEqual(self.packet(encode_packet(generation=5), recorded=5), "stale") - self.assertTrue( - self.packet(encode_packet(generation=7), recorded=5).startswith( - "error G_REPAIR_GENERATION " - ) - ) - self.assertTrue( - self.packet( - encode_packet(generation=6), recorded=5, previous=ENTROPY[:16].hex() - ).startswith("error G_REPAIR_GENERATION ") - ) - - def sample(self, sample: bytes, generation: int = 0) -> str: - return self.run_test( - "sample", self.fixture("sample.bin", sample), str(generation) - ).strip() - - def test_time_sample_layout(self): - self.assertEqual(len(encode_sample()), 16) - self.assertEqual( - self.sample(encode_sample(generation=4, flags=TEST_HOOKS), 4), - f"ok flags=8 generation=4 utc_ns={UTC_NS}", - ) - for name, sample in { - "version": encode_sample(version=2), - "flags": encode_sample(flags=0x01), - "reserved": encode_sample(reserved=1), - "utc": encode_sample(utc_ns=0), - "generation": encode_sample(generation=3), - }.items(): - with self.subTest(case=name): - self.assertTrue(self.sample(sample).startswith("error "), name) - - def test_utc_pairing_uses_the_bracket_midpoint(self): - self.assertEqual( - self.run_test("pair", "1000", "3000", "5000").strip(), - "theta_ns=3000 epsilon_ns=1000", - ) - self.assertEqual( - self.run_test("pair", "10000", "10400", "9000").strip(), - "theta_ns=-1200 epsilon_ns=200", - ) - - def test_restore_frequency_compensates_and_clamps(self): - self.assertEqual( - self.run_test("frequency", "13107200").strip(), - "frequency=-13107200 ppb=-200000", - ) - self.assertEqual( - self.run_test("frequency", "-40000000").strip(), - "frequency=32768000 ppb=500000", - ) - self.assertEqual( - self.run_test("frequency", "-65536").strip(), - "frequency=65536 ppb=1000", - ) - - def test_discipline_steps_at_128_ms_and_slews_below(self): - self.assertEqual( - self.run_test("correction", "127999999", "10002", "fast").strip(), - "slew modes=0x203d offset=127999999 status=0x2001 constant=4 " - "maxerror=128011 esterror=11", - ) - self.assertEqual( - self.run_test("correction", "-500", "20", "slow").strip(), - "slew modes=0x203d offset=-500 status=0x2001 constant=6 " - "maxerror=1 esterror=1", - ) - self.assertEqual( - self.run_test("correction", "128000000", "10", "fast").strip(), - "step modes=0x2100 sec=0 nsec=128000000", - ) - self.assertEqual( - self.run_test("correction", "-1128000000", "10", "slow").strip(), - "step modes=0x2100 sec=-2 nsec=872000000", - ) - - def test_watcher_rules_match_linux_6_18_messages(self): - messages = { - "tsc: Marking TSC unstable due to clocksource watchdog": ("G_TSC_UNSTABLE"), - "clocksource: timekeeping watchdog on CPU0: Marking clocksource " - "'tsc-early' as unstable because the skew is too large:": ( - "G_CLOCKSOURCE_UNSTABLE" - ), - "clocksource: Switched to clocksource refined-jiffies": ( - "G_CLOCKSOURCE_SWITCH" - ), - "clocksource: CPUs 1 ahead of CPU 0 for clocksource tsc.": ( - "G_CLOCKSOURCE_SKEW" - ), - "clocksource: CPUs 2-3 behind CPU 0 for clocksource tsc.": ( - "G_CLOCKSOURCE_SKEW" - ), - "TSC synchronization [CPU#0 -> CPU#1]:": "G_TSC_WARP", - "Measured 45 cycles TSC warp between CPUs, turning off TSC clock.": ( - "G_TSC_WARP" - ), - "TSC warped randomly between CPUs": "G_TSC_WARP", - "[Firmware Bug]: TSC ADJUST differs: CPU1 0 --> 100. Restoring": ( - "G_TSC_ADJUST" - ), - "rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:": "G_RCU_STALL", - "rcu: INFO: rcu_preempt self-detected stall on CPU": "G_RCU_STALL", - "rcu: INFO: rcu_preempt detected expedited stalls on CPUs/tasks: " - "{ 1-... }": "G_RCU_STALL", - "rcu: rcu_preempt kthread starved for 2603 jiffies! g345 f0x0": ( - "G_RCU_STARVED" - ), - "rcu: rcu_preempt kthread timer wakeup didn't happen for 2602 " - "jiffies! g345 f0x0": "G_RCU_STARVED", - "watchdog: BUG: soft lockup - CPU#0 stuck for 22s! [sh:123]": ( - "G_SOFT_LOCKUP" - ), - "watchdog: Watchdog detected hard LOCKUP on cpu 1": "G_HARD_LOCKUP", - "INFO: task kworker/0:1:12 blocked for more than 120 seconds.": ( - "G_HUNG_TASK" - ), - "unchecked MSR access error: RDMSR from 0x1ad at rIP: " - "0xffffffff816f11ee (__rdmsr_on_cpu+0x1e/0x30)": "G_UNCHECKED_MSR", - "[Firmware Bug]: CPU 1: APIC ID mismatch. CPUID: 0x0000 " - "APIC: 0x0001": "G_APIC_ID_MISMATCH", - "clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x1fa3": "none", - "Hyper-V: LAPIC Timer Frequency: 0x1e8480": "none", - "rcu: Hierarchical RCU implementation.": "none", - 'NVX-TIME-ABI-VIOLATION: v=1 code=G_UNCHECKED_MSR detail="unchecked ' - 'MSR access error"': "none", - } - output = self.run_test( - "kmsg", self.fixture("kmsg.txt", "\n".join(messages) + "\n") - ) - self.assertEqual(output.splitlines(), list(messages.values())) - - def boot_log(self, lines: list[str]) -> str: - return self.run_test( - "boot-log", self.fixture("boot.txt", "\n".join(lines) + "\n") - ).strip() - - BOOT_LOG = [ - "Hypervisor detected: Microsoft Hyper-V", - "Hyper-V: privilege flags low 0x8860, high 0x0, ext 0x0, hints 0x0, misc 0x100", - "Hyper-V: LAPIC Timer Frequency: 0x1e8480", - "tsc: Detected 2194.843 MHz processor", - "clocksource: Switched to clocksource tsc-early", - "clocksource: Switched to clocksource tsc", - ] - - def test_boot_log_requires_the_identity_and_a_final_switch_to_tsc(self): - self.assertEqual(self.boot_log(self.BOOT_LOG), "ok") - kvm_rate = [line.replace("0x1e8480", "0x989680") for line in self.BOOT_LOG] - self.assertEqual(self.boot_log(kvm_rate), "ok") - failures = { - "missing identity": self.BOOT_LOG[1:], - "wrong LAPIC rate": [ - line.replace("0x1e8480", "0x1e8481") for line in self.BOOT_LOG - ], - "last switch": [ - *self.BOOT_LOG, - "clocksource: Switched to clocksource refined-jiffies", - ], - "kvm-clock": [*self.BOOT_LOG, "kvm-clock: Using msrs 4b564d01"], - "refinement": [ - *self.BOOT_LOG, - "tsc: Refined TSC clocksource calibration: 2194.843 MHz", - ], - "lapic token": [ - *self.BOOT_LOG, - "APIC timer: using supplied frequency 200000000 Hz", - ], - "watcher record": [ - *self.BOOT_LOG, - "tsc: Marking TSC unstable due to clocksource watchdog", - ], - } - for name, lines in failures.items(): - with self.subTest(case=name): - self.assertTrue(self.boot_log(lines).startswith("fail "), name) - - def test_boot_log_reads_syslog_read_all_lines(self): - # SYSLOG_ACTION_READ_ALL prefixes every line with "" and, with - # printk timestamps, "[seconds.micros] ". - lines = [ - f"<6>[{index:5d}.{index * 7919 % 1000000:06d}] {line}" - for index, line in enumerate(self.BOOT_LOG) - ] - lines.insert(1, "<6>[ 0.123456] [drm] a bracketed message is kept") - own_event = ( - "<2>[ 3.000000] NVX-TIME-REPORT-VIOLATION: v=1 code=G_CONFORMANCE_C4 " - 'detail="forbidden record: kvm-clock: Using msrs"' - ) - - def syslog(text_lines: list[str]) -> str: - return self.run_test( - "syslog", self.fixture("syslog.txt", "\n".join(text_lines) + "\n") - ).strip() - - self.assertEqual(syslog(lines), "ok") - self.assertEqual(syslog([*lines, own_event]), "ok") - self.assertTrue( - syslog([*lines, "<6>[ 4.000000] kvm-clock: Using msrs"]).startswith( - "fail forbidden record: kvm-clock" - ) - ) - self.assertTrue(syslog(lines[2:]).startswith("fail no 'Hypervisor")) - - def test_boot_log_reports_a_kernel_wx_mapping_apart_from_c4(self): - # Linux 6.18's CONFIG_DEBUG_WX audit, as logged when the ITS - # mitigation's thunk pages are left writable and executable. - warning = [ - "<4>[ 0.412345] ------------[ cut here ]------------", - "<4>[ 0.412346] x86/mm: Found insecure W+X mapping at address " - "0xffffffffc0000000", - "<4>[ 0.412400] WARNING: CPU: 0 PID: 1 at " - "arch/x86/mm/dump_pagetables.c:246 note_wx+0x5a/0x70", - "<6>[ 0.412500] x86/mm: Checked W+X mappings: FAILED, 4 W+X pages " - "found.", - ] - lines = [f"<6>[ 0.100000] {line}" for line in self.BOOT_LOG] - - def syslog(text_lines: list[str]) -> list[str]: - return ( - self.run_test( - "syslog", - self.fixture("syslog.txt", "\n".join(text_lines) + "\n"), - ) - .strip() - .splitlines() - ) - - self.assertEqual( - syslog([*lines[:2], *warning, *lines[2:]]), - [ - "ok", - "wx x86/mm: Found insecure W+X mapping at address 0xffffffffc0000000", - ], - ) - self.assertEqual( - syslog( - [ - *lines, - "<6>[ 0.412500] x86/mm: Checked W+X mappings: passed, " - "no W+X pages found.", - ] - ), - ["ok"], - ) - failed = syslog([*warning, *lines[1:]]) - self.assertEqual(len(failed), 2) - self.assertTrue(failed[0].startswith("fail no 'Hypervisor")) - self.assertTrue(failed[1].startswith("wx x86/mm: Found insecure W+X")) - own_event = ( - "<2>[ 3.000000] NVX-TIME-REPORT-VIOLATION: v=1 code=G_KERNEL_WX " - 'detail="x86/mm: Found insecure W+X mapping at address 0x0"' - ) - self.assertEqual(syslog([*lines, own_event]), ["ok"]) - - def test_daemon_lines_start_on_a_fresh_console_line(self): - # The daemon prints violation events while a shell may be in the - # middle of a line (its prompt, for example). - line = "NVX-TIME-ABI-VIOLATION: v=1 code=G_RCU_STALL source=watcher\n" - self.assertEqual(self.run_test("console", line, "sync"), line) - self.assertEqual(self.run_test("console", line, "async"), "\n" + line) - - def test_exhaustive_leaf_rules(self): - # X1 and X2: a leaf reads zero or the highest basic leaf's result, - # except the explicit zero leaves, which read zero. - out_of_range = ("0x00000d0b", "0x00000001", "0x00000002", "0x00000003") - zero = ("0", "0", "0", "0") - - def leaf(number: str, value: tuple[str, ...]) -> str: - return self.run_test( - "exhaustive-leaf", number, *value, *out_of_range - ).strip() - - self.assertEqual(leaf("0x40000006", zero), "pass") - self.assertEqual(leaf("0x40000010", out_of_range), "pass") - self.assertEqual(leaf("0x40000100", zero), "pass") - self.assertEqual(leaf("0x40000100", out_of_range), "pass") - explicit = leaf("0x40000081", out_of_range) - self.assertTrue(explicit.startswith("fail leaf 0x40000081 is "), explicit) - self.assertTrue(explicit.endswith("expected zero"), explicit) - # KVM's signature leaf moved to a higher base. - kvm = ("0x40000101", "0x4b4d564b", "0x564b4d56", "0x0000004d") - signature = leaf("0x40000100", kvm) - self.assertIn('(signature "KVMKVMKVM...")', signature) - self.assertTrue(signature.endswith("or the highest basic leaf"), signature) - - def test_exhaustive_report_format(self): - # The exhaustive check prints one line per check and CPU, with an - # escaped detail, then a summary. - line = re.compile( - r"^NVX-TIME-ABI-EXHAUSTIVE: v=1 check=X[1-6] cpu=\d+ " - r'status=(pass|fail) detail="(?:[^"\\]|\\.)*"$' - ) - passed = self.run_test( - "exhaustive-report", "X3", "2", "pass", "ok" - ).splitlines() - self.assertEqual( - passed, - [ - 'NVX-TIME-ABI-EXHAUSTIVE: v=1 check=X3 cpu=2 status=pass detail="ok"', - "failures=0", - ], - ) - failed = self.run_test( - "exhaustive-report", - "X2", - "7", - "fail", - 'base 0x40000100 "KVMKVMKVM" \\ x\ty', - ).splitlines() - self.assertRegex(failed[0], line) - self.assertIn(r'detail="base 0x40000100 \"KVMKVMKVM\" \\ x\x09y"', failed[0]) - self.assertEqual(failed[1], "failures=1") - self.assertEqual( - self.run_test("exhaustive-summary", "4", "0").splitlines(), - ["NVX-TIME-ABI-EXHAUSTIVE: v=1 status=ok cpus=4 failures=0", "exit=0"], - ) - self.assertEqual( - self.run_test("exhaustive-summary", "8", "3").splitlines(), - ["NVX-TIME-ABI-EXHAUSTIVE: v=1 status=fail cpus=8 failures=3", "exit=1"], - ) - - def timer_list( - self, - sections: list[tuple[int, str, int, str, int]], - broadcast: str = "", - cpus: str = "0-1", - ) -> str: - text = TIMER_LIST_BROADCAST.format(device=broadcast) - for cpu, device, state, handler, mode in sections: - text += TIMER_LIST_CPU.format( - cpu=cpu, device=device, state=state, handler=handler - ).replace("Tick Device: mode: 1", f"Tick Device: mode: {mode}") - return self.run_test( - "timer-list", self.fixture("timer_list.txt", text), cpus - ).strip() - - def test_timer_list_requires_one_shot_lapic_ticks(self): - good = [ - (0, "lapic", 3, "hrtimer_interrupt", 1), - (1, "lapic", 4, "hrtimer_interrupt", 1), - ] - self.assertEqual(self.timer_list(good), "ok") - failures = { - "deadline": [good[0], (1, "lapic-deadline", 3, "hrtimer_interrupt", 1)], - "periodic tick": [good[0], (1, "lapic", 3, "hrtimer_interrupt", 0)], - "periodic state": [good[0], (1, "lapic", 2, "hrtimer_interrupt", 1)], - "handler": [good[0], (1, "lapic", 3, "tick_handle_periodic", 1)], - "missing cpu": [good[0]], - } - for name, sections in failures.items(): - with self.subTest(case=name): - self.assertTrue(self.timer_list(sections).startswith("fail ")) - self.assertTrue(self.timer_list(good, broadcast="pit").startswith("fail ")) - - def test_cpuinfo_flags_and_rate(self): - def cpuinfo(flags: str, cpus: int = 2) -> str: - text = "".join( - CPUINFO_CPU.format(cpu=cpu, flags=flags) for cpu in range(cpus) - ) - return self.run_test( - "cpuinfo", self.fixture("cpuinfo.txt", text), "0-1" - ).strip() - - self.assertEqual(cpuinfo(GOOD_FLAGS), "ok cpu0_khz=2194843 cpu1_khz=2194843") - for name, flags in { - "tsc_reliable": GOOD_FLAGS.replace("tsc_reliable ", ""), - "deadline": GOOD_FLAGS + " tsc_deadline_timer", - "adjust": GOOD_FLAGS + " tsc_adjust", - }.items(): - with self.subTest(case=name): - self.assertIn("code=G_CONFORMANCE_C5", cpuinfo(flags)) - self.assertIn("code=G_CONFORMANCE_C5", cpuinfo(GOOD_FLAGS, cpus=1)) - - def test_command_line_rejects_clock_workarounds(self): - accepted = "earlycon=xe9 console=hvc0 quiet clocksource=tsc tsc=reliable" - self.assertEqual(self.run_test("cmdline", accepted).strip(), "ok") - for token in ( - "tsc_early_khz=2194843", - "lapic_timer_hz=200000000", - "notsc", - "nolapic", - "nolapic_timer", - "tsc=unstable", - "hpet=force", - "clocksource=kvm-clock", - ): - with self.subTest(token=token): - self.assertIn( - "code=G_CONFORMANCE_C9", - self.run_test("cmdline", f"{accepted} {token}"), - ) - - def test_clocksource_must_be_tsc_with_only_jiffies_fallbacks(self): - self.assertEqual(self.run_test("clocksource", "tsc\n", "tsc \n").strip(), "ok") - self.assertEqual( - self.run_test( - "clocksource", "tsc\n", "tsc refined-jiffies jiffies\n" - ).strip(), - "ok", - ) - for current, available in ( - ("tsc-early\n", "tsc-early \n"), - ("tsc\n", "tsc kvm-clock \n"), - ("refined-jiffies\n", "refined-jiffies \n"), - ): - with self.subTest(current=current, available=available): - self.assertIn( - "code=G_CONFORMANCE_C6", - self.run_test("clocksource", current, available), - ) - - def test_violation_event_format_escaping_and_limit(self): - line = self.run_test( - "event", - "G_UNCHECKED_MSR", - "watcher", - "runtime", - "3", - "123456789", - 'unchecked "MSR"\\ access\x01', - ) - self.assertEqual( - line, - "NVX-TIME-ABI-VIOLATION: v=1 code=G_UNCHECKED_MSR source=watcher " - "phase=runtime generation=3 boottime_ns=123456789 " - 'detail="unchecked \\"MSR\\"\\\\ access\\x01"\n', - ) - long_line = self.run_test( - "event", "G_HUNG_TASK", "watcher", "runtime", "0", "1", "\x02" * 400 - ) - self.assertLessEqual(len(long_line), 512) - self.assertTrue(long_line.endswith('\\x02"\n')) - - def test_state_file_round_trip(self): - # The fixture lists every key, in the order that nvx-time writes them. - text = ( - "version=1\ngeneration=2\nboot_status=ok\ncapture_status=ok\n" - "restore_status=pending\nboot_cpus=8\ncapture_cpus=8\nrestore_cpus=0\n" - "boot_elapsed_us=2400\ncapture_elapsed_us=310\nrestore_elapsed_us=1830\n" - "boot_cpu_us=1900\ncapture_cpu_us=290\nrestore_cpu_us=640\n" - "capture_generation=1\nrestore_generation=2\ntsc_hz=2194843000\n" - "lapic_hz=200000000\ndiscontinuities=3\nlast_discontinuity=step\n" - "last_step_ns=-200000000\nlast_step_realtime_ns=1790841600000000000\n" - "last_downtime_ns=31000000000\nlast_downtime_source=utc\n" - "synchronized=1\noffset_ns=-42\nuncertainty_ns=1800\n" - "frequency_ppb=-1200\nsamples=17\nrejected_samples=1\n" - "last_sample_error=none\nviolations=0\n" - ) - self.assertEqual(self.run_test("state", self.fixture("state.txt", text)), text) - # Report-only mode adds the failure counts, reserved for it, after the - # generations, and a failed check's status is fail. - report = text.replace("restore_status=pending", "restore_status=fail").replace( - "restore_generation=2\n", - "restore_generation=2\nboot_failures=0\ncapture_failures=0\n" - "restore_failures=3\n", - ) - self.assertEqual( - self.run_test("state", self.fixture("state.txt", report)), report - ) - # A fresh state is the pending boot check; the capture and restore keys - # are absent until those checks first run. - self.assertTrue( - self.run_test("state", self.fixture("state.txt", "version=1\n")).startswith( - "version=1\ngeneration=0\nboot_status=pending\nboot_cpus=0\n" - "boot_elapsed_us=0\nboot_cpu_us=0\ntsc_hz=0\nlapic_hz=0\n" - ) - ) - self.assertEqual( - self.run_test( - "state", self.fixture("state.txt", "unknown=x\nsamples=bad\n") - ).strip(), - "error", - ) - - def status(self, text: str, mode: str = "enforcing") -> list[str]: - return self.run_test( - "status", self.fixture("state.txt", text), mode - ).splitlines() - - def test_status_prints_every_recorded_check_and_the_runtime_line(self): - state = ( - "version=1\ngeneration=2\nboot_status=ok\ncapture_status=ok\n" - "restore_status=ok\nboot_cpus=8\ncapture_cpus=8\nrestore_cpus=4\n" - "boot_elapsed_us=2400\ncapture_elapsed_us=310\nrestore_elapsed_us=1830\n" - "boot_cpu_us=1900\ncapture_cpu_us=290\nrestore_cpu_us=640\n" - "capture_generation=1\nrestore_generation=2\ntsc_hz=2194843000\n" - "lapic_hz=200000000\ndiscontinuities=2\nsynchronized=1\n" - "offset_ns=-42\nuncertainty_ns=1800\nrejected_samples=2\n" - "last_sample_error=G_SAMPLE_UNCERTAIN\n" - ) - rates = "tsc_hz=2194843000 lapic_hz=200000000" - # One line per recorded phase, each with the generation its check ran - # in and its wall and CPU times, then the runtime line; the hook adds - # whether status exits 0. - self.assertEqual( - self.status(state), - [ - f"NVX-TIME-ABI: v=1 phase=boot status=ok cpus=8 {rates} " - "generation=0 elapsed_us=2400 cpu_us=1900", - f"NVX-TIME-ABI: v=1 phase=capture status=ok cpus=8 {rates} " - "generation=1 elapsed_us=310 cpu_us=290", - f"NVX-TIME-ABI: v=1 phase=restore status=ok cpus=4 {rates} " - "generation=2 elapsed_us=1830 cpu_us=640", - "NVX-TIME-ABI: v=1 phase=runtime status=synchronized generation=2 " - "discontinuities=2 offset_ns=-42 uncertainty_ns=1800 " - "rejected_samples=2 last_sample_error=G_SAMPLE_UNCERTAIN", - "ok=1", - ], - ) - # A check still pending after the wait has neither elapsed_us nor - # cpu_us and fails the exit status. A cold boot has a boot line only, - # and a state without a boot record counts as a pending boot check. - pending = state.replace("restore_status=ok", "restore_status=pending") - self.assertEqual( - self.status(pending)[2::2], - [ - f"NVX-TIME-ABI: v=1 phase=restore status=pending cpus=4 {rates} " - "generation=2", - "ok=0", - ], - ) - boot_pending = ( - "NVX-TIME-ABI: v=1 phase=boot status=pending cpus=0 tsc_hz=0 " - "lapic_hz=0 generation=0" - ) - for text in ("version=1\ngeneration=0\nboot_status=pending\n", "version=1\n"): - lines = self.status(text) - self.assertEqual(lines[0], boot_pending, text) - self.assertTrue(lines[1].startswith("NVX-TIME-ABI: v=1 phase=runtime ")) - self.assertEqual(lines[2:], ["ok=0"], text) - # Report-only mode marks every line and appends each check's failure - # count from the state file, where a failed check's status is fail. - report = self.status( - state.replace("restore_status=ok", "restore_status=fail").replace( - "restore_generation=2\n", - "restore_generation=2\nboot_failures=0\nrestore_failures=3\n", - ), - "report-only", - ) - self.assertEqual( - report[0], - f"NVX-TIME-REPORT: v=1 phase=boot status=ok cpus=8 {rates} " - "generation=0 elapsed_us=2400 cpu_us=1900 failures=0", - ) - self.assertEqual( - report[2], - f"NVX-TIME-REPORT: v=1 phase=restore status=fail cpus=4 {rates} " - "generation=2 elapsed_us=1830 cpu_us=640 failures=3", - ) - self.assertTrue(report[3].startswith("NVX-TIME-REPORT: v=1 phase=runtime ")) - self.assertEqual(report[4], "ok=0") - - def test_deferred_work_starts_150_ms_late_and_is_promoted_100_ms_later(self): - # Guest repair step 7 and the asynchronous boot checks start 150 ms - # after the acknowledgement or the boot step and leave SCHED_IDLE - # 100 ms after they start. - self.assertEqual( - self.run_test("deferral"), "deferred_start_ms=150 idle_bound_ms=100\n" - ) - - def test_restore_cpu_time_is_the_deferred_work_with_the_daemons_fork(self): - # The worker's repair step 7 CPU time adds the daemon's preparation - # before the fork, the fork's cost to the daemon, which arrives on a - # pipe after the worker's copy of the timing was taken, and its own. - fields = dict( - field.split("=") - for field in self.run_test("deferred-restore-cpu", "20", "30", "10").split() - ) - self.assertGreaterEqual(int(fields["prior_ms"]), 20) - self.assertLess(int(fields["prior_ms"]), 30) - self.assertGreaterEqual(int(fields["total_ms"]), 60) - self.assertLess(int(fields["total_ms"]), 90) - - def restore_record(self, text: str) -> str: - return self.run_test( - "restore-record", self.fixture("restore.txt", text) - ).strip() - - def test_restore_record_names_the_cpus_that_restore_finish_onlined(self): - # restore-finish records the CPUs that activation brought online. - self.assertEqual( - self.restore_record("generation=3\nnew_cpus=4-6,9\n"), - "generation=3 new_cpus=4,5,6,9 count=4", - ) - self.assertEqual( - self.restore_record("generation=3\nnew_cpus=none\n"), - "generation=3 new_cpus=none count=0", - ) - self.assertEqual( - self.restore_record("generation=3\n"), "generation=3 new_cpus=none count=0" - ) - for text in ( - "", - "new_cpus=4\ngeneration=3\n", - "generation=3\nnew_cpus=7-4\n", - "generation=3\nnew_cpus=64\n", - ): - self.assertEqual(self.restore_record(text), "error", text) - - def test_parallel_checks_merge_every_failure(self): - if sys.platform != "linux": - self.skipTest("CPU affinity is Linux-only") - allowed: list[int] = sorted(os.sched_getaffinity(0)) - # Three side jobs fail 1, 2, and 3 times and the foreground once, - # whether one CPU runs them in the caller or each has a thread. - for size in sorted({1, min(2, len(allowed)), min(4, len(allowed))}): - output = self.run_test("parallel", ",".join(map(str, allowed[:size]))) - self.assertTrue(output.endswith("failures=7\n"), output) - self.assertEqual(output.count("code=G_CONFORMANCE_C4"), 6, output) - self.assertEqual(output.count("code=G_CONFORMANCE_C9"), 1, output) - # A CPU the checks cannot run on fails C1 in its job, and each such - # job's failure counts. - unusable = [cpu for cpu in range(64) if cpu not in allowed] - if not unusable: - self.skipTest("every CPU is usable") - output = self.run_test("parallel", f"{allowed[0]},{unusable[0]}") - self.assertTrue(output.endswith("failures=8\n"), output) - self.assertIn(f"cannot run on cpu {unusable[0]}", output) - if len(unusable) < 2: - self.skipTest("only one CPU is unusable") - cpus = ",".join(map(str, [allowed[0], *unusable[:2]])) - output = self.run_test("parallel", cpus) - self.assertTrue(output.endswith("failures=9\n"), output) - for cpu in unusable[:2]: - self.assertIn(f"cannot run on cpu {cpu}: ", output) - - def test_idle_work_continues_at_normal_priority_after_the_bound(self): - # The asynchronous checks leave SCHED_IDLE (5) for SCHED_OTHER (0) - # 100 ms after they start, every thread included; checks that end - # first stop the promoter without waiting for the bound. - policy = 0 if can_leave_idle_priority() else 5 - promoted, stopped = self.run_test("promote").splitlines() - self.assertEqual(promoted, f"promoted caller={policy} thread={policy}") - fields = dict(field.split("=") for field in stopped.split()[1:]) - self.assertEqual(fields["caller"], "5") - self.assertLess(int(fields["stop_ms"]), 1000) - - -if __name__ == "__main__": - unittest.main() diff --git a/scripts/test_managed_agent.py b/scripts/test_managed_agent.py deleted file mode 100644 index 794ff2ffd..000000000 --- a/scripts/test_managed_agent.py +++ /dev/null @@ -1,795 +0,0 @@ -#!/usr/bin/env python3 -"""Compile the existing guest decoder on Linux and test real wire inputs.""" - -import shutil -import struct -import subprocess -import sys -import tempfile -import unittest -from collections.abc import Sequence -from pathlib import Path - - -@unittest.skipUnless(sys.platform.startswith("linux"), "guest decoder requires Linux") -class ManagedAgentDecoderTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - compiler = shutil.which("cc") - if compiler is None: - raise unittest.SkipTest("guest decoder requires the guest build C compiler") - cls.temporary = tempfile.TemporaryDirectory(prefix="nvx-agent-decoder-") - cls.addClassCleanup(cls.temporary.cleanup) - cls.root = Path(cls.temporary.name) - cls.executable = cls.root / "decoder" - cls.fault_executable = cls.root / "fault-injection" - harness = cls.root / "decoder.c" - harness.write_text( - """ -#define main nvx_agent_main -#include "nvx-managed-agent.c" -#undef main -int main(int argc, char **argv) { - unsigned char bytes[65536]; - uint32_t timeout_ms = 0; - char **arguments = NULL; - struct exec_config config; - memset(&config, 0xa5, sizeof(config)); - if (argc > 1 && strcmp(argv[1], "--exec-config-fd") == 0) - return launch_workload(argc, argv); - int launch = argc == 3 && strcmp(argv[1], "--launch") == 0; - if (argc != 2 && !launch) return 2; - FILE *file = fopen(argv[launch ? 2 : 1], "rb"); - if (file == NULL) return 2; - size_t length = fread(bytes, 1, sizeof(bytes), file); - if (ferror(file)) return 2; - fclose(file); - int result = decode_exec_payload( - bytes, (uint32_t)length, &timeout_ms, &arguments, &config); - if (result == 0 && launch) { - int fd = create_exec_config_fd(&config, 0); - if (fd < 0) return 2; - int seals = F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; - if ((fcntl(fd, F_GET_SEALS) & seals) != seals) return 2; - char fd_text[32]; - snprintf(fd_text, sizeof(fd_text), "%d", fd); - char *helper_arguments[MAX_ARGUMENTS + 5] = { - argv[0], "--exec-config-fd", fd_text, "--" - }; - int count = 0; - while (arguments[count] != NULL) { - helper_arguments[count + 4] = arguments[count]; - count++; - } - helper_arguments[count + 4] = NULL; - return launch_workload(count + 4, helper_arguments); - } - if (result == 0) { - free_arguments(arguments); - free_exec_config(&config); - } - return result == 0 ? 0 : 1; -} -""", - encoding="utf-8", - ) - guest = Path(__file__).resolve().parent.parent / "guest" / "common" - subprocess.run( - [ - compiler, - "-D_GNU_SOURCE", - "-std=c11", - "-I", - str(guest), - str(harness), - "-o", - str(cls.executable), - ], - check=True, - capture_output=True, - timeout=30, - ) - fault_harness = cls.root / "fault-injection.c" - fault_cgroup_root = cls.root / "cgroup" - fault_exec_cgroup = fault_cgroup_root / "nvx-exec" - fault_exec_cgroup.mkdir(parents=True) - (fault_exec_cgroup / "cgroup.procs").write_text("", encoding="utf-8") - fault_harness.write_text( - """ -#define main nvx_agent_main -#include "nvx-managed-agent.c" -#undef main - -static int barrier_reader = -1; - -ssize_t __real_write(int, const void *, size_t); -ssize_t __wrap_write(int fd, const void *buffer, size_t length) { - if (barrier_reader >= 0 && length == 6 && - memcmp(buffer, "start\\n", length) == 0) { - close(barrier_reader); - barrier_reader = -1; - } - return __real_write(fd, buffer, length); -} - -int __real_setenv(const char *, const char *, int); -int __wrap_setenv(const char *name, const char *value, int overwrite) { - const char *failure = getenv("NVX_TEST_FAIL_SETENV"); - if (failure != NULL && strcmp(name, failure) == 0) { - errno = ENOMEM; - return -1; - } - return __real_setenv(name, value, overwrite); -} - -int __wrap_execv(const char *path, char *const argv[]) { - const char *marker = getenv("NVX_TEST_EXEC_MARKER"); - (void)path; - (void)argv; - if (marker != NULL) { - int fd = open(marker, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd >= 0) close(fd); - } - errno = ENOENT; - return -1; -} - -int __wrap_execvp(const char *file, char *const argv[]) { - return __wrap_execv(file, argv); -} - -int main(int argc, char **argv) { - if (argc == 3 && strcmp(argv[1], "--release-closing-barrier") == 0) { - barrier_reader = open(argv[2], O_RDONLY | O_CLOEXEC | O_NONBLOCK); - if (barrier_reader < 0) return 2; - errno = 0; - int result = release_container_barrier(argv[2], -1); - int status = errno; - if (barrier_reader >= 0) close(barrier_reader); - return result == -1 && status == EPIPE ? 0 : 1; - } - if (argc == 3 && strcmp(argv[1], "--release-orphaned-barrier") == 0) { - pid_t child = fork(); - if (child < 0) return 2; - if (child == 0) _exit(125); - int result = release_container_barrier(argv[2], child); - int status = 0; - if (waitpid(child, &status, 0) != child) return 2; - return result == 1 && WIFEXITED(status) ? 0 : 1; - } - if (argc == 3 && strcmp(argv[1], "--release-live-barrier") == 0) { - pid_t child = fork(); - if (child < 0) return 2; - if (child == 0) { - char message[6]; - int fd = open(argv[2], O_RDONLY | O_CLOEXEC); - if (fd < 0 || read(fd, message, sizeof(message)) != sizeof(message)) - _exit(125); - close(fd); - _exit(memcmp(message, "start\\n", sizeof(message)) == 0 ? 0 : 125); - } - int result = release_container_barrier(argv[2], child); - int status = 0; - if (waitpid(child, &status, 0) != child) return 2; - return result == 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0 ? 0 : 1; - } - if (argc != 3) return 2; - struct agent_config config = { - .rootfs = "/rootfs", - .hostname = "sandbox", - .uid = "1000", - .gid = "1000", - .user = "workload", - .home = "/home/workload", - .direct = 0, - }; - char *workload[] = {"/bin/true", NULL}; - if (strcmp(argv[1], "--sandbox") == 0) - exec_sandbox(&config, "/unused", argv[2], workload); - if (strcmp(argv[1], "--direct") == 0) - exec_direct(&config, argv[2], workload); - return 2; -} -""", - encoding="utf-8", - ) - subprocess.run( - [ - compiler, - f'-DCGROUP_ROOT="{fault_cgroup_root}"', - "-D_GNU_SOURCE", - "-std=c11", - "-I", - str(guest), - str(fault_harness), - "-Wl,--wrap=write", - "-Wl,--wrap=setenv", - "-Wl,--wrap=execv", - "-Wl,--wrap=execvp", - "-o", - str(cls.fault_executable), - ], - check=True, - capture_output=True, - timeout=30, - ) - - def decode( - self, - environment: tuple[bytes, ...], - *, - timeout: int = 0, - inherit: bool = False, - ) -> int: - argument = b"/bin/true" - flags = 2 | (4 if inherit else 0) - payload = struct.pack(" subprocess.CompletedProcess[bytes]: - entries = () if environment is None else environment - flags = 1 | (2 if environment is not None else 0) | (4 if inherit else 0) - payload = struct.pack( - " length) return 2; - if (handle_app_request(&session, &config, &mappings, &request) < 0) return 4; - } - printf("%u %u %d\\n", mappings.expected, mappings.applied, mappings.failed); - return 0; -} -""", - encoding="utf-8", - ) - guest = Path(__file__).resolve().parent.parent / "guest" / "common" - subprocess.run( - [ - compiler, - f'-DHOSTFS_DIR="{cls.root / "hostfs"}"', - "-D_GNU_SOURCE", - "-std=c11", - "-I", - str(guest), - str(harness), - "-Wl,--wrap=mount", - "-o", - str(cls.executable), - ], - check=True, - capture_output=True, - timeout=30, - ) - - def setUp(self): - self.case = Path(tempfile.mkdtemp(prefix="case-", dir=self.root)) - export = self.root / "hostfs" / "root" - shutil.rmtree(export, ignore_errors=True) - (export / "0" / "src").mkdir(parents=True) - (export / "0" / "notes.txt").write_text("notes", encoding="utf-8") - (export / "1").mkdir() - self.log = self.case / "mounts.log" - - @staticmethod - def maps( - first: int, - entries: Sequence[tuple[str | bytes, str | bytes, int]], - count: int | None = None, - ) -> bytes: - payload = struct.pack(" tuple[subprocess.CompletedProcess[bytes], list[tuple[int, int, int, bytes]]]: - source = self.case / "requests.bin" - source.write_bytes( - b"".join( - struct.pack(" list[tuple[str, str, int]]: - if not self.log.exists(): - return [] - return [ - (source, target, int(flags)) - for source, target, flags in ( - line.split("|") for line in self.log.read_text().splitlines() - ) - ] - - def test_mappings_mount_in_order_and_unblock_workloads(self): - export = self.root / "hostfs" / "root" - src = str(self.case / "guest" / "src") - notes = str(self.case / "guest" / "src" / "notes.txt") - out = str(self.case / "guest" / "out") - result, answers = self.run_agent( - # A stale token of an older host is ignored. - "quiet nvx_map=0,/stale,rw nvx_maps=3", - [ - (APP_EXEC, b""), - ( - APP_MAPS, - self.maps(0, [("0/src", src, 1), ("0/notes.txt", notes, 0)]), - ), - (APP_EXEC, b""), - (APP_MAPS, self.maps(2, [("1", out, 0)])), - # An empty request is malformed, so it fails only once the mappings are complete. - (APP_EXEC, b""), - ], - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, b"3 3 0\n") - self.assertEqual( - [(kind, status, payload) for kind, _, status, payload in answers], - [ - (APP_ERROR, 11, b"mappings-incomplete"), - (APP_READY, 0, b""), - (APP_ERROR, 11, b"mappings-incomplete"), - (APP_READY, 0, b""), - (APP_ERROR, 22, b"invalid-request"), - ], - ) - self.assertEqual( - self.mounts(), - [ - (str(export / "0" / "src"), src, MS_BIND), - ("-", src, REMOUNT | MS_RDONLY), - (str(export / "0" / "notes.txt"), notes, MS_BIND), - ("-", notes, REMOUNT), - (str(export / "1"), out, MS_BIND), - ("-", out, REMOUNT), - ], - ) - self.assertTrue(Path(src).is_dir()) - self.assertTrue(Path(notes).is_file()) - self.assertTrue(Path(out).is_dir()) - - def test_entries_beyond_the_announcement_or_out_of_order_are_refused(self): - target = str(self.case / "guest" / "src") - entry = [("0/src", target, 0)] - result, answers = self.run_agent( - "nvx_maps=1", - [ - (APP_MAPS, self.maps(1, entry)), - (APP_MAPS, self.maps(0, entry * 2)), - (APP_MAPS, self.maps(0, entry)), - (APP_MAPS, self.maps(1, entry)), - ], - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, b"1 1 0\n") - self.assertEqual( - [(kind, status) for kind, _, status, _ in answers], - [(APP_ERROR, 22), (APP_ERROR, 22), (APP_READY, 0), (APP_ERROR, 22)], - ) - self.assertEqual(len(self.mounts()), 2) - - def test_malformed_tables_mount_nothing(self): - target = str(self.case / "guest" / "x") - good = ("0/src", target, 0) - malformed = [ - self.maps(0, [good, ("0/../1", target, 0)]), - self.maps(0, [good, ("/0/src", target, 0)]), - self.maps(0, [good, ("0/src", "relative", 0)]), - self.maps(0, [good, (".", target, 0)]), - self.maps(0, [good, ("0//src", target, 0)]), - self.maps(0, [good, (b"0/s\0rc", target, 0)]), - self.maps(0, [good, ("0/src", target, 2)]), - self.maps(0, [good, ("", target, 0)]), - self.maps(0, [good]) + b"\0", - self.maps(0, [good], count=2), - self.maps(0, [], count=0), - b"\0\0\0", - ] - result, answers = self.run_agent( - "nvx_maps=2", [(APP_MAPS, payload) for payload in malformed] - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, b"2 0 0\n") - self.assertEqual( - [(kind, status, payload) for kind, _, status, payload in answers], - [(APP_ERROR, 22, b"invalid-request")] * len(malformed), - ) - self.assertEqual(self.mounts(), []) - - def test_without_an_announcement_tables_are_refused(self): - target = str(self.case / "guest" / "src") - result, answers = self.run_agent( - "nvx_map=0,/w,rw", - [(APP_MAPS, self.maps(0, [("0/src", target, 0)])), (APP_EXEC, b"")], - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, b"0 0 0\n") - self.assertEqual( - [(kind, status, payload) for kind, _, status, payload in answers], - [(APP_ERROR, 22, b"invalid-request"), (APP_ERROR, 22, b"invalid-request")], - ) - self.assertEqual(self.mounts(), []) - - def test_a_failed_mount_keeps_refusing_workloads(self): - target = str(self.case / "guest" / "missing") - result, answers = self.run_agent( - "nvx_maps=2", - [ - (APP_MAPS, self.maps(0, [("0/missing", target, 0)])), - (APP_EXEC, b""), - (APP_MAPS, self.maps(0, [("0/src", target, 0)])), - ], - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, b"2 0 1\n") - self.assertEqual( - [(kind, status, payload) for kind, _, status, payload in answers], - [ - (APP_ERROR, 2, b"mapping-failed"), - (APP_ERROR, 11, b"mappings-incomplete"), - (APP_ERROR, 22, b"invalid-request"), - ], - ) - - def test_mapping_counts_are_validated(self): - for command_line in [ - "nvx_maps=0", - "nvx_maps=", - "nvx_maps=4097", - "nvx_maps=99999999999", - "nvx_maps=1x", - "nvx_maps=1 nvx_maps=1", - ]: - result, _ = self.run_agent(command_line, []) - self.assertEqual(result.returncode, 3, command_line) - result, _ = self.run_agent("nvx_maps=4096", []) - self.assertEqual(result.stdout, b"4096 0 0\n") - - def test_features_announce_the_mapping_table_but_not_retired_bit_one(self): - _, answers = self.run_agent("", [(APP_FEATURES, b"")]) - self.assertEqual(len(answers), 1) - kind, _, status, payload = answers[0] - self.assertEqual((kind, status), (APP_READY, 0)) - (features,) = struct.unpack(" set[str]: match.group(1): { path.replace("**", "example").replace("*", "example") for path in re.findall(r"'([^']+)'", match.group(2)) + if not path.startswith("!") } for match in re.finditer( r"^\s+([A-Z_]+)_INPUT_HASH: \$\{\{ hashFiles\((.+)\) \}\}$", @@ -5316,6 +5328,7 @@ def test_ci_takes_rust_toolchain_from_repository_pin(self): setup = (workflows / "copilot-setup-steps.yml").read_text(encoding="utf-8") self.assertIn(" - rust-toolchain.toml\n", setup) + self.assertIn(" - nvx-rs/**\n", setup) self.assertIn( "rust-toolchain.toml", _workflow_step( @@ -5324,6 +5337,7 @@ def test_ci_takes_rust_toolchain_from_repository_pin(self): ) rust = _workflow_step(setup, "copilot-setup-steps", "Install Rust toolchains") self.assertIn('rustup default "${RUST_TOOLCHAIN}"', rust) + self.assertIn("x86_64-unknown-linux-musl", rust) self.assertNotIn("stable", rust) # A binary built by another compiler must not satisfy the cache. @@ -6430,6 +6444,20 @@ def test_guest_descriptors_and_source_manifest_share_build_pins(self): OpenVMMBuildConstants.CONTROL_CONTRACT_REVISION, manifest["openvmm"]["control_contract_revision"], ) + self.assertEqual( + manifest["guest_rust"], + { + "toolchain": GuestRustBuildConstants.TOOLCHAIN, + "target": GuestRustBuildConstants.TARGET, + }, + ) + for guest in ("alpine", "ubuntu", "azurelinux"): + with self.subTest(guest=guest): + self.assertTrue( + set(GuestRustBuildConstants.SOURCE_PATHS).issubset( + manifest[guest]["guest_sources"] + ) + ) def test_cache_defaults_are_resolved_for_each_configuration(self): with tempfile.TemporaryDirectory() as temporary: @@ -6469,13 +6497,13 @@ def test_initramfs_provenance_tracks_constants_source(self): records = cast(list[dict[str, str]], original["source_files"]) self.assertEqual( [record for record in records if record["path"] == relative], - [{"path": relative, "sha256": common.sha256_file(source)}], + [{"path": relative, "sha256": common.normalized_sha256_file(source)}], ) def changed_sha256(path: Path) -> str: - return "f" * 64 if path == source else common.sha256_file(path) + return "f" * 64 if path == source else common.normalized_sha256_file(path) - with patch.object(build, "sha256_file", side_effect=changed_sha256): + with patch.object(build, "normalized_sha256_file", side_effect=changed_sha256): changed = build.initramfs_provenance_inputs() self.assertNotEqual(original, changed) self.assertEqual(original["alpine"], changed["alpine"]) @@ -6486,6 +6514,11 @@ def test_constants_are_in_docker_and_release_source_inputs(self): ) self.assertIn("COPY scripts/ /repo/scripts/", dockerfile) self.assertIn("scripts", ReleaseBuildConstants.PROJECT_SOURCE_PATHS) + self.assertIn("nvx-rs", ReleaseBuildConstants.PROJECT_SOURCE_PATHS) + self.assertIn( + Path("nvx-rs") / "target", + ReleaseBuildConstants.PROJECT_SOURCE_EXCLUDES, + ) self.assertTrue( ( BuildConstants.REPO_ROOT @@ -6508,6 +6541,69 @@ def test_project_source_paths_exist_and_are_tracked(self): ) self.assertEqual(tracked.returncode, 0, tracked.stderr) + def test_no_c_or_assembler_sources_remain_outside_openvmm(self): + tracked = subprocess.run( + ["git", "ls-files", "-z", "--cached", "--others", "--exclude-standard"], + cwd=BuildConstants.REPO_ROOT, + capture_output=True, + check=True, + ).stdout.split(b"\0") + forbidden = sorted( + path.decode("utf-8") + for path in tracked + if path + and (BuildConstants.REPO_ROOT / path.decode("utf-8")).is_file() + and not path.startswith(b"openvmm/") + and PurePosixPath(path.decode("utf-8")).suffix.lower() in {".c", ".h", ".s"} + ) + self.assertEqual(forbidden, []) + + def test_guest_binary_third_party_notices_cover_the_rust_runtime(self): + notices = (BuildConstants.REPO_ROOT / "THIRD_PARTY_NOTICES.md").read_text( + encoding="utf-8" + ) + musl = next( + line for line in notices.splitlines() if line.startswith("| musl libc |") + ) + self.assertEqual( + set(re.findall(r"`/sbin/(nvx-[a-z-]+)`", musl)), + set(GuestRustBuildConstants.BINARIES), + ) + for component in ( + "Rust core and standard libraries", + "Rust `compiler_builtins`", + "Rust `libc` crate", + "Rust `nix` crate 0.31.3", + "Rust `getrandom` crate 0.4.3", + "Rust `bitflags` crate 2.13.2", + "Rust `cfg-if` crate 1.0.5", + "Rust `memoffset` crate 0.9.1", + "Apache-2.0 WITH LLVM-exception", + ): + with self.subTest(component=component): + self.assertIn(component, notices) + + def test_project_source_archive_excludes_the_nvx_rs_target(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + crate = root / "nvx-rs" + (crate / "src").mkdir(parents=True) + (crate / "src" / "lib.rs").write_text("source\n", encoding="utf-8") + (crate / "target" / "debug").mkdir(parents=True) + (crate / "target" / "debug" / "nvx-rs").write_text( + "build output\n", encoding="utf-8" + ) + output = root / "source.tar.gz" + archive.create_reproducible_tar_gz( + output, + [(crate, "nvx-project-source/nvx-rs")], + ) + with tarfile.open(output, "r:gz") as source_archive: + names = source_archive.getnames() + + self.assertIn("nvx-project-source/nvx-rs/src/lib.rs", names) + self.assertFalse(any("/target" in name for name in names)) + def test_constants_consumers_import_in_either_order(self): modules = ( "nvx_tools.build_constants", @@ -7802,6 +7898,57 @@ def test_manifest_tracks_every_kernel_patch(self): "control_contract_revision": "nvx-microvm-v2-control-v2", }, ) + self.assertEqual( + manifest["guest_rust"], + { + "toolchain": GuestRustBuildConstants.TOOLCHAIN, + "target": GuestRustBuildConstants.TARGET, + }, + ) + + def test_guest_artifact_inputs_cover_every_rust_binary_source(self): + rust_sources = set(guest_binaries.guest_binary_source_files()) + inputs = { + "alpine provenance": set(build._initramfs_source_files()), + "alpine customization": set( + build._guest_customization_files(guests.ALPINE_GUEST) + ), + "ubuntu customization": set(ubuntu.customization_files()), + "azure linux": set(azurelinux.input_files()), + } + for name, files in inputs.items(): + with self.subTest(inputs=name): + self.assertTrue(rust_sources.issubset(files)) + self.assertIn( + BuildConstants.REPO_ROOT / "scripts" / "nvx_tools" / "guest_binaries.py", + inputs["alpine provenance"], + ) + + def test_guest_artifact_identities_normalize_source_line_endings(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "nvx-rs" / "src" / "lib.rs" + source.parent.mkdir(parents=True) + source.write_bytes(b"pub fn fixture() {}\n") + with ( + patch.object(BuildConstants, "REPO_ROOT", root), + patch.object(build, "_initramfs_source_files", return_value=(source,)), + patch.object(ubuntu, "load_package_lock", return_value=()), + patch.object(azurelinux, "input_files", return_value=(source,)), + ): + lf = { + "alpine": build.initramfs_provenance_inputs(), + "ubuntu": ubuntu.converter_input_sha256((source,)), + "azurelinux": azurelinux.input_sha256(), + } + source.write_bytes(b"pub fn fixture() {}\r\n") + crlf = { + "alpine": build.initramfs_provenance_inputs(), + "ubuntu": ubuntu.converter_input_sha256((source,)), + "azurelinux": azurelinux.input_sha256(), + } + + self.assertEqual(crlf, lf) def test_guest_descriptors_preserve_alpine_and_select_guest_outputs(self): alpine = guests.guest_descriptor("alpine") @@ -8635,6 +8782,29 @@ def test_ci_guest_kernel_keys_include_the_patch_identity(self): action, ) + def test_ci_guest_image_keys_include_the_rust_source_identity(self): + # hashFiles hashes contents only, so a module move that kept every + # file's bytes reused images whose provenance named the old paths. + action = ( + BuildConstants.REPO_ROOT + / ".github" + / "actions" + / "build-guest-artifacts" + / "action.yml" + ).read_text(encoding="utf-8") + self.assertIn( + "rust_source_identity=$(python3 scripts/nvx.py guest-binary-source-identity)", + action, + ) + for key in ( + "alpine=alpine-image-v2-${ALPINE_VERSION}-${ALPINE_INPUT_HASH}", + "ubuntu=ubuntu-initramfs-v2-${UBUNTU_VERSION}-${UBUNTU_INPUT_HASH}", + "ubuntu-distro=ubuntu-distro-v2-${UBUNTU_VERSION}-${UBUNTU_INPUT_HASH}", + "azurelinux=azurelinux-initramfs-v2-${AZURELINUX_VERSION}-${AZURELINUX_INPUT_HASH}", + ): + with self.subTest(key=key.split("=", 1)[0]): + self.assertIn(f'echo "{key}-${{rust_source_identity}}"', action) + def test_ci_guest_cache_keys_include_build_configuration(self): action = ( BuildConstants.REPO_ROOT @@ -8788,10 +8958,28 @@ def test_ci_guest_cache_keys_include_shared_build_modules(self): / "build-guest-artifacts" / "action.yml" ).read_text(encoding="utf-8") - for variable in ("ALPINE_INPUT_HASH", "UBUNTU_INPUT_HASH"): + for variable in ( + "ALPINE_INPUT_HASH", + "UBUNTU_INPUT_HASH", + "AZURELINUX_INPUT_HASH", + ): assignment = next(line for line in action.splitlines() if variable in line) self.assertIn("'scripts/nvx_tools/common.py'", assignment) self.assertIn("'scripts/nvx_tools/guests.py'", assignment) + self.assertIn("'scripts/nvx_tools/elf.py'", assignment) + self.assertIn("'scripts/nvx_tools/guest_binaries.py'", assignment) + self.assertIn("'nvx-rs/**'", assignment) + self.assertIn("'!nvx-rs/target/**'", assignment) + self.assertIn("'rust-toolchain.toml'", assignment) + for prefix in ( + "alpine-image", + "ubuntu-initramfs", + "ubuntu-distro", + "azurelinux-initramfs", + ): + with self.subTest(cache=prefix): + self.assertIn(f"{prefix}-v2-", action) + self.assertNotIn(f"{prefix}-v1-", action) def test_quality_checks_include_shared_identity_probe(self): action = ( @@ -8816,13 +9004,132 @@ def test_docker_guest_builder_pins_erofs_toolchain(self): self.assertIn("ARG EROFS_UTILS_VERSION=1.5-1", dockerfile) self.assertIn("erofs-utils=${EROFS_UTILS_VERSION}", dockerfile) + def test_docker_builds_one_shared_rust_guest_binary_set(self): + dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( + encoding="utf-8" + ) + self.assertIn( + f"ARG RUST_BUILDER_IMAGE={DockerBuildConstants.RUST_BUILDER_IMAGE}", + dockerfile, + ) + builder = dockerfile.split("FROM ${RUST_BUILDER_IMAGE} AS guest-binaries", 1)[ + 1 + ].split("\nFROM ", 1)[0] + self.assertIn("COPY rust-toolchain.toml /repo/", builder) + self.assertIn("COPY nvx-rs/ /repo/nvx-rs/", builder) + self.assertIn( + 'rustup target add --toolchain "$toolchain" x86_64-unknown-linux-musl', + builder, + ) + self.assertIn( + "python3 scripts/nvx.py build-guest-binaries --output /out/guest-binaries", + builder, + ) + self.assertEqual(dockerfile.count("build-guest-binaries"), 1) + + base = dockerfile.split("FROM ${DEBIAN_IMAGE} AS base", 1)[1].split( + "\nFROM ", 1 + )[0] + self.assertIn(" build-essential \\\n", base) + self.assertNotIn("musl-tools", base) + self.assertNotIn("nvx-rs", base) + self.assertNotIn("rust-toolchain.toml", base) + self.assertNotIn("guest-binaries", base) + + guest_base = dockerfile.split("FROM base AS guest-base", 1)[1].split( + "\nFROM ", 1 + )[0] + self.assertIn("COPY nvx-rs/ /repo/nvx-rs/", guest_base) + self.assertIn("COPY rust-toolchain.toml /repo/", guest_base) + self.assertIn( + "COPY --from=guest-binaries /out/guest-binaries/ " + "/repo/build/guest-binaries/", + guest_base, + ) + self.assertIn( + "ENV NVX_GUEST_BINARIES=/repo/build/guest-binaries", + guest_base, + ) + + determinism = dockerfile.split("FROM guest-base AS ubuntu-determinism", 1)[ + 1 + ].split("\nFROM ", 1)[0] + self.assertIn( + "COPY --from=guest-binaries /usr/local/cargo/ /usr/local/cargo/", + determinism, + ) + self.assertIn( + "COPY --from=guest-binaries /usr/local/rustup/ /usr/local/rustup/", + determinism, + ) + self.assertIn( + "RUN NVX_GUEST_BINARIES= python3 scripts/nvx.py verify-guest-determinism", + determinism, + ) + + azure = dockerfile.split("FROM guest-base AS azurelinux-initramfs", 1)[1].split( + "\nFROM ", 1 + )[0] + self.assertIn( + "source = Path('/repo/build/guest-binaries')", + azure, + ) + self.assertIn("inspect_guest_binaries", azure) + self.assertIn("for name in GuestRustBuildConstants.BINARIES:", azure) + self.assertIn("'helpers': helpers", azure) + self.assertNotIn("azurelinux-helpers", dockerfile) + self.assertNotIn("musl-gcc", dockerfile) + self.assertNotIn("cc -static", dockerfile) + + def test_docker_kernel_targets_do_not_build_the_guest_binaries(self): + # BuildKit builds every stage that a target inherits or copies from, so + # a kernel-only build must not compile the Rust guest binaries. + dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( + encoding="utf-8" + ) + dependencies: dict[str, set[str]] = {} + stage = "" + for line in dockerfile.splitlines(): + if match := re.fullmatch(r"FROM (\S+) AS (\S+)", line): + stage = match.group(2) + dependencies[stage] = {match.group(1)} + elif match := re.match(r"COPY --from=(\S+) ", line): + dependencies[stage].add(match.group(1)) + + def stages_built_for(target: str) -> set[str]: + built: set[str] = set() + pending = [target] + while pending: + for dependency in dependencies.get(pending.pop(), set()): + if dependency not in built: + built.add(dependency) + pending.append(dependency) + return built + + for target in ( + "kernel-artifacts", + DockerBuildConstants.DEBUG_KERNEL_TARGET, + DockerBuildConstants.LINUX_SOURCE_TARGET, + ): + with self.subTest(target=target): + self.assertIn("base", stages_built_for(target)) + self.assertNotIn("guest-binaries", stages_built_for(target)) + for target in ( + DockerBuildConstants.ALPINE_TARGET, + DockerBuildConstants.UBUNTU_TARGET, + DockerBuildConstants.AZURELINUX_TARGET, + "ubuntu-determinism", + ): + with self.subTest(target=target): + self.assertIn("guest-binaries", stages_built_for(target)) + def test_azurelinux_initramfs_uses_locked_busybox_applets(self): dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( encoding="utf-8" ) base_stage = dockerfile.split("FROM base AS kernel", 1)[0] self.assertNotIn("busybox", base_stage) - azure_stage = dockerfile.split("FROM base AS azurelinux-initramfs", 1)[1] + azure_stage = dockerfile.split("FROM guest-base AS azurelinux-initramfs", 1)[1] self.assertIn("ln -sf ../sbin/busybox /rootfs/usr/bin/busybox", azure_stage) applets = ( azure_stage.split("for utility in ", 1)[1] @@ -8850,7 +9157,7 @@ def test_azurelinux_initramfs_gives_nobody_a_home(self): dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( encoding="utf-8" ) - azure_stage = dockerfile.split("FROM base AS azurelinux-initramfs", 1)[1] + azure_stage = dockerfile.split("FROM guest-base AS azurelinux-initramfs", 1)[1] self.assertIn( "sed -i 's#^\\(nobody:[^:]*:65534:65534:[^:]*:\\)/dev/null:" "#\\1/nonexistent:#'", @@ -8897,7 +9204,7 @@ def test_azurelinux_initramfs_packs_normalized_rootfs(self): dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( encoding="utf-8" ) - azure_stage = dockerfile.split("FROM base AS azurelinux-initramfs", 1)[1] + azure_stage = dockerfile.split("FROM guest-base AS azurelinux-initramfs", 1)[1] cleanup = azure_stage.index( "rm -rf /rootfs/usr/lib/sysimage/tdnf /rootfs/var/cache/tdnf \\\n" " /rootfs/var/cache/ldconfig /rootfs/var/lib/rpm" @@ -8914,7 +9221,7 @@ def test_azurelinux_manifest_records_shared_input_digest(self): dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( encoding="utf-8" ) - azure_stage = dockerfile.split("FROM base AS azurelinux-initramfs", 1)[1] + azure_stage = dockerfile.split("FROM guest-base AS azurelinux-initramfs", 1)[1] self.assertIn("from nvx_tools.azurelinux import input_sha256", azure_stage) self.assertIn( "'input_sha256': input_sha256(\n" @@ -8947,6 +9254,9 @@ def test_azurelinux_input_digest_tracks_guest_sources_and_dockerfile(self): "scripts/nvx_tools/azurelinux.py", "scripts/nvx_tools/build_constants.py", "scripts/nvx_tools/common.py", + "scripts/nvx_tools/guest_binaries.py", + "nvx-rs/src/lib.rs", + "rust-toolchain.toml", ): with self.subTest(path=relative): path = root / relative @@ -8967,23 +9277,39 @@ def install(_source: Path, destination: Path) -> dict[str, str]: installed.append(destination) return {} - root = Path("rootfs") - with ( - patch.object(build, "_install", side_effect=install), - patch.object(build, "_build_static_helper", return_value={}), - patch.object(build, "_build_device_io_helper", return_value={}), - ): - build._install_guest_files( - build_config.InitramfsBuildConfig(work=Path("work")), - root, - guests.ALPINE_GUEST, + def stage_binaries(output: Path) -> guest_binaries.GuestBinaryBuildResult: + output.mkdir() + binaries: dict[str, str] = {} + for name in GuestRustBuildConstants.BINARIES: + path = output / name + path.write_bytes(name.encode("ascii")) + binaries[name] = common.sha256_file(path) + return guest_binaries.GuestBinaryBuildResult( + source_sha256="a" * 64, + binaries=binaries, ) - self.assertIn(root / "sbin" / "nvx-hostmount", installed) + + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) / "rootfs" + work = Path(temporary) / "work" + (root / "sbin").mkdir(parents=True) + (root / "etc").mkdir() + work.mkdir() + with ( + patch.object(build, "_install", side_effect=install), + patch.object(build, "build_guest_binaries", side_effect=stage_binaries), + ): + build._install_guest_files( + build_config.InitramfsBuildConfig(work=work), + root, + guests.ALPINE_GUEST, + ) + self.assertIn(root / "sbin" / "nvx-hostmount", installed) dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( encoding="utf-8" ) - azure_stage = dockerfile.split("FROM base AS azurelinux-initramfs", 1)[1] + azure_stage = dockerfile.split("FROM guest-base AS azurelinux-initramfs", 1)[1] azure_sbin_scripts = azure_stage.split( "install -m 0755 /repo/guest/common/init /rootfs/init\n", 1 )[1].split("/rootfs/sbin/", 1)[0] @@ -9036,43 +9362,31 @@ def test_apk_add_uses_host_ca_bundle_without_overriding_configuration(self): environment = run.call_args.kwargs["env"] self.assertEqual(environment["SSL_CERT_FILE"], "/etc/configured-ca.pem") - def test_device_io_helper_is_static_with_nonexecutable_stack(self): - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - work = root / "work" - destination = root / "root" / "sbin" / "nvx-device-io" - work.mkdir() - destination.parent.mkdir(parents=True) - - def compile_helper( - command: list[str | os.PathLike[str]], **_kwargs: object - ) -> None: - output = Path(command[command.index("-o") + 1]) - output.write_bytes(b"static-elf") - - with ( - patch.object(build, "require_tool", return_value="cc"), - patch.object(build, "run_checked", side_effect=compile_helper) as run, - ): - provenance = build._build_device_io_helper(work, destination) - - command = run.call_args.args[0] - self.assertIn("-nostdlib", command) - self.assertIn("-static", command) - self.assertIn("-Wl,-z,noexecstack", command) - self.assertEqual(destination.read_bytes(), b"static-elf") - self.assertEqual( - provenance["binary_sha256"], common.sha256_file(destination) - ) - self.assertEqual(len(provenance["source_sha256"]), 64) - - def test_device_io_provenance_uses_shared_helper_source(self): + def test_device_io_provenance_uses_shared_rust_source_digest(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) checkout = root / "nvx" - source = checkout / "guest" / "common" / "nvx-device-io.c" - source.parent.mkdir(parents=True) - source.write_bytes(b"shared helper source") + crate = checkout / "nvx-rs" + (crate / "src").mkdir(parents=True) + sources = { + crate / "Cargo.toml": "[package]\nname = 'fixture'\n", + crate / "Cargo.lock": "# lock\n", + crate / "build.rs": "fn main() {}\n", + crate / "src" / "lib.rs": "pub fn fixture() {}\n", + checkout / "rust-toolchain.toml": ( + f'[toolchain]\nchannel = "{GuestRustBuildConstants.TOOLCHAIN}"\n' + ), + checkout / "scripts" / "nvx_tools" / "build_constants.py": ( + "PROFILE = 'guest'\n" + ), + checkout / "scripts" / "nvx_tools" / "guest_binaries.py": ( + "def build(): pass\n" + ), + } + for path, contents in sources.items(): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(contents, encoding="utf-8") + source_sha256 = guest_binaries.guest_binary_source_sha256(checkout) initrd = root / "initramfs.cpio.gz" manifest = initrd.with_name(f"{initrd.name}.packages.json") manifest.write_text( @@ -9080,7 +9394,7 @@ def test_device_io_provenance_uses_shared_helper_source(self): { "helpers": { "nvx-device-io": { - "source_sha256": common.sha256_file(source), + "source_sha256": source_sha256, "binary_sha256": "1" * 64, } } @@ -9094,11 +9408,32 @@ def test_device_io_provenance_uses_shared_helper_source(self): initrd, ) - self.assertEqual(provenance["source"], str(source.resolve())) - self.assertEqual( - provenance["source_sha256"], - common.sha256_file(source), - ) + self.assertEqual(provenance["source"], str(crate.resolve())) + self.assertEqual(provenance["source_sha256"], source_sha256) + self.assertEqual(provenance["toolchain"], GuestRustBuildConstants.TOOLCHAIN) + self.assertEqual(provenance["target"], GuestRustBuildConstants.TARGET) + + with ( + patch.object(GuestRustBuildConstants, "TOOLCHAIN", "9.9.9"), + self.assertRaisesRegex( + common.ScriptError, + "Rust guest build toolchain mismatch", + ), + ): + benchmark._device_io_helper_provenance( + argparse.Namespace(nvx_dir=checkout), + initrd, + ) + + (crate / "src" / "lib.rs").write_text("changed\n", encoding="utf-8") + with self.assertRaisesRegex( + ValueError, + "Rust guest binary sources do not match", + ): + benchmark._device_io_helper_provenance( + argparse.Namespace(nvx_dir=checkout), + initrd, + ) def test_sandbox_kernel_config_requires_every_feature(self): with tempfile.TemporaryDirectory() as temporary: @@ -9496,36 +9831,89 @@ def test_guest_build_adds_debug_kernel_only_when_requested(self): debug.work.name, KernelBuildConstants.DEBUG_WORK_DIRECTORY_NAME ) - def test_guest_files_build_time_probe_with_its_own_flags(self): - source = BuildConstants.REPO_ROOT / "guest" / "common" / "nvx-time-probe.c" - header = source.read_text(encoding="utf-8").splitlines()[:2] - self.assertEqual( - header, - [ - "// Copyright (c) Microsoft Corporation.", - "// Licensed under the MIT License.", - ], - ) + def test_guest_files_install_one_checked_rust_build(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) / "root" work = Path(temporary) / "work" for directory in (root / "sbin", root / "etc", work): directory.mkdir(parents=True) - commands: list[list[str]] = [] + payloads = { + name: f"rust-guest-binary {name}".encode() + for name in GuestRustBuildConstants.BINARIES + } + result = guest_binaries.GuestBinaryBuildResult( + source_sha256="a" * 64, + binaries={ + name: hashlib.sha256(payload).hexdigest() + for name, payload in payloads.items() + }, + ) - def compile_helper( - command: list[str | os.PathLike[str]], **_kwargs: object - ) -> None: - commands.append([os.fspath(part) for part in command]) - output = Path(command[command.index("-o") + 1]) - output.write_bytes(f"static-elf {output.name}".encode()) + def stage_binaries(output: Path) -> guest_binaries.GuestBinaryBuildResult: + output.mkdir() + for name, payload in payloads.items(): + (output / name).write_bytes(payload) + return result + + with patch.object( + build, "build_guest_binaries", side_effect=stage_binaries + ) as rust_build: + helpers = build._install_guest_files( + build_config.InitramfsBuildConfig(work=work), + root, + guests.guest_descriptor("alpine"), + ) - def find_tool(name: str) -> str: - return name + rust_build.assert_called_once_with( + work / GuestRustBuildConstants.OUTPUT_DIRECTORY_NAME + ) + for name, payload in payloads.items(): + with self.subTest(helper=name): + installed = root / "sbin" / name + self.assertEqual(installed.read_bytes(), payload) + if os.name != "nt": + self.assertEqual(installed.stat().st_mode & 0o777, 0o755) + self.assertEqual( + helpers[name], + { + "source_sha256": result.source_sha256, + "binary_sha256": result.binaries[name], + }, + ) + for name in ( + "STATIC_HELPER_CFLAGS", + "DEVICE_IO_CFLAGS", + "STATIC_HELPERS", + "MUSL_COMPILER", + "MUSL_STATIC_HELPERS", + "TIME_PROBE_CFLAGS", + ): + with self.subTest(helper=name): + self.assertFalse(hasattr(InitramfsBuildConstants, name)) + def test_guest_files_reuse_the_configured_prebuilt_rust_binaries(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) / "root" + work = Path(temporary) / "work" + prebuilt = Path(temporary) / "prebuilt" + for directory in (root / "sbin", root / "etc", work, prebuilt): + directory.mkdir(parents=True) + binaries: dict[str, str] = {} + for name in GuestRustBuildConstants.BINARIES: + path = prebuilt / name + path.write_bytes(name.encode("ascii")) + binaries[name] = common.sha256_file(path) + result = guest_binaries.GuestBinaryBuildResult( + source_sha256="a" * 64, + binaries=binaries, + ) + environment = GuestRustBuildConstants.BINARIES_VARIABLE with ( - patch.object(build, "require_tool", side_effect=find_tool), - patch.object(build, "run_checked", side_effect=compile_helper), + patch.dict(os.environ, {environment: str(prebuilt)}), + patch.object( + build, "inspect_guest_binaries", return_value=result + ) as inspect, + patch.object(build, "build_guest_binaries") as rust_build, ): helpers = build._install_guest_files( build_config.InitramfsBuildConfig(work=work), @@ -9533,49 +9921,15 @@ def find_tool(name: str) -> str: guests.guest_descriptor("alpine"), ) - probe = InitramfsBuildConstants.TIME_PROBE_NAME - flags_by_source = { - Path(command[-1]).stem: command[1 : command.index("-o")] - for command in commands - } - compiler_by_source = { - Path(command[-1]).stem: command[0] for command in commands - } - self.assertEqual( - flags_by_source[probe], list(InitramfsBuildConstants.TIME_PROBE_CFLAGS) - ) - self.assertIn("-pthread", flags_by_source[probe]) - musl = InitramfsBuildConstants.MUSL_COMPILER - self.assertEqual(compiler_by_source[probe], musl) - for name in InitramfsBuildConstants.STATIC_HELPERS: - with self.subTest(helper=name): - self.assertEqual( - flags_by_source[name], - list(InitramfsBuildConstants.STATIC_HELPER_CFLAGS), - ) - self.assertEqual(compiler_by_source[name], "cc") - for name in InitramfsBuildConstants.MUSL_STATIC_HELPERS: - with self.subTest(helper=name): - self.assertEqual( - flags_by_source[name], - list(InitramfsBuildConstants.STATIC_HELPER_CFLAGS), - ) - self.assertEqual(compiler_by_source[name], musl) - dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( - encoding="utf-8" - ) - self.assertIn(" musl-tools \\\n", dockerfile) - self.assertIn( - " for helper in " - + " ".join(InitramfsBuildConstants.MUSL_STATIC_HELPERS) - + "; do \\\n musl-gcc ", - dockerfile, - ) - self.assertEqual(helpers[probe]["source_sha256"], common.sha256_file(source)) - self.assertEqual( - helpers[probe]["binary_sha256"], - hashlib.sha256(f"static-elf {probe}".encode()).hexdigest(), - ) + inspect.assert_called_once_with(prebuilt) + rust_build.assert_not_called() + for name in GuestRustBuildConstants.BINARIES: + with self.subTest(helper=name): + self.assertEqual( + (root / "sbin" / name).read_bytes(), name.encode("ascii") + ) + self.assertEqual(helpers[name]["source_sha256"], "a" * 64) + self.assertEqual(helpers[name]["binary_sha256"], binaries[name]) def test_docker_debug_kernel_target_exports_debug_artifacts(self): dockerfile = (BuildConstants.REPO_ROOT / "docker" / "Dockerfile").read_text( @@ -10815,266 +11169,6 @@ def run(*arguments: str) -> subprocess.CompletedProcess[str]: self.assertNotIn("NVX-UBUNTU-SANDBOX-OK", result.stdout) -class ManagedAgentCgroupTests(unittest.TestCase): - SOURCE = Path(__file__).parents[1] / "guest" / "common" / "nvx-managed-agent.c" - - def setUp(self): - if sys.platform != "linux": - self.skipTest("the managed agent requires Linux") - compiler = shutil.which("cc") or shutil.which("gcc") - if compiler is None: - self.skipTest("C compiler is unavailable") - temporary = tempfile.TemporaryDirectory() - self.addCleanup(temporary.cleanup) - root = Path(temporary.name) - self.cgroup = root / "cgroup" - self.cgroup.mkdir() - (self.cgroup / "cgroup.procs").touch() - self.subtree_control = self.cgroup / "cgroup.subtree_control" - self.subtree_control.touch() - self.execution = self.cgroup / "nvx-exec" - self.execution.mkdir() - self.tasks = self.execution / "pids.current" - self.tasks.write_bytes(b"0\n") - (self.execution / "cgroup.kill").touch() - source = root / "cgroup-test.c" - source.write_text( - f"""#define _GNU_SOURCE -#include -#include -static pid_t harness_waitpid(pid_t pid, int *status, int options); -#define waitpid(pid, status, options) harness_waitpid(pid, status, options) -#define CGROUP_ROOT {json.dumps(str(self.cgroup))} -#define main managed_agent_main -#include {json.dumps(str(self.SOURCE))} -#undef main -#undef waitpid - -static pid_t harness_process = -1; - -/* Like the pids controller, stops charging a process once it is reaped. */ -static pid_t harness_waitpid(pid_t pid, int *status, int options) -{{ - pid_t result = waitpid(pid, status, options); - - if (result > 0 && result == harness_process) {{ - int fd = open(EXEC_CGROUP "/pids.current", O_WRONLY | O_TRUNC | O_CLOEXEC); - - if (fd < 0 || write(fd, "0\\n", 2) != 2 || close(fd) != 0) {{ - _exit(3); - }} - harness_process = -1; - }} - return result; -}} - -/* - * Calls `check` while a workload process that the cgroup charges exits, or - * once it has exited if `exited` is set, and requires `check` to reap it. - */ -static int with_exiting_process(int (*check)(void), int exited) -{{ - siginfo_t information = {{0}}; - int result; - int status; - pid_t child = fork(); - - if (child < 0) {{ - _exit(2); - }} - if (child == 0) {{ - const struct timespec delay = {{.tv_nsec = 50L * 1000L * 1000L}}; - - nanosleep(&delay, NULL); - _exit(0); - }} - harness_process = child; - if (exited && waitid(P_PID, (id_t)child, &information, WEXITED | WNOWAIT) != 0) {{ - _exit(2); - }} - result = check(); - status = errno; - if (harness_process >= 0 || waitpid(child, NULL, WNOHANG) >= 0 || errno != ECHILD) {{ - _exit(4); - }} - errno = status; - return result; -}} - -int main(int argc, char **argv) -{{ - int result; - if (argc != 2) {{ - return 2; - }} - errno = 0; - if (strcmp(argv[1], "retry") == 0) {{ - struct control_session session = {{.fd = STDOUT_FILENO}}; - struct agent_config config = {{.direct = 1}}; - struct exec_config exec_config = {{0}}; - char *command[] = {{"/bin/true", NULL}}; - result = run_exec(&session, &config, 42, 0, command, &exec_config); - return result == 0 - ? run_exec(&session, &config, 43, 0, command, &exec_config) - : result; - }} - if (strcmp(argv[1], "tasks") == 0) {{ - result = exec_cgroup_has_tasks(); - }} else if (strcmp(argv[1], "prepare") == 0) {{ - result = prepare_exec_cgroup(); - }} else if (strcmp(argv[1], "settle") == 0) {{ - result = settle_exec_cgroup(); - }} else if (strcmp(argv[1], "settle-reap") == 0) {{ - result = with_exiting_process(settle_exec_cgroup, 0); - }} else if (strcmp(argv[1], "verify-reap") == 0) {{ - result = with_exiting_process(reap_exec_cgroup, 1); - }} else {{ - return 2; - }} - printf("%d %d\\n", result, errno); - return 0; -}} -""", - encoding="utf-8", - ) - self.helper = root / "cgroup-test" - flags = [ - flag - for flag in InitramfsBuildConstants.STATIC_HELPER_CFLAGS - if flag != "-static" - ] - result = subprocess.run( - [compiler, *flags, "-o", str(self.helper), str(source)], - capture_output=True, - text=True, - timeout=30, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - - def _check(self, mode: str) -> tuple[int, int]: - result = subprocess.run( - [str(self.helper), mode], - capture_output=True, - text=True, - timeout=5, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - value, status = result.stdout.split() - return int(value), int(status) - - def _assert_further_exec_refused(self): - result = subprocess.run( - [str(self.helper), "retry"], - capture_output=True, - timeout=5, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - frames = result.stdout - for expected_id in (42, 43): - outer = ManagedAgentStopTests.OUTER - app = ManagedAgentStopTests.APP - *_, length = outer.unpack(frames[: outer.size]) - frame = frames[outer.size : outer.size + length] - _, _, kind, _, request_id, status, payload_len = app.unpack( - frame[: app.size] - ) - payload = frame[app.size :] - self.assertEqual((kind, request_id, status), (0xFF, expected_id, 125)) - self.assertEqual(payload_len, len(payload)) - self.assertEqual(payload, b"containment-failed") - frames = frames[outer.size + length :] - self.assertEqual(frames, b"") - - def test_task_count_requires_a_canonical_decimal(self): - for contents, expected in ( - (b"0\n", 0), - (b"1\n", 1), - (b"10\n", 1), - (b"4194304\n", 1), - ): - with self.subTest(contents=contents): - self.tasks.write_bytes(contents) - self.assertEqual(self._check("tasks")[0], expected) - for contents in ( - b"", - b"\n", - b"0", - b"00\n", - b"01\n", - b"-1\n", - b"+1\n", - b" 1\n", - b"1 \n", - b"0\n0\n", - b"1\x00\n", - b"max\n", - ): - with self.subTest(contents=contents): - self.tasks.write_bytes(contents) - self.assertEqual(self._check("tasks"), (-1, errno.EPROTO)) - self.tasks.write_bytes(b"1" * 40 + b"\n") - self.assertEqual(self._check("tasks"), (-1, errno.EOVERFLOW)) - - def test_missing_and_unreadable_task_counts_are_errors(self): - self.tasks.unlink() - self.assertEqual(self._check("tasks"), (-1, errno.ENOENT)) - self._assert_further_exec_refused() - self.tasks.mkdir() - self.assertEqual(self._check("tasks"), (-1, errno.EISDIR)) - self._assert_further_exec_refused() - - def test_preparation_enables_the_pids_controller_once(self): - self.assertEqual(self._check("prepare")[0], 0) - self.assertEqual(self.subtree_control.read_bytes(), b"") - self.tasks.unlink() - self.assertEqual(self._check("prepare")[0], 0) - self.assertEqual(self.subtree_control.read_bytes(), b"+pids") - self.subtree_control.unlink() - self.assertEqual(self._check("prepare"), (-1, errno.ENOENT)) - - def test_settlement_verifies_emptiness(self): - self.assertEqual(self._check("settle")[0], 0) - self.assertEqual((self.execution / "cgroup.kill").read_bytes(), b"1") - - def test_settlement_and_verification_reap_what_the_workload_left(self): - for mode in ("settle-reap", "verify-reap"): - with self.subTest(mode=mode): - self.tasks.write_bytes(b"1\n") - self.assertEqual(self._check(mode)[0], 0) - self.assertEqual(self.tasks.read_bytes(), b"0\n") - - def test_settlement_does_not_hide_kill_or_verification_failures(self): - self.tasks.write_bytes(b"1\n") - (self.execution / "cgroup.kill").unlink() - self.assertEqual(self._check("settle"), (-1, errno.ENOENT)) - self._assert_further_exec_refused() - (self.execution / "cgroup.kill").touch() - self.tasks.unlink() - self.assertEqual(self._check("settle"), (-1, errno.ENOENT)) - self._assert_further_exec_refused() - - def test_unreaped_tasks_at_deadline_are_not_settled(self): - # cgroup.events no longer counts a killed process that awaits its - # reaper, but the pids controller still charges it. - (self.execution / "cgroup.events").write_bytes(b"populated 0\nfrozen 0\n") - self.tasks.write_bytes(b"1\n") - started = time.monotonic() - self.assertEqual(self._check("settle"), (-1, errno.ETIMEDOUT)) - self.assertGreaterEqual(time.monotonic() - started, 1.9) - self._assert_further_exec_refused() - - def test_malformed_task_count_refuses_further_exec_until_verified_empty(self): - self.tasks.write_bytes(b"max\n") - self.assertEqual(self._check("settle"), (-1, errno.EPROTO)) - self._assert_further_exec_refused() - self.tasks.write_bytes(b"0\n") - self.assertEqual(self._check("tasks")[0], 0) - self.assertEqual(self._check("settle")[0], 0) - - def _elf_file( segments: tuple[tuple[int, bytes], ...] = ((1, b"code"),), *, @@ -11229,631 +11323,6 @@ def test_check_lists_every_executable_and_fails_on_dynamic_ones(self): self.assertIn("not static executables", error.getvalue()) -class GuestExitTests(unittest.TestCase): - """nvx-exit's arguments, and its single one-byte write at port 0x604.""" - - SOURCE = Path(__file__).parents[1] / "guest" / "common" / "nvx-exit.c" - PORT = 0x604 - - def setUp(self): - if sys.platform != "linux": - self.skipTest("nvx-exit builds on Linux") - compiler = shutil.which("cc") or shutil.which("gcc") - if compiler is None: - self.skipTest("C compiler is unavailable") - temporary = tempfile.TemporaryDirectory() - self.addCleanup(temporary.cleanup) - root = Path(temporary.name) - # A regular file stands in for /dev/port, so no test writes a port. - self.device = root / "port" - flags = [ - flag - for flag in InitramfsBuildConstants.STATIC_HELPER_CFLAGS - if flag != "-static" - ] - self.binaries: dict[str, Path] = {} - for name, device in (("ok", self.device), ("missing", root / "no" / "port")): - binary = root / f"nvx-exit-{name}" - result = subprocess.run( - [ - compiler, - *flags, - f'-DNVX_EXIT_DEVICE="{device}"', - "-o", - str(binary), - str(self.SOURCE), - ], - capture_output=True, - text=True, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.binaries[name] = binary - - def _run(self, name: str, *arguments: str) -> subprocess.CompletedProcess[bytes]: - return subprocess.run( - [str(self.binaries[name]), *arguments], - capture_output=True, - check=False, - timeout=10, - ) - - def test_writes_the_exit_code_as_one_byte_at_the_control_port(self): - cases = ( - ((), 1), - (("",), 1), - (("0",), 0), - (("37",), 37), - (("255",), 255), - (("256",), 1), - (("-1",), 1), - (("+5",), 1), - ((" 7",), 1), - (("12a",), 1), - (("99999999999999999999",), 1), - # Out of range after any digit, so the value never wraps. - (("4294967296",), 1), - (("4294967551",), 1), - (("00000000000000000255",), 255), - # Decimal: the old script's printf read a leading zero as octal. - (("010",), 10), - (("7", "9"), 7), - ) - for arguments, expected in cases: - with self.subTest(arguments=arguments): - self.device.write_bytes(b"") - result = self._run("ok", *arguments) - self.assertEqual( - (result.returncode, result.stdout, result.stderr), (0, b"", b"") - ) - self.assertEqual( - self.device.read_bytes(), bytes(self.PORT) + bytes([expected]) - ) - - def test_fails_silently_when_the_port_cannot_be_opened(self): - result = self._run("missing", "0") - self.assertEqual( - (result.returncode, result.stdout, result.stderr), (1, b"", b"") - ) - - -class ManagedAgentStopTests(unittest.TestCase): - SOURCE = Path(__file__).parents[1] / "guest" / "common" / "nvx-managed-agent.c" - OUTER = struct.Struct("<4sHBB16sQQI") - APP = struct.Struct("<4sBBHQiI") - - def setUp(self): - if sys.platform != "linux": - self.skipTest("the managed agent requires a Linux pseudo-terminal") - compiler = shutil.which("cc") or shutil.which("gcc") - if compiler is None: - self.skipTest("C compiler is unavailable") - temporary = tempfile.TemporaryDirectory() - self.addCleanup(temporary.cleanup) - self.agent = Path(temporary.name) / "nvx-managed-agent" - flags = [ - flag - for flag in InitramfsBuildConstants.STATIC_HELPER_CFLAGS - if flag != "-static" - ] - result = subprocess.run( - [compiler, *flags, "-o", str(self.agent), str(self.SOURCE)], - capture_output=True, - text=True, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - - def _record( - self, record_type: int, instance: bytes, sequence: int, payload: bytes = b"" - ) -> bytes: - header = self.OUTER.pack( - b"NVXS", 1, record_type, 0, instance, 1, sequence, len(payload) - ) - return header + payload - - def _read_exact(self, fd: int, length: int, deadline: float) -> bytes: - data = b"" - while len(data) < length: - remaining = deadline - time.monotonic() - self.assertGreater(remaining, 0, "managed agent did not respond") - readable, _, _ = select.select([fd], [], [], remaining) - if readable: - chunk = os.read(fd, length - len(data)) - self.assertTrue(chunk, "managed agent closed the control tty") - data += chunk - return data - - def _read_record(self, fd: int, deadline: float) -> tuple[int, int, bytes]: - header = self._read_exact(fd, self.OUTER.size, deadline) - magic, version, record_type, flags, _, _, sequence, length = self.OUTER.unpack( - header - ) - self.assertEqual((magic, version, flags), (b"NVXS", 1, 0)) - return record_type, sequence, self._read_exact(fd, length, deadline) - - @staticmethod - def _reap(process: subprocess.Popen[bytes]) -> None: - if process.poll() is None: - process.kill() - process.wait(timeout=5) - if process.stderr is not None: - process.stderr.close() - - def _open_session( - self, rootfs: str - ) -> tuple[subprocess.Popen[bytes], int, bytes, float]: - """Starts an agent and completes the attach and reset handshake.""" - if sys.platform != "linux": - self.skipTest("the managed agent requires a Linux pseudo-terminal") - import pty - - master, slave = pty.openpty() - self.addCleanup(os.close, master) - self.addCleanup(os.close, slave) - process = subprocess.Popen( - [ - str(self.agent), - os.ttyname(slave), - rootfs, - "nvx-sandbox", - "65534", - "65534", - "nobody", - "/nonexistent", - ], - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.PIPE, - ) - self.addCleanup(self._reap, process) - deadline = time.monotonic() + 10 - instance = bytes(range(1, 17)) - - self.assertEqual(self._read_record(master, deadline)[0], 1) - os.write(master, self._record(3, instance, 7)) - record_type, sequence, credit = self._read_record(master, deadline) - self.assertEqual((record_type, sequence, len(credit)), (4, 0, 4)) - return process, master, instance, deadline - - def _request( - self, rootfs: str, kind: int, payload: bytes = b"" - ) -> tuple[int, int, int, bytes]: - """Sends one request to a fresh agent; returns the kind, request ID, status, and payload of its answer.""" - _, master, instance, deadline = self._open_session(rootfs) - frame = self.APP.pack(b"NVXC", 1, kind, 0, 42, 0, len(payload)) + payload - os.write(master, self._record(5, instance, 8, frame)) - self.assertEqual(self._read_record(master, deadline)[0], 9) - record_type, _, answer = self._read_record(master, deadline) - self.assertEqual(record_type, 5) - _, _, answered, _, request_id, status, length = self.APP.unpack( - answer[: self.APP.size] - ) - body = answer[self.APP.size :] - self.assertEqual(length, len(body)) - return answered, request_id, status, body - - def test_sandbox_agent_returns_to_init_agent_after_stop(self): - if sys.platform != "linux": - self.skipTest("the managed agent requires a Linux pseudo-terminal") - process, master, instance, deadline = self._open_session("/run/nvx/rootfs") - stop = self.APP.pack(b"NVXC", 1, 3, 0, 42, 0, 0) - os.write(master, self._record(5, instance, 8, stop)) - self.assertEqual(self._read_record(master, deadline)[0], 9) - record_type, _, payload = self._read_record(master, deadline) - self.assertEqual(record_type, 5) - _, _, kind, _, request_id, status, length = self.APP.unpack(payload) - self.assertEqual((kind, request_id, status, length), (0x85, 42, 0, 0)) - - _, stderr = process.communicate(timeout=10) - self.assertEqual(process.returncode, 0, stderr) - - def test_agent_advertises_the_control_features_of_its_mode(self): - cancel, workload_account, exec_cgroup, environment = (1, 4, 8, 16) - exec_cwd, host_mapping_table = (32, 64) - for rootfs, expected in ( - # Only the direct agent maps host paths, gives each workload a cgroup, and - # refuses a working directory that the workload cannot enter. Bit 1, which - # announced nvx_map= kernel tokens, is retired. - ( - "-", - cancel - | workload_account - | exec_cgroup - | environment - | exec_cwd - | host_mapping_table, - ), - ("/run/nvx/rootfs", cancel | workload_account | environment), - ): - with self.subTest(rootfs=rootfs): - kind, request_id, status, body = self._request(rootfs, 5) - self.assertEqual((kind, request_id, status), (0x81, 42, 0)) - self.assertEqual(struct.unpack(" bytes: - """Encodes an extended EXEC payload that carries `cwd` after the arguments.""" - arguments = b"".join(struct.pack(" -int harness_setenv(const char *name, const char *value, int overwrite); -#define setenv(name, value, overwrite) harness_setenv(name, value, overwrite) -#define CGROUP_ROOT {json.dumps(str(cgroup))} -#define setgroups(size, list) harness_setgroups(size, list) -#define setegid(gid) harness_setegid(gid) -#define seteuid(uid) harness_seteuid(uid) -#define main managed_agent_main -#include {json.dumps(str(self.SOURCE))} -#undef main - -int harness_setenv(const char *name, const char *value, int overwrite) -{{ - if (strcmp(name, "PWD") == 0 && getenv("HARNESS_FAIL_PWD") != NULL) {{ - errno = ENOMEM; - return -1; - }} - return (setenv)(name, value, overwrite); -}} - -int harness_setgroups(size_t size, const gid_t *list) -{{ - (void)size; - (void)list; - if (getenv("HARNESS_FAIL_SETGROUPS") != NULL) {{ - errno = EPERM; - return -1; - }} - return 0; -}} - -int harness_setegid(gid_t gid) -{{ - (void)gid; - return 0; -}} - -int harness_seteuid(uid_t uid) -{{ - (void)uid; - return 0; -}} - -int main(int argc, char **argv) -{{ - if (argc >= 2 && strcmp(argv[1], "--exec-config-fd") == 0) {{ - return launch_workload(argc, argv); - }} - - struct control_session session = {{.fd = STDOUT_FILENO}}; - struct agent_config config = {{ - .direct = 1, - .uid = "65534", - .gid = "65534", - .user = "nobody", - .home = "/", - }}; - char *environment[] = {{getenv("HARNESS_ENVIRONMENT"), NULL}}; - struct exec_config exec_config = {{ - .cwd = argc == 3 ? argv[2] : NULL, - .environment = environment[0] == NULL ? NULL : environment, - .environment_count = environment[0] == NULL ? 0 : 1, - .environment_present = environment[0] != NULL, - .inherit_default_env = - environment[0] != NULL && getenv("HARNESS_LAYER_ENVIRONMENT") != NULL, - }}; - char *command[] = {{"/bin/sh", "-c", argv[1], NULL}}; - - if (argc != 2 && argc != 3) {{ - return 2; - }} - return run_exec(&session, &config, 42, 0, command, &exec_config) == 0 ? 0 : 1; -}} -""", - encoding="utf-8", - ) - flags = [ - flag - for flag in InitramfsBuildConstants.STATIC_HELPER_CFLAGS - if flag != "-static" - ] - result = subprocess.run( - [compiler, *flags, "-o", str(self.harness), str(source)], - capture_output=True, - text=True, - timeout=60, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - self.environment = { - **os.environ, - "PATH": f"{tools}{os.pathsep}{os.environ.get('PATH', '/usr/bin:/bin')}", - } - for name in ( - "HARNESS_BEFORE_LAUNCH", - "HARNESS_ENVIRONMENT", - "HARNESS_FAIL_PWD", - "HARNESS_FAIL_SETGROUPS", - "HARNESS_LAYER_ENVIRONMENT", - ): - self.environment.pop(name, None) - - def _exec( - self, - script: str, - cwd: str | None = None, - environment: dict[str, str] | None = None, - ) -> list[tuple[int, int, int, bytes]]: - """Runs one workload; returns the kind, request ID, status, and payload of each frame.""" - arguments = [str(self.harness), script] - if cwd is not None: - arguments.append(cwd) - result = subprocess.run( - arguments, - capture_output=True, - timeout=30, - env=environment or self.environment, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - frames: list[tuple[int, int, int, bytes]] = [] - rest = result.stdout - while rest: - _, _, record_type, _, _, _, _, length = self.OUTER.unpack( - rest[: self.OUTER.size] - ) - payload = rest[self.OUTER.size : self.OUTER.size + length] - rest = rest[self.OUTER.size + length :] - self.assertEqual(record_type, 5) - _, _, kind, _, request_id, status, payload_len = self.APP.unpack( - payload[: self.APP.size] - ) - body = payload[self.APP.size :] - self.assertEqual(payload_len, len(body)) - frames.append((kind, request_id, status, body)) - return frames - - def _completed(self, frames: list[tuple[int, int, int, bytes]]) -> bytes: - """Returns the standard output of a workload that exited with status zero.""" - self.assertEqual(frames[-1], (0x84, 42, 0, b"exit"), frames) - self.assertTrue(all(kind == 0x82 for kind, *_ in frames[:-1]), frames) - return b"".join(body for *_, body in frames[:-1]) - - def _refused( - self, frames: list[tuple[int, int, int, bytes]], cwd: str, error: int - ) -> None: - diagnostic = ( - f"nvx-managed-agent: cannot enter working directory {cwd}: " - f"{os.strerror(error)}\n" - ).encode() - self.assertEqual( - frames, - [(0x83, 42, 0, diagnostic), (0xFF, 42, error, b"cwd-failed")], - ) - - def test_workloads_start_in_the_root_directory_by_default(self): - output = self._completed(self._exec('pwd; printf "%s\\n" "$PWD"')) - self.assertEqual(output, b"/\n/\n") - - def test_workloads_start_in_the_requested_directory(self): - for name in ("a", "b"): - directory = self.root / "work" / name - directory.mkdir(parents=True) - with self.subTest(directory=directory): - output = self._completed( - self._exec('pwd; printf "%s\\n" "$PWD"', str(directory)) - ) - self.assertEqual(output, f"{directory}\n{directory}\n".encode()) - - def test_pwd_keeps_the_requested_name_of_a_canonical_path(self): - target = self.root / "target" - target.mkdir() - link = self.root / "link" - link.symlink_to(target) - output = self._completed(self._exec("pwd; /bin/pwd -P", str(link))) - self.assertEqual(output, f"{link}\n{target}\n".encode()) - # Without a canonical name, PWD names the directory itself. - output = self._completed(self._exec('printf "%s\\n" "$PWD"', f"{link}/.")) - self.assertEqual(output, f"{target}\n".encode()) - - def test_unusable_directories_refuse_the_launch(self): - marker = self.root / "ran" - regular = self.root / "file" - regular.touch() - cases = [ - (self.root / "missing", errno.ENOENT), - (regular, errno.ENOTDIR), - ] - # Root may search any directory, so only an unprivileged user observes EACCES. - if sys.platform == "linux" and os.geteuid() != 0: - locked = self.root / "locked" - locked.mkdir(mode=0) - self.addCleanup(locked.chmod, 0o755) - cases.append((locked, errno.EACCES)) - for directory, error in cases: - with self.subTest(directory=directory): - frames = self._exec(f"touch {marker}", str(directory)) - self._refused(frames, str(directory), error) - self.assertFalse(marker.exists()) - - def test_identity_failures_refuse_the_launch(self): - environment = {**self.environment, "HARNESS_FAIL_SETGROUPS": "1"} - frames = self._exec(f"touch {self.root / 'ran'}", None, environment) - self.assertEqual(frames, [(0xFF, 42, 125, b"launch-failed")]) - self.assertFalse((self.root / "ran").exists()) - - def test_workloads_start_in_the_directory_that_was_checked(self): - checked = self.root / "checked" - retarget = self.root / "retarget" - renamed = self.root / "renamed" - checked.mkdir() - retarget.mkdir() - link = self.root / "link" - link.symlink_to(checked) - # Neither a retargeted link nor a renamed directory may fail the - # launch or move the workload to a directory that was not checked. - quote = shlex.quote - for cwd, change, expected in ( - (link, f"ln -sfn {quote(str(retarget))} {quote(str(link))}", checked), - (retarget, f"mv {quote(str(retarget))} {quote(str(renamed))}", renamed), - ): - with self.subTest(cwd=cwd): - environment = {**self.environment, "HARNESS_BEFORE_LAUNCH": change} - frames = self._exec("/bin/pwd -P", str(cwd), environment) - self.assertEqual(self._completed(frames), f"{expected}\n".encode()) - - def test_pwd_failures_refuse_the_launch(self): - marker = self.root / "ran" - environment = {**self.environment, "HARNESS_FAIL_PWD": "1"} - for cwd in (None, str(self.root)): - with self.subTest(cwd=cwd): - frames = self._exec(f"touch {marker}", cwd, environment) - self.assertEqual(frames, [(0xFF, 42, 125, b"launch-failed")]) - self.assertFalse(marker.exists()) - - def test_exact_environments_control_pwd(self): - # The agent leaves PWD to a request that replaces the environment, so - # it does not try to set PWD, and cannot fail to. - environment = { - **self.environment, - "HARNESS_ENVIRONMENT": "MODE=exact", - "HARNESS_FAIL_PWD": "1", - } - output = self._completed( - self._exec( - 'printf "%s\\n" "$MODE"; /bin/pwd -P', str(self.root), environment - ) - ) - self.assertEqual(output, f"exact\n{self.root}\n".encode()) - - def test_layered_environments_keep_the_agents_pwd(self): - # Entries layered over the default environment keep the PWD that the - # agent points at the directory, which, unlike a PWD that the shell - # computes, names a link as the request did. - target = self.root / "target" - target.mkdir() - link = self.root / "link" - link.symlink_to(target) - layered = { - **self.environment, - "HARNESS_ENVIRONMENT": "MODE=layered", - "HARNESS_LAYER_ENVIRONMENT": "1", - } - output = self._completed( - self._exec('printf "%s %s\\n" "$MODE" "$PWD"', str(link), layered) - ) - self.assertEqual(output, f"layered {link}\n".encode()) - frames = self._exec("true", str(link), {**layered, "HARNESS_FAIL_PWD": "1"}) - self.assertEqual(frames, [(0xFF, 42, 125, b"launch-failed")]) - - class AciSandboxRunnerTests(unittest.TestCase): def _artifacts(self, root: Path) -> dict[str, Path]: names = { @@ -21805,6 +21274,18 @@ def test_package_rejects_tampered_source_metadata_without_replacing_output(self) "0" * 64, "Alpine minirootfs_sha256", ), + ( + "alpine", + "guest_sources", + ["guest/common", "guest/alpine"], + "Alpine guest_sources", + ), + ( + "guest_rust", + "toolchain", + "stable", + "Rust guest toolchain", + ), ) for section, field, invalid_value, error in cases: with ( diff --git a/scripts/test_tool_versions.py b/scripts/test_tool_versions.py index 1456605a3..0fe40bde5 100644 --- a/scripts/test_tool_versions.py +++ b/scripts/test_tool_versions.py @@ -691,6 +691,13 @@ def test_linux_setups_pin_their_tools_to_the_manifest(self): [f"{name}={value}" for name, value in expected.items()], ) + def test_linux_setups_install_the_pinned_musl_target(self): + for script in (LINUX_RUNNER, LINUX_MSHV): + with self.subTest(script=script.name): + source = script.read_text(encoding="utf-8") + self.assertIn('target add --toolchain "$RUST_TOOLCHAIN"', source) + self.assertIn("x86_64-unknown-linux-musl", source) + def test_windows_setup_pins_its_tools_to_the_manifest(self): powershells = _powershells() if not powershells: