From 4b67728cce9180af7f27687fb5666decdfd47ac8 Mon Sep 17 00:00:00 2001 From: aculix <35836838+aculix@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:28:32 +0530 Subject: [PATCH] Initial release --- .gitignore | 16 + LICENSE | 21 + Package.swift | 14 + README.md | 192 ++++ SECURITY.md | 25 + Sources/Knotchy/AppDelegate.swift | 249 +++++ Sources/Knotchy/AppSettings.swift | 293 ++++++ Sources/Knotchy/ChromeExtension.swift | 28 + Sources/Knotchy/DebugTour.swift | 164 +++ Sources/Knotchy/Info.plist | 75 ++ Sources/Knotchy/Keychain.swift | 38 + Sources/Knotchy/KnotGlyph.swift | 29 + Sources/Knotchy/KnotchyApp.swift | 15 + Sources/Knotchy/MenuBar.swift | 212 ++++ Sources/Knotchy/NotchStyle.swift | 196 ++++ Sources/Knotchy/NotchViews.swift | 603 +++++++++++ Sources/Knotchy/NotchWindow.swift | 418 ++++++++ Sources/Knotchy/SettingsView.swift | 291 ++++++ Sources/Knotchy/SetupWindow.swift | 245 +++++ Sources/Knotchy/VideoSurface.swift | 234 +++++ Sources/KnotchyCore/Backend/CLIRunner.swift | 115 +++ .../Backend/ContainerCLIBackend.swift | 311 ++++++ .../KnotchyCore/Backend/HostEnvironment.swift | 35 + .../KnotchyCore/Backend/NetworkRoute.swift | 245 +++++ Sources/KnotchyCore/Backend/VMBackend.swift | 65 ++ .../KnotchyCore/Geometry/NotchGeometry.swift | 117 +++ Sources/KnotchyCore/Links/LinkIntake.swift | 131 +++ .../KnotchyCore/Session/BackendChain.swift | 49 + .../Session/SessionController.swift | 956 ++++++++++++++++++ .../KnotchyCore/Session/SessionState.swift | 36 + Sources/KnotchyCore/Session/SetupRun.swift | 20 + .../KnotchyCore/Setup/ContainerRuntime.swift | 55 + Sources/KnotchyCore/Setup/HostChecks.swift | 44 + .../KnotchyCore/Setup/RuntimeInstaller.swift | 209 ++++ Sources/KnotchyCore/Setup/SetupDriving.swift | 15 + Sources/KnotchyCore/Setup/SetupFlow.swift | 360 +++++++ .../KnotchyCore/Setup/SetupLaunchRule.swift | 12 + .../KnotchyCore/Stream/ContentReadiness.swift | 39 + Sources/KnotchyCore/Stream/FrameSampler.swift | 22 + Sources/KnotchyCore/Stream/H264.swift | 88 ++ Sources/KnotchyCore/Stream/InputEvent.swift | 78 ++ Sources/KnotchyCore/Stream/StreamClient.swift | 348 +++++++ .../KnotchyCore/Stream/StreamConnection.swift | 50 + Sources/KnotchyCore/Stream/VideoDecoder.swift | 74 ++ Sources/KnotchyCore/Stream/WireProtocol.swift | 214 ++++ Sources/KnotchyCore/Time/DeadlineTimers.swift | 67 ++ Sources/KnotchyCore/Time/KnotchyClock.swift | 15 + Sources/knotchyctl/main.swift | 121 +++ .../KnotchyCoreTests/BackendChainTests.swift | 66 ++ Tests/KnotchyCoreTests/CLIRunnerTests.swift | 19 + Tests/KnotchyCoreTests/ClockTests.swift | 156 +++ .../ContainerCLIBackendRuntimeTests.swift | 37 + .../ContainerRuntimeTests.swift | 34 + .../ContentReadinessTests.swift | 150 +++ Tests/KnotchyCoreTests/H264Tests.swift | 108 ++ Tests/KnotchyCoreTests/HostChecksTests.swift | 35 + Tests/KnotchyCoreTests/LinkIntakeTests.swift | 154 +++ .../KnotchyCoreTests/NetworkRouteTests.swift | 145 +++ .../KnotchyCoreTests/NotchGeometryTests.swift | 165 +++ .../RuntimeInstallerTests.swift | 83 ++ .../SessionLifecycleTests.swift | 372 +++++++ .../SessionNewLinkTests.swift | 204 ++++ .../KnotchyCoreTests/SessionPanelTests.swift | 340 +++++++ .../SessionRuntimeInstallTests.swift | 82 ++ .../SessionSetupRunTests.swift | 121 +++ .../KnotchyCoreTests/SessionSetupTests.swift | 301 ++++++ Tests/KnotchyCoreTests/SetupFlowTests.swift | 242 +++++ Tests/KnotchyCoreTests/Support/Fakes.swift | 336 ++++++ Tests/KnotchyCoreTests/Support/Harness.swift | 87 ++ .../KnotchyCoreTests/Support/SetupFakes.swift | 117 +++ .../KnotchyCoreTests/Support/TestClock.swift | 66 ++ Tests/KnotchyCoreTests/Support/Waiting.swift | 19 + .../KnotchyCoreTests/WireProtocolTests.swift | 216 ++++ assets/AppIcon.icns | Bin 0 -> 590999 bytes assets/dmg-background.tiff | Bin 0 -> 51034 bytes assets/knotchy-icon.svg | 1 + assets/knotchy-mark.svg | 1 + extensions/chrome/README.md | 30 + extensions/chrome/STORE.md | 76 ++ extensions/chrome/background.js | 13 + extensions/chrome/icons/icon-128.png | Bin 0 -> 10908 bytes extensions/chrome/icons/icon-16.png | Bin 0 -> 866 bytes extensions/chrome/icons/icon-32.png | Bin 0 -> 2033 bytes extensions/chrome/icons/icon-48.png | Bin 0 -> 3464 bytes extensions/chrome/manifest.json | 14 + extensions/chrome/store/marquee-1400x560.png | Bin 0 -> 140084 bytes extensions/chrome/store/promo-440x280.png | Bin 0 -> 44916 bytes scripts/build-vm.sh | 9 + scripts/bundle-app.sh | 47 + scripts/icons/frame.swift | 28 + scripts/icons/knot.json | 1 + scripts/icons/knot.py | 122 +++ scripts/icons/render.swift | 183 ++++ scripts/make-icons.sh | 17 + scripts/package-extension.sh | 11 + scripts/release.sh | 96 ++ scripts/route-server/Containerfile | 8 + scripts/route-server/serve.sh | 49 + scripts/run-vm.sh | 57 ++ scripts/test-egress.sh | 247 +++++ scripts/test-routes.sh | 189 ++++ vm/Containerfile | 52 + vm/fonts.conf | 32 + vm/knotchy-agent.c | 697 +++++++++++++ vm/knotchy-init | 197 ++++ vm/progress-gate.c | 53 + vm/progress-gate.h | 21 + vm/tests/test-progress-gate.c | 155 +++ 108 files changed, 13310 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Package.swift create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 Sources/Knotchy/AppDelegate.swift create mode 100644 Sources/Knotchy/AppSettings.swift create mode 100644 Sources/Knotchy/ChromeExtension.swift create mode 100644 Sources/Knotchy/DebugTour.swift create mode 100644 Sources/Knotchy/Info.plist create mode 100644 Sources/Knotchy/Keychain.swift create mode 100644 Sources/Knotchy/KnotGlyph.swift create mode 100644 Sources/Knotchy/KnotchyApp.swift create mode 100644 Sources/Knotchy/MenuBar.swift create mode 100644 Sources/Knotchy/NotchStyle.swift create mode 100644 Sources/Knotchy/NotchViews.swift create mode 100644 Sources/Knotchy/NotchWindow.swift create mode 100644 Sources/Knotchy/SettingsView.swift create mode 100644 Sources/Knotchy/SetupWindow.swift create mode 100644 Sources/Knotchy/VideoSurface.swift create mode 100644 Sources/KnotchyCore/Backend/CLIRunner.swift create mode 100644 Sources/KnotchyCore/Backend/ContainerCLIBackend.swift create mode 100644 Sources/KnotchyCore/Backend/HostEnvironment.swift create mode 100644 Sources/KnotchyCore/Backend/NetworkRoute.swift create mode 100644 Sources/KnotchyCore/Backend/VMBackend.swift create mode 100644 Sources/KnotchyCore/Geometry/NotchGeometry.swift create mode 100644 Sources/KnotchyCore/Links/LinkIntake.swift create mode 100644 Sources/KnotchyCore/Session/BackendChain.swift create mode 100644 Sources/KnotchyCore/Session/SessionController.swift create mode 100644 Sources/KnotchyCore/Session/SessionState.swift create mode 100644 Sources/KnotchyCore/Session/SetupRun.swift create mode 100644 Sources/KnotchyCore/Setup/ContainerRuntime.swift create mode 100644 Sources/KnotchyCore/Setup/HostChecks.swift create mode 100644 Sources/KnotchyCore/Setup/RuntimeInstaller.swift create mode 100644 Sources/KnotchyCore/Setup/SetupDriving.swift create mode 100644 Sources/KnotchyCore/Setup/SetupFlow.swift create mode 100644 Sources/KnotchyCore/Setup/SetupLaunchRule.swift create mode 100644 Sources/KnotchyCore/Stream/ContentReadiness.swift create mode 100644 Sources/KnotchyCore/Stream/FrameSampler.swift create mode 100644 Sources/KnotchyCore/Stream/H264.swift create mode 100644 Sources/KnotchyCore/Stream/InputEvent.swift create mode 100644 Sources/KnotchyCore/Stream/StreamClient.swift create mode 100644 Sources/KnotchyCore/Stream/StreamConnection.swift create mode 100644 Sources/KnotchyCore/Stream/VideoDecoder.swift create mode 100644 Sources/KnotchyCore/Stream/WireProtocol.swift create mode 100644 Sources/KnotchyCore/Time/DeadlineTimers.swift create mode 100644 Sources/KnotchyCore/Time/KnotchyClock.swift create mode 100644 Sources/knotchyctl/main.swift create mode 100644 Tests/KnotchyCoreTests/BackendChainTests.swift create mode 100644 Tests/KnotchyCoreTests/CLIRunnerTests.swift create mode 100644 Tests/KnotchyCoreTests/ClockTests.swift create mode 100644 Tests/KnotchyCoreTests/ContainerCLIBackendRuntimeTests.swift create mode 100644 Tests/KnotchyCoreTests/ContainerRuntimeTests.swift create mode 100644 Tests/KnotchyCoreTests/ContentReadinessTests.swift create mode 100644 Tests/KnotchyCoreTests/H264Tests.swift create mode 100644 Tests/KnotchyCoreTests/HostChecksTests.swift create mode 100644 Tests/KnotchyCoreTests/LinkIntakeTests.swift create mode 100644 Tests/KnotchyCoreTests/NetworkRouteTests.swift create mode 100644 Tests/KnotchyCoreTests/NotchGeometryTests.swift create mode 100644 Tests/KnotchyCoreTests/RuntimeInstallerTests.swift create mode 100644 Tests/KnotchyCoreTests/SessionLifecycleTests.swift create mode 100644 Tests/KnotchyCoreTests/SessionNewLinkTests.swift create mode 100644 Tests/KnotchyCoreTests/SessionPanelTests.swift create mode 100644 Tests/KnotchyCoreTests/SessionRuntimeInstallTests.swift create mode 100644 Tests/KnotchyCoreTests/SessionSetupRunTests.swift create mode 100644 Tests/KnotchyCoreTests/SessionSetupTests.swift create mode 100644 Tests/KnotchyCoreTests/SetupFlowTests.swift create mode 100644 Tests/KnotchyCoreTests/Support/Fakes.swift create mode 100644 Tests/KnotchyCoreTests/Support/Harness.swift create mode 100644 Tests/KnotchyCoreTests/Support/SetupFakes.swift create mode 100644 Tests/KnotchyCoreTests/Support/TestClock.swift create mode 100644 Tests/KnotchyCoreTests/Support/Waiting.swift create mode 100644 Tests/KnotchyCoreTests/WireProtocolTests.swift create mode 100644 assets/AppIcon.icns create mode 100644 assets/dmg-background.tiff create mode 100644 assets/knotchy-icon.svg create mode 100644 assets/knotchy-mark.svg create mode 100644 extensions/chrome/README.md create mode 100644 extensions/chrome/STORE.md create mode 100644 extensions/chrome/background.js create mode 100644 extensions/chrome/icons/icon-128.png create mode 100644 extensions/chrome/icons/icon-16.png create mode 100644 extensions/chrome/icons/icon-32.png create mode 100644 extensions/chrome/icons/icon-48.png create mode 100644 extensions/chrome/manifest.json create mode 100644 extensions/chrome/store/marquee-1400x560.png create mode 100644 extensions/chrome/store/promo-440x280.png create mode 100755 scripts/build-vm.sh create mode 100755 scripts/bundle-app.sh create mode 100644 scripts/icons/frame.swift create mode 100644 scripts/icons/knot.json create mode 100755 scripts/icons/knot.py create mode 100644 scripts/icons/render.swift create mode 100755 scripts/make-icons.sh create mode 100755 scripts/package-extension.sh create mode 100755 scripts/release.sh create mode 100644 scripts/route-server/Containerfile create mode 100644 scripts/route-server/serve.sh create mode 100755 scripts/run-vm.sh create mode 100755 scripts/test-egress.sh create mode 100755 scripts/test-routes.sh create mode 100644 vm/Containerfile create mode 100644 vm/fonts.conf create mode 100644 vm/knotchy-agent.c create mode 100755 vm/knotchy-init create mode 100644 vm/progress-gate.c create mode 100644 vm/progress-gate.h create mode 100644 vm/tests/test-progress-gate.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77bc72e --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.DS_Store + +.build/ +build/ +DerivedData/ +xcuserdata/ +.swiftpm/ +*.app + +snapshots/ + +a.out +*.o +*.dSYM/ +__pycache__/ +*.pyc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b26c32a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Aculix Technologies LLP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..27f8c31 --- /dev/null +++ b/Package.swift @@ -0,0 +1,14 @@ +// swift-tools-version: 6.2 +import PackageDescription + +let package = Package( + name: "Knotchy", + platforms: [.macOS(.v26)], + targets: [ + // Everything that isn't UI: session logic, protocol, geometry, VM backend. + .target(name: "KnotchyCore"), + .executableTarget(name: "Knotchy", dependencies: ["KnotchyCore"], exclude: ["Info.plist"]), + .executableTarget(name: "knotchyctl", dependencies: ["KnotchyCore"]), + .testTarget(name: "KnotchyCoreTests", dependencies: ["KnotchyCore"]), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c34fe0 --- /dev/null +++ b/README.md @@ -0,0 +1,192 @@ + + +# Knotchy + +Open suspicious links in a throwaway browser that lives in your Mac's notch. + +> **Status:** 0.1.0, the first release. It works, but it's young. If something breaks, please open an issue. + +Some links you'd rather not open. A shortened link in a text from a number you don't know. An "invoice" from a +company you've never dealt with. Knotchy opens them somewhere else: a small Linux virtual machine that exists for +that one link. The page appears in a panel under the notch, you look at it, and when you're done the whole machine +is deleted, along with anything the page did inside it. + +The VM can reach the internet. It can't reach your Mac or anything else on your network. + +## Requirements + +- A Mac with Apple silicon +- macOS 26 or later +- About 3 GB of free space for the first setup +- An administrator password, once, for Apple's installer + +## Install + +1. Download `Knotchy-0.1.0.dmg` from [Releases](https://github.com/aculix/knotchy/releases/latest). It's signed + and notarized, so macOS opens it without warnings. +2. Open the disk image and drag Knotchy to Applications. +3. Open Knotchy. A knot appears in the menu bar and the setup window opens. +4. Click **Set Up**, and Knotchy works through the list: + - It checks that your Mac can run virtual machines and has enough space. + - It downloads Apple's [`container`](https://github.com/apple/container) runtime (version 1.4.1, 118 MB), + checks it against the SHA-256 built into Knotchy, and opens it in Apple's Installer. Click through Installer + and enter your password there. Knotchy never asks for administrator rights itself. + - It starts the runtime, which downloads a Linux kernel the first time. + - It builds the browser VM image on your Mac. That takes about 3 minutes and downloads about 225 MB. +5. When every row has a green tick, click **Done**. + +If `container` 1.4 or newer is already installed, Knotchy uses it and goes straight to the build. You can close the +window at any point and setup carries on; **Set Up Knotchy…** in the menu bar brings it back. + +### The browser extension + +Safari and Mail need nothing extra. Chrome, Edge, Brave and Arc do, because they pass the Services menu a link's +text instead of its address. Until the extension is on the Chrome Web Store, install it by hand: + +1. Download `open-in-knotchy-1.0.0.zip` from [Releases](https://github.com/aculix/knotchy/releases/latest) and unzip + it somewhere you'll keep it. (The same files are in [`extensions/chrome`](extensions/chrome).) +2. Open `chrome://extensions` and turn on **Developer mode** in the top-right corner. +3. Click **Load unpacked** and choose the unzipped folder. +4. Right-click any link and choose **Open in Knotchy**. The first time, the browser asks whether to open Knotchy. + Allow it. + +The extension has one permission, adding that menu item, and only passes `http` and `https` links to the app. +It won't update itself, and deleting the folder removes it from the browser. + +## Opening links + +- In Safari, Mail and most other apps, right-click a link and choose **Services → Open in Knotchy**. It also works + on selected text that contains a link. +- In Chrome and other Chromium browsers, right-click a link and choose **Open in Knotchy**. +- Anywhere else, copy the link and press **⌃⌥⌘L**, or choose **Open Copied Link** from the menu bar. + +Only web links open. Anything else gets a brief "No web link found" in the notch. + +## Using the panel + +The page opens in a panel that drops down from the notch. Scroll, click and type as you would in any browser. The +toolbar has back, forward and reload, an address field, and a button that makes the panel bigger. + +Click anywhere outside the panel and it folds back into the notch, with a countdown beside it. Hover over the notch +to bring the page back. Leave it folded and the VM is destroyed when the countdown runs out, after 5 minutes unless +you change it. **Destroy** in the toolbar, or ⌘W, ends it right away. + +Knotchy shows one page at a time. Opening a new link replaces the current VM with a fresh one. + +## Settings + +The knot in the menu bar opens a menu with the open page (show it or destroy it), **Open Copied Link**, the +connection picker and **Rebuild VM**. **Settings…** has three sections. + +General sets how long a folded page lives, whether pages open in the small or large panel, the ⌃⌥⌘L shortcut, and +whether Knotchy opens at login. + +Network sends pages through an HTTP or SOCKS5 proxy, with a username and password if it needs them, or through a +WireGuard VPN. For a VPN, import the `.conf` files or the `.zip` your provider gives you; Mullvad, Proton VPN and IVPN +all offer them. If the proxy or VPN is down, pages don't load. They never fall back to your normal connection. +Proxies on your Mac or your local network are refused, because a page could reach your network through them. +Passwords and VPN keys are kept in your keychain. + +VM shows the runtime version and when the image was built, and has **Rebuild Now**. Rebuilding picks up WebKit +security fixes, and Knotchy suggests it once the image is two weeks old. + +## How it works + +There are two programs: the Mac app, and a small agent inside each VM. + +When a link comes in, the app checks that it's `http` or `https` and asks Apple's `container` tool to start a new VM +from the `local/knotchy-vm` image. That's Debian with WPE WebKit and GStreamer, given 4 CPU cores and 2 GB of memory. +Each VM gets a random name (`knotchy-` and 8 hex digits) and is deleted when it stops. + +The VM's first process is [`vm/knotchy-init`](vm/knotchy-init), a shell script. Before anything else runs, it sets up +an nftables firewall. On a direct connection the firewall allows the public internet and blocks private and +link-local addresses, carrier-grade NAT, IPv6, and the networks your Mac is on. With a proxy, the proxy is the only +thing the VM can reach. With WireGuard, it's the VPN server and the tunnel. If the firewall can't be put in place, +the VM stops instead of starting without it. Init then drops all its capabilities and starts the agent as an +unprivileged user, so nothing the browser runs can change the firewall. + +The agent, [`vm/knotchy-agent.c`](vm/knotchy-agent.c), loads the page in WPE WebKit at 1024 × 640, encodes what it +draws as H.264 with x264, and writes the frames to a Unix socket. `container` exposes that socket to the Mac as a +file, so there's no network connection between the two. The app decodes the video with VideoToolbox and draws it +in the panel. Clicks, scrolls, keys and address changes go back over the same socket. The message format is at the +top of the agent's source. + +Destroying a page stops and deletes its VM. If Knotchy quits with a VM running, it deletes it on the way out, and +any VM left behind by a crash is deleted at the next launch. + +The image is built on your Mac from the [`vm`](vm) folder that ships inside the app, so what runs in the VM is what's +in this repository. Proxy passwords and VPN keys reach the VM through a shared folder that init reads, deletes and +unmounts before the browser starts. They're never passed as environment variables, which `container` saves to disk. + +### What's where + +| Path | Contents | +|---|---| +| `Sources/Knotchy` | The Mac app: menu bar, notch panel, setup window and Settings | +| `Sources/KnotchyCore` | The session state machine, the VM backend, the stream protocol and decoder, setup logic | +| `Sources/knotchyctl` | A command-line tool that opens a link in a VM and saves a screenshot, for testing without the UI | +| `Tests` | Unit tests for KnotchyCore; none of them need a VM | +| `vm` | The VM image: `Containerfile`, `knotchy-init` and `knotchy-agent.c` | +| `extensions/chrome` | The Open in Knotchy extension | +| `scripts` | Build, release, test and icon scripts | + +## Known limitations + +- The "Isolated" badge means isolated, not safe. A page can still try to trick you inside the VM. +- There's no sound. Clipboard, downloads, popups and links that open a new window stay inside the VM or don't work. +- The picture is streamed at 1×, so it's slightly soft on Retina screens. +- Proxy and VPN routes are IPv4 only, a WireGuard config can have only one peer, and OpenVPN isn't supported. +- The firewall runs in the VM's own kernel, so a kernel exploit inside the VM could get around it. +- A site can still reach your router through your network's public address (hairpin NAT), and routes that a VPN + on your Mac adds for public addresses aren't blocked. Knotchy reads your Mac's networks when each page starts. +- Unprivileged user namespaces are on in the VM, because WebKit's sandbox needs them. + +## Building from source + +You need a Mac with Apple silicon, macOS 26 or later, Xcode 26 or later, and Apple's `container` installed. + +```bash +git clone https://github.com/aculix/knotchy.git +cd knotchy +scripts/bundle-app.sh +``` + +`bundle-app.sh` builds Knotchy.app and installs it in `~/Applications`. It signs with your Developer ID certificate +if you have one and ad hoc if you don't, which is fine for your own Mac. The app builds its VM image the first time +it opens, the same as a downloaded copy. + +The other scripts: + +- `swift test` runs the unit tests. +- `scripts/build-vm.sh` builds the VM image from the command line. +- `scripts/test-egress.sh` starts a VM and checks what it can and can't reach. Run it after any change in `vm`. +- `scripts/test-routes.sh` checks the proxy and VPN routes, kill switch included. Build its test server first with + `container build --dns 1.1.1.1 -t local/knotchy-route-server scripts/route-server`. +- `scripts/release.sh` builds a signed, notarized disk image. It needs a Developer ID certificate and notarization + credentials saved with `xcrun notarytool store-credentials`. +- `scripts/package-extension.sh` zips the browser extension. +- `scripts/make-icons.sh` redraws every icon from the knot in `scripts/icons/knot.py`. + +A VM image you build yourself is exactly as trustworthy as the checkout you built it from. + +## Uninstalling + +Quit Knotchy and move it to the Trash. Then remove its VM image in Terminal: + +```bash +container image rm local/knotchy-vm +``` + +If nothing else on your Mac uses Apple's `container`, you can remove it as well. This deletes all of its data: + +```bash +/usr/local/bin/uninstall-container.sh -d +``` + +## Security + +To report a security problem, please use GitHub's private reporting. [SECURITY.md](SECURITY.md) has the details. + +## License + +MIT. See [LICENSE](LICENSE). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2bab937 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security + +Knotchy exists to keep untrusted pages away from your Mac, so a security bug here is the worst kind of bug. If you +find one, thank you. + +## Reporting a vulnerability + +Please report it privately: on this repository's **Security** tab, choose **Report a vulnerability**. Don't open a +public issue for it. + +Tell us what you found, how to reproduce it, and which versions of Knotchy and macOS you used. We'll reply within a +few days, and we'll have a fix or a plan before anything is made public. + +## What counts + +- A page in the VM reaching your Mac, your local network, or anything else the firewall should block +- Getting around a proxy or VPN route's kill switch +- Knotchy running code on the Mac, or installing anything other than Apple's pinned `container` package +- Proxy passwords or VPN keys leaking from the keychain or from the VM's secrets share + +A page taking over the browser inside the VM is expected; that's what the VM is for. Escaping the VM is what counts. + +## Supported versions + +Only the latest release gets fixes. diff --git a/Sources/Knotchy/AppDelegate.swift b/Sources/Knotchy/AppDelegate.swift new file mode 100644 index 0000000..5ad9081 --- /dev/null +++ b/Sources/Knotchy/AppDelegate.swift @@ -0,0 +1,249 @@ +import AppKit +import KnotchyCore + +@MainActor +final class AppDelegate: NSObject, NSApplicationDelegate { + let backend: ContainerCLIBackend + let controller: SessionController + let services: ServicesProvider + let settings = AppSettings() + let vmStatus: VMStatus + let unsupported: Unsupported? + let setupFlow: SetupFlow + var isSupportedMac: Bool { unsupported == nil } + private(set) lazy var setupWindow = SetupWindowController( + unsupported.map(SetupWindowController.Content.unsupported) ?? .setup(setupFlow)) + private(set) lazy var settingsWindow = SettingsWindowController(app: self) + private var notch: NotchWindowController? + private var copiedLinkHotKey: GlobalHotKey? + private var wantsCopiedLinkHotKey = false + private var signalSources: [DispatchSourceSignal] = [] + private var quitting = false + + override init() { + unsupported = Self.detectUnsupported() + let vmSources = Bundle.main.resourceURL?.appendingPathComponent("vm") + let hasSources = vmSources.map { FileManager.default.fileExists(atPath: $0.appendingPathComponent("Containerfile").path) } ?? false + backend = ContainerCLIBackend(vmSources: hasSources ? vmSources : nil) + controller = SessionController( + backend: backend, + connector: StreamClient(renderSize: SessionController.renderSize), + diskSpace: SystemDiskSpace(), + hostNetworks: { HostNetworks.current() }, + requestQuit: { NSApp.terminate(nil) } + ) + services = ServicesProvider(controller: controller) + vmStatus = VMStatus(backend: backend, controller: controller) + setupFlow = SetupFlow(host: SystemHost(), installer: SystemRuntimeInstaller(), driver: controller) + super.init() + controller.quitsWhenIdle = false + controller.showSetup = { [weak self] in self?.setupWindow.show() } + if isSupportedMac { + settings.onChange = { [weak self] in self?.applySettings() } + applySettings() + } else { + services.redirect = { [weak self] in self?.setupWindow.show() } + } + } + + private static func detectUnsupported() -> Unsupported? { + #if DEBUG + if ProcessInfo.processInfo.environment["KNOTCHY_PRETEND"] == "no-virtualization" { return .noVirtualization } + #endif + return SystemHost.unsupported() + } + + private func applySettings() { + settings.apply(to: controller) + backend.setNetworkRoute(settings.route) + guard settings.copiedLinkShortcut != wantsCopiedLinkHotKey else { return } + wantsCopiedLinkHotKey = settings.copiedLinkShortcut + if wantsCopiedLinkHotKey { + copiedLinkHotKey = GlobalHotKey.openCopiedLink { [services] in services.openCopiedLink() } + if copiedLinkHotKey == nil { ServicesLog.write("\(GlobalHotKey.openCopiedLinkLabel) is taken by another app") } + } else { + copiedLinkHotKey = nil + } + } + + func applicationWillFinishLaunching(_ notification: Notification) { + NSApp.setActivationPolicy(.accessory) + // Registered before launch finishes, so a Services request that launched the app isn't lost. + NSApp.servicesProvider = services + NSUpdateDynamicServices() + } + + func applicationDidFinishLaunching(_ notification: Notification) { + #if DEBUG + if let directory = DebugTour.argument(after: "--setup-snapshot") { + Task { + await DebugTour.snapshotSetup(app: self, to: URL(fileURLWithPath: directory)) + NSApp.terminate(nil) + } + return + } + #endif + guard isSupportedMac else { return setupWindow.show() } + notch = NotchWindowController(controller: controller, routeLabel: { [backend] in backend.lastStartedRoute.label }) + vmStatus.refresh() + + // Route Ctrl-C, kill and logout through applicationShouldTerminate so the VMs get destroyed. + for sig in [SIGINT, SIGTERM, SIGHUP] { + signal(sig, SIG_IGN) + let source = DispatchSource.makeSignalSource(signal: sig, queue: .main) + source.setEventHandler { NSApp.terminate(nil) } + source.resume() + signalSources.append(source) + } + + #if DEBUG + let all = CommandLine.arguments + if let index = all.firstIndex(of: "--tour"), index + 2 < all.count, let url = URL(string: all[index + 2]), + let notch { + let directory = URL(fileURLWithPath: all[index + 1]) + Task { await DebugTour.run(controller: controller, snapshot: { notch.snapshot(to: directory, name: $0) }, url: url) } + return + } + if all.contains("--settings-timing") { + Task { + await DebugTour.timeNetworkSwitching(app: self) + NSApp.terminate(nil) + } + return + } + if let index = all.firstIndex(of: "--settings-snapshot"), index + 1 < all.count { + let directory = URL(fileURLWithPath: all[index + 1]) + Task { + await DebugTour.snapshotSettings(app: self, to: directory) + NSApp.terminate(nil) + } + return + } + if let index = all.firstIndex(of: "--tour-timing"), index + 1 < all.count, let url = URL(string: all[index + 1]) { + Task { await DebugTour.run(controller: controller, snapshot: nil, url: url) } + return + } + #endif + + Task { await openSetupIfNeeded() } + let arguments = CommandLine.arguments.dropFirst().filter { !$0.hasPrefix("-") } + if let first = arguments.first { controller.open(text: first) } + } + + private func openSetupIfNeeded() async { + let runtime = await backend.runtimeStatus() + let completed = UserDefaults.standard.bool(forKey: SetupLaunchRule.completedKey) + if SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: completed, runtime: runtime) { + setupWindow.show() + } + } + + // From the browser extensions: Chrome's Services menu never sees a link's address. + func application(_ application: NSApplication, open urls: [URL]) { + guard isSupportedMac else { return setupWindow.show() } + for url in urls where url.scheme == "knotchy" && url.host() == "open" { + let link = URLComponents(url: url, resolvingAgainstBaseURL: false)? + .queryItems?.first { $0.name == "url" }?.value + ServicesLog.write("knotchy:// request, link \(link == nil ? "missing" : "present")") + if let link, let target = URL(string: link) { + controller.open(target) + } else { + controller.open(text: "") + } + } + } + + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + guard !quitting else { return .terminateNow } + quitting = true + setupFlow.cancel() + if setupFlow.isDownloading { SystemRuntimeInstaller.removeLeftovers() } + // Not Now mid-build closes the window before the build ends. + if setupFlow.allRequiredDone { UserDefaults.standard.set(true, forKey: SetupLaunchRule.completedKey) } + // Synchronous on purpose: terminate: often runs inside a non-reentrant main-queue block; awaiting here deadlocked. + notch?.hide() + CATransaction.flush() + CLIRunner.stopBuilds() + controller.closeBackendChain() + let ids = Array(controller.liveIDs.snapshot) + if !ids.isEmpty { backend.destroyBlocking(ids: ids, deadline: .seconds(20)) } + return .terminateNow + } +} + +@MainActor +final class ServicesProvider: NSObject { + private let controller: SessionController + /// In minimal mode, requests only bring the explanation window forward, so the calling app gets a normal reply. + var redirect: (@MainActor () -> Void)? + + init(controller: SessionController) { + self.controller = controller + } + + @objc func openLink(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer) { + open(from: pasteboard, service: "Open in Knotchy") + } + + @objc func openCopiedLink(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer) { + open(from: .general, service: "Open Copied Link in Knotchy") + } + + func openCopiedLink() { + open(from: .general, service: "Shortcut") + } + + private func open(from pasteboard: NSPasteboard, service: String) { + if let redirect { return redirect() } + let types = pasteboard.types?.map(\.rawValue) ?? [] + let urls = pasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL] ?? [] + let url: URL? + let source: String + if let found = urls.first(where: { LinkIntake.validate($0) != nil }) { + (url, source) = (found, "url") + } else if let html = pasteboard.string(forType: .html), let found = LinkIntake.extractURL(fromHTML: html) { + // A selected hyperlink's text rarely holds the address; its HTML does. + (url, source) = (found, "html") + } else if let found = Self.firstLink(inRTF: pasteboard.data(forType: .rtf)) { + (url, source) = (found, "rtf") + } else if let text = pasteboard.string(forType: .string), let found = LinkIntake.extractURL(from: text) { + (url, source) = (found, "text") + } else { + (url, source) = (nil, "none") + } + ServicesLog.write("\(service): types \(types), link from \(source)") + if let url { + controller.open(url) + } else { + controller.open(text: "") + } + } + + private static func firstLink(inRTF data: Data?) -> URL? { + guard let data, let text = NSAttributedString(rtf: data, documentAttributes: nil) else { return nil } + var found: URL? + text.enumerateAttribute(.link, in: NSRange(location: 0, length: text.length)) { value, _, stop in + let url = (value as? URL) ?? (value as? String).flatMap(URL.init(string:)) + if let url, let valid = LinkIntake.validate(url) { + found = valid + stop.pointee = true + } + } + return found + } +} + +// Pasteboard types and sources only; never log the links themselves. +enum ServicesLog { + static func write(_ line: String) { + let url = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Logs/Knotchy.log") + let entry = "\(ISO8601DateFormatter().string(from: Date())) \(line)\n" + if let handle = try? FileHandle(forWritingTo: url) { + handle.seekToEndOfFile() + handle.write(Data(entry.utf8)) + try? handle.close() + } else { + try? Data(entry.utf8).write(to: url) + } + } +} diff --git a/Sources/Knotchy/AppSettings.swift b/Sources/Knotchy/AppSettings.swift new file mode 100644 index 0000000..865b3db --- /dev/null +++ b/Sources/Knotchy/AppSettings.swift @@ -0,0 +1,293 @@ +import AppKit +import Carbon.HIToolbox +import KnotchyCore +import Observation +import ServiceManagement + +@MainActor +@Observable +final class AppSettings { + static let autoDestroyChoices = [1, 2, 5, 10, 15, 30] + + private let defaults = UserDefaults.standard + @ObservationIgnored var onChange: () -> Void = {} + + var autoDestroyMinutes: Int { + didSet { save(autoDestroyMinutes, "autoDestroyMinutes") } + } + + var opensLarge: Bool { + didSet { save(opensLarge, "opensLarge") } + } + + var copiedLinkShortcut: Bool { + didSet { save(copiedLinkShortcut, "copiedLinkShortcut") } + } + + // MARK: Network + + enum NetworkMode: String, CaseIterable { + case direct, proxy, vpn + } + + var networkMode: NetworkMode { + didSet { save(networkMode.rawValue, "networkMode") } + } + + var proxyKind: ProxySettings.Kind { + didSet { save(proxyKind.rawValue, "proxyKind") } + } + + var proxyHost: String { + didSet { save(proxyHost, "proxyHost") } + } + + var proxyPort: Int { + didSet { save(proxyPort, "proxyPort") } + } + + var proxyUsername: String { + didSet { save(proxyUsername, "proxyUsername") } + } + + // Debounced: after an app update, every keychain write can raise a keychain prompt. + var proxyPassword: String { + didSet { + passwordSave?.cancel() + let password = proxyPassword + passwordSave = Task { [weak self] in + try? await Task.sleep(for: .milliseconds(500)) + guard !Task.isCancelled, let self else { return } + Keychain.write(password, for: "proxy-password") + self.defaults.set(!password.isEmpty, forKey: "proxyHasPassword") + } + onChange() + } + } + @ObservationIgnored private var passwordSave: Task? + + // In the keychain, not UserDefaults: WireGuard configs hold private keys. + private(set) var wireGuardConfigs: [String: String] { + didSet { + let json = (try? JSONEncoder().encode(wireGuardConfigs)).flatMap { String(data: $0, encoding: .utf8) } ?? "" + Keychain.write(wireGuardConfigs.isEmpty ? "" : json, for: "wireguard-configs") + onChange() + } + } + + var wireGuardServer: String { + didSet { save(wireGuardServer, "wireGuardServer") } + } + + var selectedWireGuard: WireGuardConfig? { + wireGuardConfigs[wireGuardServer].flatMap { try? WireGuardConfig.parse($0) } + } + + var wireGuardNames: [String] { + wireGuardConfigs.keys.sorted { $0.localizedStandardCompare($1) == .orderedAscending } + } + + // An unusable proxy or VPN must fail the start, never fall back to a direct connection. + var route: NetworkRoute { + switch networkMode { + case .direct: + return .direct + case .proxy: + let host = proxyHost.trimmingCharacters(in: .whitespaces) + guard !host.isEmpty, (1...65535).contains(proxyPort) else { + return .unusable(reason: "The proxy isn't set up. Add its server and port in Settings → Network, or switch to Direct.") + } + return .proxy(ProxySettings(kind: proxyKind, host: host, port: proxyPort, + username: proxyUsername, password: proxyPassword)) + case .vpn: + guard let text = wireGuardConfigs[wireGuardServer] else { + return .unusable(reason: "No VPN server is chosen. Import a WireGuard config in Settings → Network, or switch to Direct.") + } + do { + var config = try WireGuardConfig.parse(text) + config.name = wireGuardServer + return .wireGuard(config) + } catch { + return .unusable(reason: "The WireGuard config “\(wireGuardServer)” \(error.message).") + } + } + } + + /// A hint for Settings only; sessions enforce `ProxySettings.isLocal` on the resolved address. + var proxyIsLocal: Bool { + let host = proxyHost.trimmingCharacters(in: .whitespaces).lowercased() + if host == "localhost" || [".local", ".lan", ".home", ".internal"].contains(where: host.hasSuffix) { return true } + let octets = host.split(separator: ".", omittingEmptySubsequences: false).compactMap { Int($0) } + guard octets.count == 4 else { return false } + let (a, b) = (octets[0], octets[1]) + return a == 10 || a == 127 || (a == 172 && (16...31).contains(b)) || (a == 192 && b == 168) + || (a == 100 && (64...127).contains(b)) || (a == 169 && b == 254) + } + + func importWireGuard(from urls: [URL]) async -> String? { + var found: [(String, String)] = [] + var problems: [String] = [] + for url in urls { + let files: [(String, String)] + let size = (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0 + if url.pathExtension.lowercased() == "zip" { + files = await Task.detached { Self.confFiles(inZip: url) }.value + if files.isEmpty { problems.append("\(url.lastPathComponent) has no .conf files") } + } else if size <= 64 * 1024, let text = try? String(contentsOf: url, encoding: .utf8) { + files = [(url.deletingPathExtension().lastPathComponent, text)] + } else { + problems.append("couldn't read \(url.lastPathComponent)") + continue + } + for (name, text) in files { + do { + _ = try WireGuardConfig.parse(text) + found.append((name, text)) + } catch { + problems.append("“\(name)” \(error.message)") + } + } + } + guard !found.isEmpty else { return problems.first.map { "Nothing imported: \($0)." } ?? "Nothing imported." } + var configs = wireGuardConfigs + for (name, text) in found { configs[name] = text } + wireGuardConfigs = configs + wireGuardServer = found.map(\.0).sorted { $0.localizedStandardCompare($1) == .orderedAscending }[0] + networkMode = .vpn + return problems.isEmpty ? nil : "Imported \(found.count); skipped: \(problems.joined(separator: "; "))." + } + + func removeWireGuardConfigs() { + wireGuardConfigs = [:] + wireGuardServer = "" + } + + private nonisolated static func confFiles(inZip url: URL) -> [(String, String)] { + // Output past `limit` fails the read, so a zip bomb can't exhaust memory. + func unzip(_ arguments: [String], limit: Int) -> Data? { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip") + process.arguments = arguments + let pipe = Pipe() + process.standardOutput = pipe + process.standardError = FileHandle.nullDevice + guard (try? process.run()) != nil else { return nil } + let pid = process.processIdentifier + let watchdog = DispatchWorkItem { kill(pid, SIGKILL) } + DispatchQueue.global().asyncAfter(deadline: .now() + 10, execute: watchdog) + defer { watchdog.cancel() } + var data = Data() + while let chunk = try? pipe.fileHandleForReading.read(upToCount: 16 * 1024), !chunk.isEmpty { + data.append(chunk) + if data.count > limit { + process.terminate() + process.waitUntilExit() + return nil + } + } + process.waitUntilExit() + return process.terminationStatus == 0 ? data : nil + } + guard let listing = unzip(["-Z1", url.path], limit: 1024 * 1024).flatMap({ String(data: $0, encoding: .utf8) }) + else { return [] } + let entries = listing.split(whereSeparator: \.isNewline).map(String.init) + .filter { $0.lowercased().hasSuffix(".conf") && !$0.hasPrefix("__MACOSX") } + .prefix(500) + return entries.compactMap { entry in + guard let data = unzip(["-p", url.path, entry], limit: 64 * 1024), + let text = String(data: data, encoding: .utf8) else { return nil } + return (URL(fileURLWithPath: entry).deletingPathExtension().lastPathComponent, text) + } + } + + init() { + defaults.register(defaults: ["autoDestroyMinutes": 5, "opensLarge": false, "copiedLinkShortcut": true, + "networkMode": "direct", "proxyKind": "http", "proxyPort": 8080]) + autoDestroyMinutes = defaults.integer(forKey: "autoDestroyMinutes") + opensLarge = defaults.bool(forKey: "opensLarge") + copiedLinkShortcut = defaults.bool(forKey: "copiedLinkShortcut") + networkMode = NetworkMode(rawValue: defaults.string(forKey: "networkMode") ?? "") ?? .direct + proxyKind = ProxySettings.Kind(rawValue: defaults.string(forKey: "proxyKind") ?? "") ?? .http + proxyHost = defaults.string(forKey: "proxyHost") ?? "" + proxyPort = defaults.integer(forKey: "proxyPort") + let username = defaults.string(forKey: "proxyUsername") ?? "" + let server = defaults.string(forKey: "wireGuardServer") ?? "" + proxyUsername = username + wireGuardServer = server + // Skip the keychain (and its possible prompt) unless a secret was saved. + proxyPassword = defaults.bool(forKey: "proxyHasPassword") || !username.isEmpty + ? Keychain.read("proxy-password") ?? "" : "" + wireGuardConfigs = server.isEmpty ? [:] : + (Keychain.read("wireguard-configs")?.data(using: .utf8)) + .flatMap { try? JSONDecoder().decode([String: String].self, from: $0) } ?? [:] + } + + private func save(_ value: Any, _ key: String) { + defaults.set(value, forKey: key) + onChange() + } + + func apply(to controller: SessionController) { + controller.idleDestroyDelay = .seconds(autoDestroyMinutes * 60) + controller.opensExpanded = opensLarge + } +} + +@MainActor +enum LoginItem { + static var isEnabled: Bool { + SMAppService.mainApp.status == .enabled + } + + static func set(_ enabled: Bool) -> String? { + do { + if enabled { + try SMAppService.mainApp.register() + } else { + try SMAppService.mainApp.unregister() + } + return nil + } catch { + return error.localizedDescription + } + } +} + +// Carbon, because its hot keys need no Accessibility permission. +@MainActor +final class GlobalHotKey { + static let openCopiedLinkLabel = "⌃⌥⌘L" + + private var hotKey: EventHotKeyRef? + private var handler: EventHandlerRef? + private let action: () -> Void + + static func openCopiedLink(_ action: @escaping () -> Void) -> GlobalHotKey? { + GlobalHotKey(keyCode: UInt32(kVK_ANSI_L), modifiers: UInt32(controlKey | optionKey | cmdKey), action: action) + } + + private init?(keyCode: UInt32, modifiers: UInt32, action: @escaping () -> Void) { + self.action = action + var type = EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)) + let installed = InstallEventHandler(GetApplicationEventTarget(), { _, _, userData in + guard let userData else { return OSStatus(eventNotHandledErr) } + let hotKey = Unmanaged.fromOpaque(userData).takeUnretainedValue() + MainActor.assumeIsolated { hotKey.action() } + return noErr + }, 1, &type, Unmanaged.passUnretained(self).toOpaque(), &handler) + guard installed == noErr else { return nil } + let id = EventHotKeyID(signature: OSType(0x4B4E_4F54), id: 1) + // Exclusive, so a shortcut another app owns fails instead of silently sharing. + guard RegisterEventHotKey(keyCode, modifiers, id, GetApplicationEventTarget(), + OptionBits(kEventHotKeyExclusive), &hotKey) == noErr else { + RemoveEventHandler(handler) + handler = nil // deinit also runs after a failed init; don't remove it twice. + return nil + } + } + + isolated deinit { + if let hotKey { UnregisterEventHotKey(hotKey) } + if let handler { RemoveEventHandler(handler) } + } +} diff --git a/Sources/Knotchy/ChromeExtension.swift b/Sources/Knotchy/ChromeExtension.swift new file mode 100644 index 0000000..be1ce37 --- /dev/null +++ b/Sources/Knotchy/ChromeExtension.swift @@ -0,0 +1,28 @@ +import AppKit + +enum ChromeExtension { + /// Set once the Chrome Web Store assigns the item ID; until then the setup window hides the row. + static let storeURL: URL? = nil + + struct Browser: Equatable { + let name: String + let app: URL + } + + // Chromium browsers all install from the Chrome Web Store. + private static let candidates = [("Chrome", "com.google.Chrome"), ("Edge", "com.microsoft.edgemac"), + ("Brave", "com.brave.Browser"), ("Arc", "company.thebrowser.Browser")] + + static func installedBrowser() -> Browser? { + guard storeURL != nil else { return nil } + for (name, id) in candidates { + if let app = NSWorkspace.shared.urlForApplication(withBundleIdentifier: id) { return Browser(name: name, app: app) } + } + return nil + } + + static func open(in browser: Browser) { + guard let storeURL else { return } + NSWorkspace.shared.open([storeURL], withApplicationAt: browser.app, configuration: NSWorkspace.OpenConfiguration()) + } +} diff --git a/Sources/Knotchy/DebugTour.swift b/Sources/Knotchy/DebugTour.swift new file mode 100644 index 0000000..ffb54cb --- /dev/null +++ b/Sources/Knotchy/DebugTour.swift @@ -0,0 +1,164 @@ +#if DEBUG +import AppKit +import KnotchyCore +import QuartzCore +import SwiftUI + +// `Knotchy --tour ` saves PNGs of each state; `--tour-timing ` reports dropped frames. +// Checks the UI without screen recording permission; video doesn't appear in the snapshots. +@MainActor +enum DebugTour { + static func run(controller: SessionController, snapshot: ((String) -> Void)?, url: URL) async { + let meter = FrameMeter() + func wait(_ seconds: Double) async { try? await Task.sleep(for: .seconds(seconds)) } + func step(_ name: String, _ times: [Double], _ action: () -> Void) async { + if snapshot == nil { meter.start() } + action() + var elapsed = 0.0 + for time in times { + await wait(time - elapsed) + elapsed = time + snapshot?("\(name)-\(Int(time * 1000))ms") + } + if snapshot == nil { print("\(name): \(meter.stop())") } + } + + await step("01-no-link", [0.08, 0.2, 0.6, 1.0]) { controller.open(text: "no link here") } + await wait(2.5) + await step("02-opening", [0.06, 0.12, 0.2, 0.3, 0.45, 0.7, 1.2]) { controller.open(url) } + for _ in 0..<300 where controller.state != .live(.peek) { await wait(0.1) } + await wait(1) + await step("04-expand", [0.08, 0.16, 0.25, 0.4, 1.0]) { controller.expand() } + await step("05-shrink", [0.08, 0.16, 0.3, 1.0]) { controller.shrink() } + await step("06-fold", [0.06, 0.12, 0.2, 0.35, 1.0]) { controller.clickedOutside() } + await wait(0.5) + await step("07-unfold", [0.06, 0.12, 0.2, 0.35, 1.0]) { controller.pointerEntered() } + await step("08-destroy", [0.08, 0.2, 0.4, 0.8, 1.6, 2.2, 2.6]) { controller.destroy() } + await wait(1) + await step("09-reopen", [0.06, 0.12, 0.2, 0.3, 0.45, 0.7, 1.2]) { controller.open(url) } + for _ in 0..<300 where controller.state != .live(.peek) { await wait(0.1) } + await step("10-destroy", [0.3, 2.6]) { controller.destroy() } + await wait(1) + NSApp.terminate(nil) + } + + static func argument(after flag: String) -> String? { + let all = CommandLine.arguments + guard let index = all.firstIndex(of: flag), index + 1 < all.count else { return nil } + return all[index + 1] + } + + /// `Knotchy --setup-snapshot `; with `KNOTCHY_PRETEND=no-virtualization` it shows minimal mode. + static func snapshotSetup(app: AppDelegate, to directory: URL) async { + app.setupWindow.show() + try? await Task.sleep(for: .seconds(5)) + guard let window = app.setupWindow.window, let view = window.contentView else { return } + print("setup: window \(window.frame.size)") + DebugSnapshot.write(view, to: directory.appendingPathComponent("setup.png")) + } +} + +extension DebugTour { + static func snapshotSettings(app: AppDelegate, to directory: URL) async { + let settings = app.settings + let saved = (settings.networkMode, settings.proxyHost) + let cases: [(String, SettingsTab, AppSettings.NetworkMode)] = [ + ("general", .general, .direct), ("network-direct", .network, .direct), + ("network-proxy", .network, .proxy), ("network-vpn", .network, .vpn), ("vm", .vm, .direct), + ] + for (name, section, mode) in cases { + settings.networkMode = mode + if mode == .proxy { settings.proxyHost = "192.168.1.20" } + app.settingsWindow.show(section) + try? await Task.sleep(for: .seconds(0.8)) + guard let window = app.settingsWindow.window, let view = window.contentView else { continue } + print("\(name): window \(window.frame.size)") + DebugSnapshot.write(view, to: directory.appendingPathComponent("settings-\(name).png")) + } + app.settingsWindow.window?.orderOut(nil) + (settings.networkMode, settings.proxyHost) = saved + } + + static func timeNetworkSwitching(app: AppDelegate) async { + let settings = app.settings + let saved = settings.networkMode + settings.networkMode = .direct + app.settingsWindow.show(.general) + try? await Task.sleep(for: .seconds(1)) + let meter = FrameMeter() + let steps: [(String, () -> Void)] = [ + ("section network", { app.settingsWindow.show(.network) }), + ("mode proxy", { settings.networkMode = .proxy }), + ("mode vpn", { settings.networkMode = .vpn }), + ("mode direct", { settings.networkMode = .direct }), + ("section vm", { app.settingsWindow.show(.vm) }), + ("section general", { app.settingsWindow.show(.general) }), + ("section network", { app.settingsWindow.show(.network) }), + ] + for (name, step) in steps { + meter.start() + step() + try? await Task.sleep(for: .seconds(0.8)) + let size = app.settingsWindow.window?.frame.size ?? .zero + print("\(name) (window \(Int(size.width))x\(Int(size.height))): \(meter.stop())") + } + app.settingsWindow.window?.orderOut(nil) + settings.networkMode = saved + } +} + +enum DebugSnapshot { + @MainActor + static func write(_ view: NSView, to url: URL) { + guard let layer = view.layer, let window = view.window else { return } + let scale = window.backingScaleFactor + let size = view.bounds.size + guard let context = CGContext(data: nil, width: Int(size.width * scale), height: Int(size.height * scale), + bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpace(name: CGColorSpace.sRGB)!, + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return } + context.setFillColor(CGColor(gray: 0.55, alpha: 1)) + context.fill(CGRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale)) + context.scaleBy(x: scale, y: scale) + if view.isFlipped || !layer.isGeometryFlipped { + context.translateBy(x: 0, y: size.height) + context.scaleBy(x: 1, y: -1) + } + (layer.presentation() ?? layer).render(in: context) + guard let image = context.makeImage(), + let destination = CGImageDestinationCreateWithURL(url as CFURL, "public.png" as CFString, 1, nil) else { return } + CGImageDestinationAddImage(destination, image, nil) + CGImageDestinationFinalize(destination) + } +} + +// SwiftUI animates on the main thread, so a late display-link callback is a dropped frame. +@MainActor +final class FrameMeter: NSObject { + private var link: CADisplayLink? + private var stamps: [CFTimeInterval] = [] + private var nominal: CFTimeInterval = 1.0 / 60 + + func start() { + stamps = [] + link = (NSScreen.main ?? NSScreen.screens[0]).displayLink(target: self, selector: #selector(tick(_:))) + link?.add(to: .main, forMode: .common) + } + + @objc private func tick(_ link: CADisplayLink) { + stamps.append(CACurrentMediaTime()) + nominal = link.targetTimestamp - link.timestamp + } + + func stop() -> String { + link?.invalidate() + link = nil + let intervals = zip(stamps.dropFirst(), stamps).map { $0 - $1 } + func ms(_ t: CFTimeInterval) -> String { String(format: "%.0f", t * 1000) } + // "duration@end", both in ms, end measured from the start. + let late = zip(intervals, stamps.dropFirst()).filter { $0.0 > nominal * 1.5 } + .map { "\(ms($0.0))@\(ms($0.1 - (stamps.first ?? 0)))" } + return "\(stamps.count) frames at \(ms(nominal)) ms, worst \(ms(intervals.max() ?? 0)) ms, " + + "\(late.count) late \(late)" + } +} +#endif diff --git a/Sources/Knotchy/Info.plist b/Sources/Knotchy/Info.plist new file mode 100644 index 0000000..fdc3b94 --- /dev/null +++ b/Sources/Knotchy/Info.plist @@ -0,0 +1,75 @@ + + + + + CFBundleIdentifier + aculix.knotchy.app + CFBundleName + Knotchy + CFBundleDisplayName + Knotchy + CFBundleExecutable + Knotchy + CFBundleIconFile + AppIcon + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 26.0 + LSUIElement + + NSHumanReadableCopyright + © 2026 Aculix Technologies LLP. MIT License. + CFBundleURLTypes + + + CFBundleURLName + aculix.knotchy.open + CFBundleURLSchemes + + knotchy + + + + NSServices + + + NSMenuItem + + default + Open in Knotchy + + NSMessage + openLink + NSPortName + Knotchy + NSSendTypes + + public.url + public.html + public.rtf + public.utf8-plain-text + + NSRequiredContext + + + + NSMenuItem + + default + Open Copied Link in Knotchy + + NSMessage + openCopiedLink + NSPortName + Knotchy + NSRequiredContext + + + + + diff --git a/Sources/Knotchy/Keychain.swift b/Sources/Knotchy/Keychain.swift new file mode 100644 index 0000000..92e112f --- /dev/null +++ b/Sources/Knotchy/Keychain.swift @@ -0,0 +1,38 @@ +import Foundation +import Security + +enum Keychain { + private static let service = "aculix.knotchy.app" + + static func read(_ account: String) -> String? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: account, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne, + ] + var result: AnyObject? + guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess, let data = result as? Data else { return nil } + return String(data: data, encoding: .utf8) + } + + static func write(_ value: String, for account: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: account, + ] + guard !value.isEmpty else { + SecItemDelete(query as CFDictionary) + return + } + let data = Data(value.utf8) + if SecItemUpdate(query as CFDictionary, [kSecValueData as String: data] as CFDictionary) == errSecItemNotFound { + var item = query + item[kSecValueData as String] = data + item[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked + SecItemAdd(item as CFDictionary, nil) + } + } +} diff --git a/Sources/Knotchy/KnotGlyph.swift b/Sources/Knotchy/KnotGlyph.swift new file mode 100644 index 0000000..998c48f --- /dev/null +++ b/Sources/Knotchy/KnotGlyph.swift @@ -0,0 +1,29 @@ +// Generated by scripts/icons/knot.py; run scripts/make-icons.sh to change it. +import AppKit + +enum KnotGlyph { + static let strands: [[CGPoint]] = [ + [CGPoint(x: 7.27, y: 5.99), CGPoint(x: 7.15, y: 6.15), CGPoint(x: 7.04, y: 6.32), CGPoint(x: 6.93, y: 6.49), CGPoint(x: 6.82, y: 6.67), CGPoint(x: 6.72, y: 6.84), CGPoint(x: 6.62, y: 7.02), CGPoint(x: 6.53, y: 7.20), CGPoint(x: 6.43, y: 7.39), CGPoint(x: 6.35, y: 7.57), CGPoint(x: 6.27, y: 7.76), CGPoint(x: 6.19, y: 7.94), CGPoint(x: 6.11, y: 8.13), CGPoint(x: 6.05, y: 8.32), CGPoint(x: 5.98, y: 8.51), CGPoint(x: 5.92, y: 8.70), CGPoint(x: 5.86, y: 8.89), CGPoint(x: 5.81, y: 9.09), CGPoint(x: 5.77, y: 9.28), CGPoint(x: 5.72, y: 9.47), CGPoint(x: 5.69, y: 9.66), CGPoint(x: 5.65, y: 9.85), CGPoint(x: 5.63, y: 10.05), CGPoint(x: 5.60, y: 10.24), CGPoint(x: 5.58, y: 10.43), CGPoint(x: 5.57, y: 10.62), CGPoint(x: 5.56, y: 10.81), CGPoint(x: 5.56, y: 10.99), CGPoint(x: 5.56, y: 11.18), CGPoint(x: 5.56, y: 11.36), CGPoint(x: 5.57, y: 11.55), CGPoint(x: 5.58, y: 11.73), CGPoint(x: 5.60, y: 11.91), CGPoint(x: 5.62, y: 12.08), CGPoint(x: 5.65, y: 12.26), CGPoint(x: 5.68, y: 12.43), CGPoint(x: 5.72, y: 12.60), CGPoint(x: 5.76, y: 12.76), CGPoint(x: 5.80, y: 12.93), CGPoint(x: 5.85, y: 13.09), CGPoint(x: 5.90, y: 13.24), CGPoint(x: 5.96, y: 13.40), CGPoint(x: 6.02, y: 13.55), CGPoint(x: 6.08, y: 13.69), CGPoint(x: 6.15, y: 13.83), CGPoint(x: 6.22, y: 13.97), CGPoint(x: 6.30, y: 14.11), CGPoint(x: 6.38, y: 14.24), CGPoint(x: 6.46, y: 14.36), CGPoint(x: 6.54, y: 14.49), CGPoint(x: 6.63, y: 14.60), CGPoint(x: 6.72, y: 14.71), CGPoint(x: 6.81, y: 14.82), CGPoint(x: 6.91, y: 14.92), CGPoint(x: 7.01, y: 15.02), CGPoint(x: 7.11, y: 15.12), CGPoint(x: 7.21, y: 15.20), CGPoint(x: 7.32, y: 15.29), CGPoint(x: 7.43, y: 15.36), CGPoint(x: 7.54, y: 15.44), CGPoint(x: 7.65, y: 15.50), CGPoint(x: 7.76, y: 15.56), CGPoint(x: 7.88, y: 15.62), CGPoint(x: 8.00, y: 15.67), CGPoint(x: 8.11, y: 15.71), CGPoint(x: 8.23, y: 15.75), CGPoint(x: 8.35, y: 15.79), CGPoint(x: 8.47, y: 15.82), CGPoint(x: 8.59, y: 15.84), CGPoint(x: 8.71, y: 15.85), CGPoint(x: 8.84, y: 15.86), CGPoint(x: 8.96, y: 15.87), CGPoint(x: 9.08, y: 15.87), CGPoint(x: 9.20, y: 15.86), CGPoint(x: 9.33, y: 15.85), CGPoint(x: 9.45, y: 15.83), CGPoint(x: 9.57, y: 15.81), CGPoint(x: 9.69, y: 15.78), CGPoint(x: 9.81, y: 15.74), CGPoint(x: 9.93, y: 15.70), CGPoint(x: 10.04, y: 15.65), CGPoint(x: 10.16, y: 15.60), CGPoint(x: 10.27, y: 15.54), CGPoint(x: 10.39, y: 15.48), CGPoint(x: 10.50, y: 15.41), CGPoint(x: 10.61, y: 15.34), CGPoint(x: 10.72, y: 15.26), CGPoint(x: 10.82, y: 15.17), CGPoint(x: 10.92, y: 15.09), CGPoint(x: 11.02, y: 14.99), CGPoint(x: 11.12, y: 14.89), CGPoint(x: 11.22, y: 14.79), CGPoint(x: 11.31, y: 14.68), CGPoint(x: 11.40, y: 14.56), CGPoint(x: 11.49, y: 14.45), CGPoint(x: 11.57, y: 14.32), CGPoint(x: 11.65, y: 14.19), CGPoint(x: 11.73, y: 14.06), CGPoint(x: 11.80, y: 13.93), CGPoint(x: 11.87, y: 13.79), CGPoint(x: 11.94, y: 13.64), CGPoint(x: 12.00, y: 13.50), CGPoint(x: 12.06, y: 13.35), CGPoint(x: 12.11, y: 13.19), CGPoint(x: 12.17, y: 13.03), CGPoint(x: 12.21, y: 12.87), CGPoint(x: 12.26, y: 12.71), CGPoint(x: 12.28, y: 12.60)], + [CGPoint(x: 11.68, y: 7.63), CGPoint(x: 11.59, y: 7.45), CGPoint(x: 11.51, y: 7.26), CGPoint(x: 11.41, y: 7.08), CGPoint(x: 11.31, y: 6.90), CGPoint(x: 11.21, y: 6.73), CGPoint(x: 11.11, y: 6.55), CGPoint(x: 11.00, y: 6.38), CGPoint(x: 10.89, y: 6.21), CGPoint(x: 10.77, y: 6.04), CGPoint(x: 10.65, y: 5.88), CGPoint(x: 10.53, y: 5.72), CGPoint(x: 10.40, y: 5.56), CGPoint(x: 10.27, y: 5.41), CGPoint(x: 10.14, y: 5.26), CGPoint(x: 10.00, y: 5.11), CGPoint(x: 9.87, y: 4.96), CGPoint(x: 9.73, y: 4.82), CGPoint(x: 9.58, y: 4.69), CGPoint(x: 9.44, y: 4.55), CGPoint(x: 9.29, y: 4.43), CGPoint(x: 9.14, y: 4.30), CGPoint(x: 8.99, y: 4.18), CGPoint(x: 8.83, y: 4.06), CGPoint(x: 8.68, y: 3.95), CGPoint(x: 8.52, y: 3.85), CGPoint(x: 8.36, y: 3.74), CGPoint(x: 8.20, y: 3.65), CGPoint(x: 8.04, y: 3.55), CGPoint(x: 7.88, y: 3.46), CGPoint(x: 7.72, y: 3.38), CGPoint(x: 7.55, y: 3.30), CGPoint(x: 7.39, y: 3.23), CGPoint(x: 7.22, y: 3.16), CGPoint(x: 7.06, y: 3.10), CGPoint(x: 6.90, y: 3.04), CGPoint(x: 6.73, y: 2.98), CGPoint(x: 6.57, y: 2.94), CGPoint(x: 6.40, y: 2.89), CGPoint(x: 6.24, y: 2.85), CGPoint(x: 6.08, y: 2.82), CGPoint(x: 5.92, y: 2.79), CGPoint(x: 5.76, y: 2.77), CGPoint(x: 5.60, y: 2.75), CGPoint(x: 5.44, y: 2.74), CGPoint(x: 5.29, y: 2.73), CGPoint(x: 5.13, y: 2.73), CGPoint(x: 4.98, y: 2.73), CGPoint(x: 4.83, y: 2.74), CGPoint(x: 4.69, y: 2.75), CGPoint(x: 4.54, y: 2.77), CGPoint(x: 4.40, y: 2.79), CGPoint(x: 4.26, y: 2.82), CGPoint(x: 4.12, y: 2.85), CGPoint(x: 3.99, y: 2.89), CGPoint(x: 3.85, y: 2.93), CGPoint(x: 3.73, y: 2.98), CGPoint(x: 3.60, y: 3.03), CGPoint(x: 3.48, y: 3.08), CGPoint(x: 3.36, y: 3.14), CGPoint(x: 3.25, y: 3.21), CGPoint(x: 3.14, y: 3.27), CGPoint(x: 3.03, y: 3.34), CGPoint(x: 2.93, y: 3.42), CGPoint(x: 2.83, y: 3.50), CGPoint(x: 2.74, y: 3.58), CGPoint(x: 2.65, y: 3.67), CGPoint(x: 2.57, y: 3.76), CGPoint(x: 2.49, y: 3.85), CGPoint(x: 2.41, y: 3.95), CGPoint(x: 2.34, y: 4.05), CGPoint(x: 2.28, y: 4.16), CGPoint(x: 2.22, y: 4.26), CGPoint(x: 2.16, y: 4.37), CGPoint(x: 2.11, y: 4.48), CGPoint(x: 2.07, y: 4.60), CGPoint(x: 2.03, y: 4.71), CGPoint(x: 1.99, y: 4.83), CGPoint(x: 1.96, y: 4.95), CGPoint(x: 1.94, y: 5.08), CGPoint(x: 1.92, y: 5.20), CGPoint(x: 1.91, y: 5.33), CGPoint(x: 1.90, y: 5.46), CGPoint(x: 1.90, y: 5.59), CGPoint(x: 1.90, y: 5.72), CGPoint(x: 1.91, y: 5.85), CGPoint(x: 1.93, y: 5.98), CGPoint(x: 1.95, y: 6.11), CGPoint(x: 1.97, y: 6.25), CGPoint(x: 2.01, y: 6.38), CGPoint(x: 2.04, y: 6.52), CGPoint(x: 2.09, y: 6.65), CGPoint(x: 2.13, y: 6.79), CGPoint(x: 2.19, y: 6.92), CGPoint(x: 2.25, y: 7.06), CGPoint(x: 2.31, y: 7.19), CGPoint(x: 2.38, y: 7.32), CGPoint(x: 2.46, y: 7.46), CGPoint(x: 2.54, y: 7.59), CGPoint(x: 2.62, y: 7.72), CGPoint(x: 2.72, y: 7.85), CGPoint(x: 2.81, y: 7.98), CGPoint(x: 2.91, y: 8.10), CGPoint(x: 3.02, y: 8.23), CGPoint(x: 3.13, y: 8.35), CGPoint(x: 3.25, y: 8.47), CGPoint(x: 3.37, y: 8.59), CGPoint(x: 3.45, y: 8.67)], + [CGPoint(x: 8.05, y: 10.63), CGPoint(x: 8.25, y: 10.65), CGPoint(x: 8.46, y: 10.66), CGPoint(x: 8.66, y: 10.67), CGPoint(x: 8.86, y: 10.68), CGPoint(x: 9.07, y: 10.68), CGPoint(x: 9.27, y: 10.67), CGPoint(x: 9.48, y: 10.67), CGPoint(x: 9.68, y: 10.65), CGPoint(x: 9.88, y: 10.64), CGPoint(x: 10.08, y: 10.62), CGPoint(x: 10.28, y: 10.59), CGPoint(x: 10.48, y: 10.56), CGPoint(x: 10.68, y: 10.52), CGPoint(x: 10.88, y: 10.49), CGPoint(x: 11.07, y: 10.44), CGPoint(x: 11.27, y: 10.39), CGPoint(x: 11.46, y: 10.34), CGPoint(x: 11.65, y: 10.29), CGPoint(x: 11.84, y: 10.23), CGPoint(x: 12.02, y: 10.16), CGPoint(x: 12.21, y: 10.10), CGPoint(x: 12.39, y: 10.02), CGPoint(x: 12.56, y: 9.95), CGPoint(x: 12.74, y: 9.87), CGPoint(x: 12.91, y: 9.79), CGPoint(x: 13.08, y: 9.70), CGPoint(x: 13.24, y: 9.61), CGPoint(x: 13.40, y: 9.52), CGPoint(x: 13.56, y: 9.42), CGPoint(x: 13.71, y: 9.32), CGPoint(x: 13.86, y: 9.22), CGPoint(x: 14.01, y: 9.12), CGPoint(x: 14.15, y: 9.01), CGPoint(x: 14.29, y: 8.90), CGPoint(x: 14.42, y: 8.78), CGPoint(x: 14.55, y: 8.67), CGPoint(x: 14.67, y: 8.55), CGPoint(x: 14.79, y: 8.43), CGPoint(x: 14.91, y: 8.31), CGPoint(x: 15.02, y: 8.19), CGPoint(x: 15.12, y: 8.06), CGPoint(x: 15.22, y: 7.93), CGPoint(x: 15.32, y: 7.80), CGPoint(x: 15.41, y: 7.68), CGPoint(x: 15.49, y: 7.54), CGPoint(x: 15.57, y: 7.41), CGPoint(x: 15.64, y: 7.28), CGPoint(x: 15.71, y: 7.15), CGPoint(x: 15.77, y: 7.01), CGPoint(x: 15.83, y: 6.88), CGPoint(x: 15.88, y: 6.74), CGPoint(x: 15.93, y: 6.61), CGPoint(x: 15.97, y: 6.47), CGPoint(x: 16.01, y: 6.34), CGPoint(x: 16.03, y: 6.20), CGPoint(x: 16.06, y: 6.07), CGPoint(x: 16.08, y: 5.94), CGPoint(x: 16.09, y: 5.80), CGPoint(x: 16.10, y: 5.67), CGPoint(x: 16.10, y: 5.54), CGPoint(x: 16.10, y: 5.41), CGPoint(x: 16.09, y: 5.29), CGPoint(x: 16.07, y: 5.16), CGPoint(x: 16.05, y: 5.04), CGPoint(x: 16.03, y: 4.91), CGPoint(x: 16.00, y: 4.79), CGPoint(x: 15.96, y: 4.67), CGPoint(x: 15.92, y: 4.56), CGPoint(x: 15.87, y: 4.45), CGPoint(x: 15.82, y: 4.33), CGPoint(x: 15.76, y: 4.23), CGPoint(x: 15.70, y: 4.12), CGPoint(x: 15.63, y: 4.02), CGPoint(x: 15.56, y: 3.92), CGPoint(x: 15.49, y: 3.82), CGPoint(x: 15.40, y: 3.73), CGPoint(x: 15.32, y: 3.64), CGPoint(x: 15.23, y: 3.55), CGPoint(x: 15.13, y: 3.47), CGPoint(x: 15.03, y: 3.39), CGPoint(x: 14.93, y: 3.32), CGPoint(x: 14.82, y: 3.25), CGPoint(x: 14.71, y: 3.18), CGPoint(x: 14.60, y: 3.12), CGPoint(x: 14.48, y: 3.06), CGPoint(x: 14.36, y: 3.01), CGPoint(x: 14.23, y: 2.96), CGPoint(x: 14.10, y: 2.92), CGPoint(x: 13.97, y: 2.88), CGPoint(x: 13.83, y: 2.84), CGPoint(x: 13.70, y: 2.81), CGPoint(x: 13.56, y: 2.79), CGPoint(x: 13.41, y: 2.76), CGPoint(x: 13.27, y: 2.75), CGPoint(x: 13.12, y: 2.74), CGPoint(x: 12.97, y: 2.73), CGPoint(x: 12.81, y: 2.73), CGPoint(x: 12.66, y: 2.74), CGPoint(x: 12.50, y: 2.74), CGPoint(x: 12.35, y: 2.76), CGPoint(x: 12.19, y: 2.78), CGPoint(x: 12.03, y: 2.80), CGPoint(x: 11.87, y: 2.83), CGPoint(x: 11.70, y: 2.87), CGPoint(x: 11.54, y: 2.91), CGPoint(x: 11.38, y: 2.95), CGPoint(x: 11.27, y: 2.98)], + ] + + static let menuBarImage: NSImage = { + let image = NSImage(size: NSSize(width: 18, height: 18), flipped: true) { _ in + NSColor.black.setStroke() + for strand in strands { + let path = NSBezierPath() + path.move(to: strand[0]) + for point in strand.dropFirst() { path.line(to: point) } + path.lineWidth = 1.9 + path.lineCapStyle = .round + path.lineJoinStyle = .round + path.stroke() + } + return true + } + image.isTemplate = true + image.accessibilityDescription = "Knotchy" + return image + }() +} diff --git a/Sources/Knotchy/KnotchyApp.swift b/Sources/Knotchy/KnotchyApp.swift new file mode 100644 index 0000000..402ccfa --- /dev/null +++ b/Sources/Knotchy/KnotchyApp.swift @@ -0,0 +1,15 @@ +import KnotchyCore +import SwiftUI + +@main +struct KnotchyApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) private var app + + var body: some Scene { + MenuBarExtra(isInserted: .constant(app.isSupportedMac)) { + KnotchyMenu(app: app, settings: app.settings) + } label: { + Image(nsImage: KnotGlyph.menuBarImage) + } + } +} diff --git a/Sources/Knotchy/MenuBar.swift b/Sources/Knotchy/MenuBar.swift new file mode 100644 index 0000000..b454561 --- /dev/null +++ b/Sources/Knotchy/MenuBar.swift @@ -0,0 +1,212 @@ +import AppKit +import KnotchyCore +import Observation +import SwiftUI + +struct KnotchyMenu: View { + let app: AppDelegate + @Bindable var settings: AppSettings + + private var controller: SessionController { app.controller } + + var body: some View { + if app.vmStatus.needsSetup { + Button("Set Up Knotchy…") { app.setupWindow.show() } + Divider() + } + + if let session = SessionSummary(controller.state, url: controller.pageURL ?? controller.openedURL, + setupFolded: controller.setupFolded) { + Section(session.title) { + if session.canShow { + Button("Show Panel") { controller.showPanel() } + } + if session.canDestroy { + Button("Destroy VM") { controller.destroy() } + } + } + Divider() + } + + Button("Open Copied Link") { app.services.openCopiedLink() } + .keyboardShortcut("l", modifiers: [.control, .option, .command]) + + Divider() + + Picker("Auto-Destroy Folded Pages", selection: $settings.autoDestroyMinutes) { + ForEach(AppSettings.autoDestroyChoices, id: \.self) { minutes in + Text(minutes == 1 ? "After 1 Minute" : "After \(minutes) Minutes").tag(minutes) + } + } + Picker("Open Pages In", selection: $settings.opensLarge) { + Text("Small Panel").tag(false) + Text("Large Panel").tag(true) + } + Menu("Connection") { + Picker("Connection", selection: connection) { + Text("Direct").tag(Connection.direct) + if !settings.proxyHost.isEmpty { + Text("Proxy (\(settings.proxyHost):\(String(settings.proxyPort)))").tag(Connection.proxy) + } + if !settings.wireGuardNames.isEmpty { + Section("WireGuard VPN") { + ForEach(settings.wireGuardNames, id: \.self) { Text($0).tag(Connection.vpn($0)) } + } + } + } + .pickerStyle(.inline) + .labelsHidden() + Divider() + Button("Network Settings…") { showSettings(.network) } + } + + Divider() + + Button { + controller.rebuildImage() + } label: { + Text("Rebuild VM") + Text(app.vmStatus.summary) + } + .disabled(isSettingUp || app.vmStatus.image == .noContainerTool || app.vmStatus.needsSetup) + + Divider() + + if !app.vmStatus.needsSetup { + Button("Set Up Knotchy…") { app.setupWindow.show() } + } + Button("Settings…") { showSettings(nil) } + .keyboardShortcut(",") + Button("Quit Knotchy") { NSApp.terminate(nil) } + .keyboardShortcut("q") + } + + private enum Connection: Hashable { + case direct, proxy, vpn(String) + } + + private var connection: Binding { + Binding { + switch settings.networkMode { + case .direct: .direct + case .proxy: .proxy + case .vpn: .vpn(settings.wireGuardServer) + } + } set: { choice in + switch choice { + case .direct: + settings.networkMode = .direct + case .proxy: + settings.networkMode = .proxy + case .vpn(let server): + settings.wireGuardServer = server + settings.networkMode = .vpn + } + } + } + + private var isSettingUp: Bool { + if case .settingUp = controller.state { true } else { false } + } + + private func showSettings(_ section: SettingsTab?) { + app.settingsWindow.show(section) + } +} + +@MainActor +@Observable +final class VMStatus { + private(set) var image: ContainerCLIBackend.ImageStatus? + private(set) var runtime: RuntimeStatus? + @ObservationIgnored private let backend: ContainerCLIBackend + @ObservationIgnored private let controller: SessionController + + init(backend: ContainerCLIBackend, controller: SessionController) { + self.backend = backend + self.controller = controller + watchSetup() + } + + var needsSetup: Bool { + switch runtime { + case .missing?, .outdated?: true + default: false + } + } + + var runtimeSummary: String { + switch runtime { + case nil: "Checking…" + case .missing: "Not installed" + case .outdated(let version): "Apple container \(version) (too old)" + case .current(let version?): "Apple container \(version)" + case .current(nil): "Apple container (version unknown)" + } + } + + func refresh() { + Task { + let runtime = await backend.runtimeStatus() + if runtime != self.runtime { self.runtime = runtime } + let status = await backend.imageStatus() + if status != image { image = status } + } + } + + var summary: String { + if needsSetup { return "Setup isn't finished" } + return switch image { + case nil: "Checking…" + case .noContainerTool: "Setup isn't finished" + case .missing: "Not built yet" + case .built(nil): "Built" + case .built(let date?): "Built " + RelativeDateTimeFormatter().localizedString(for: date, relativeTo: Date()) + } + } + + private func watchSetup() { + let wasSettingUp: Bool + if case .settingUp = controller.state { wasSettingUp = true } else { wasSettingUp = false } + withObservationTracking { + _ = controller.state + } onChange: { [weak self] in + Task { @MainActor in + guard let self else { return } + if wasSettingUp { self.refresh() } + self.watchSetup() + } + } + } +} + +struct SessionSummary { + var title: String + var canShow: Bool + var canDestroy: Bool + + init?(_ state: SessionState, url: URL?, setupFolded: Bool) { + let host = url?.host().map { " — \($0)" } ?? "" + switch state { + case .starting(_, let folded): + (title, canShow, canDestroy) = ("Opening\(host)", folded, true) + case .live(let mode): + (title, canShow, canDestroy) = ("Open\(host)", mode == .collapsed, true) + case .settingUp(let issue): + (title, canShow, canDestroy) = (Self.setupTitle(issue), setupFolded, false) + case .destroying: + (title, canShow, canDestroy) = ("Destroying the VM…", false, false) + default: + return nil + } + } + + static func setupTitle(_ issue: SetupIssue) -> String { + switch issue { + case .cliMissing: "Installing the container runtime…" + case .cliOutdated: "Updating the container runtime…" + case .serviceStopped, .kernelMissing: "Starting the container runtime…" + default: "Building the VM…" + } + } +} diff --git a/Sources/Knotchy/NotchStyle.swift b/Sources/Knotchy/NotchStyle.swift new file mode 100644 index 0000000..6559df5 --- /dev/null +++ b/Sources/Knotchy/NotchStyle.swift @@ -0,0 +1,196 @@ +import SwiftUI + +/// Flared top "shoulders" like the notch's own; `rect` includes them. +struct NotchShape: Shape { + var topRadius: CGFloat + var bottomRadius: CGFloat + + var animatableData: AnimatablePair { + get { AnimatablePair(topRadius, bottomRadius) } + set { (topRadius, bottomRadius) = (newValue.first, newValue.second) } + } + + func path(in rect: CGRect) -> Path { + let top = max(0, min(topRadius, rect.width / 4, rect.height / 2)) + let bottom = max(0, min(bottomRadius, (rect.width - 2 * top) / 2, rect.height - top)) + var path = Path() + path.move(to: CGPoint(x: rect.minX, y: rect.minY)) + path.addQuadCurve(to: CGPoint(x: rect.minX + top, y: rect.minY + top), + control: CGPoint(x: rect.minX + top, y: rect.minY)) + path.addLine(to: CGPoint(x: rect.minX + top, y: rect.maxY - bottom)) + path.addQuadCurve(to: CGPoint(x: rect.minX + top + bottom, y: rect.maxY), + control: CGPoint(x: rect.minX + top, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.maxX - top - bottom, y: rect.maxY)) + path.addQuadCurve(to: CGPoint(x: rect.maxX - top, y: rect.maxY - bottom), + control: CGPoint(x: rect.maxX - top, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.maxX - top, y: rect.minY + top)) + path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.minY), + control: CGPoint(x: rect.maxX - top, y: rect.minY)) + path.closeSubpath() + return path + } +} + +struct Morph: ViewModifier, Animatable { + var progress: CGFloat + var smallest: CGFloat = 0.55 + var blur: CGFloat = 10 + var clearUntil: CGFloat = 0.3 + var opaqueFrom: CGFloat = 0.85 + + nonisolated var animatableData: CGFloat { + get { progress } + set { progress = newValue } + } + + func body(content: Content) -> some View { + content + .scaleEffect(smallest + (1 - smallest) * progress, anchor: .top) + .blur(radius: max(0, 1 - progress) * blur) + .opacity(Double(min(1, max(0, (progress - clearUntil) / (opaqueFrom - clearUntil))))) + } +} + +extension AnyTransition { + /// Old content is gone early so it barely overlaps the new. + static var island: AnyTransition { + .asymmetric( + insertion: .modifier(active: Morph(progress: 0), identity: Morph(progress: 1)), + removal: .modifier(active: Morph(progress: 0, smallest: 0.8, blur: 8, clearUntil: 0.6, opaqueFrom: 0.95), + identity: Morph(progress: 1, smallest: 0.8, blur: 8, clearUntil: 0.6, opaqueFrom: 0.95)) + .animation(.spring(response: 0.2, dampingFraction: 1)) + ) + } + + static var swap: AnyTransition { + .modifier(active: Morph(progress: 0, smallest: 0.92, blur: 6, clearUntil: 0.2, opaqueFrom: 0.8), + identity: Morph(progress: 1, smallest: 0.92, blur: 6, clearUntil: 0.2, opaqueFrom: 0.8)) + .animation(.spring(response: 0.3, dampingFraction: 0.9)) + } +} + +enum Motion { + static let open = Animation.spring(response: 0.46, dampingFraction: 0.74) + static let resize = Animation.spring(response: 0.42, dampingFraction: 0.8) + static let fold = Animation.spring(response: 0.4, dampingFraction: 0.84) + static let small = Animation.spring(response: 0.4, dampingFraction: 0.7) + static let hide = Animation.spring(response: 0.36, dampingFraction: 0.9) +} + +struct KnotMark: View { + var size: CGFloat + var color: Color = Palette.coral + + var body: some View { + KnotShape() + .stroke(color, style: StrokeStyle(lineWidth: size * 1.9 / 18, lineCap: .round, lineJoin: .round)) + .frame(width: size, height: size) + .accessibilityHidden(true) + } +} + +/// `KnotGlyph.strands` are in an 18-point box. +struct KnotShape: Shape { + func path(in rect: CGRect) -> Path { + let scale = min(rect.width, rect.height) / 18 + var path = Path() + for strand in KnotGlyph.strands { + path.move(to: CGPoint(x: rect.minX + strand[0].x * scale, y: rect.minY + strand[0].y * scale)) + for point in strand.dropFirst() { + path.addLine(to: CGPoint(x: rect.minX + point.x * scale, y: rect.minY + point.y * scale)) + } + } + return path + } +} + +struct Breathing: ViewModifier { + func body(content: Content) -> some View { + content.phaseAnimator([1.0, 0.55]) { view, opacity in + view.opacity(opacity) + } animation: { _ in .easeInOut(duration: 1.6) } + } +} + +enum Palette { + static let coral = Color(red: 1, green: 0.54, blue: 0.36) + static let green = Color(red: 0.25, green: 0.86, blue: 0.47) + static let red = Color(red: 1, green: 0.38, blue: 0.36) + static let amber = Color(red: 1, green: 0.72, blue: 0.2) + static let surface = LinearGradient(colors: [Color(white: 0.1), Color(white: 0.055)], + startPoint: .top, endPoint: .bottom) +} + +struct IconButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + Styled(configuration: configuration) + } + + struct Styled: View { + let configuration: Configuration + @State private var hovering = false + @Environment(\.isEnabled) private var isEnabled + + var body: some View { + configuration.label + .foregroundStyle(.white.opacity(isEnabled ? (hovering ? 1 : 0.72) : 0.22)) + .background( + RoundedRectangle(cornerRadius: 7, style: .continuous) + .fill(.white.opacity(configuration.isPressed ? 0.18 : (hovering && isEnabled ? 0.1 : 0))) + ) + .contentShape(Rectangle()) + .onHover { hovering = $0 } + .animation(.easeOut(duration: 0.12), value: hovering) + } + } +} + +struct CapsuleButtonStyle: ButtonStyle { + enum Kind { case primary, secondary, destructive } + + var kind: Kind = .secondary + var small = false + + func makeBody(configuration: Configuration) -> some View { + Styled(configuration: configuration, kind: kind, small: small) + } + + struct Styled: View { + let configuration: Configuration + let kind: Kind + let small: Bool + @State private var hovering = false + + var body: some View { + configuration.label + .font(.system(size: small ? 11 : 12, weight: .semibold)) + .lineLimit(1) + .padding(.horizontal, small ? 9 : 14) + .frame(height: small ? 20 : 28) + .foregroundStyle(foreground) + .background(Capsule().fill(fill)) + .contentShape(Capsule()) + .scaleEffect(configuration.isPressed ? 0.96 : 1) + .onHover { hovering = $0 } + .animation(.easeOut(duration: 0.12), value: hovering) + .animation(.easeOut(duration: 0.08), value: configuration.isPressed) + } + + private var foreground: Color { + switch kind { + case .primary: .black + case .secondary: .white + case .destructive: Palette.red + } + } + + private var fill: Color { + let boost = configuration.isPressed ? 0.1 : (hovering ? 0.06 : 0) + switch kind { + case .primary: return .white.opacity(0.92 + boost) + case .secondary: return .white.opacity(0.12 + boost) + case .destructive: return Palette.red.opacity(0.16 + boost * 1.5) + } + } + } +} diff --git a/Sources/Knotchy/NotchViews.swift b/Sources/Knotchy/NotchViews.swift new file mode 100644 index 0000000..5c98876 --- /dev/null +++ b/Sources/Knotchy/NotchViews.swift @@ -0,0 +1,603 @@ +import KnotchyCore +import SwiftUI + +struct NotchRootView: View { + let controller: SessionController + let chrome: NotchChrome + + var body: some View { + let size = chrome.bodySize + ZStack(alignment: .top) { + switch chrome.content { + case .hidden: + Color.clear.frame(width: 0, height: 0) + case .compact: + CompactView(controller: controller, state: chrome.state, notchWidth: chrome.notchSize.width) + .frame(width: NotchChrome.bodySize(.compact, notch: chrome.notchSize, panel: chrome.panelSize, + hasNotch: chrome.hasNotch).width, + height: chrome.notchSize.height) + .transition(.island) + case .banner: + BannerView(controller: controller, state: chrome.state, notice: chrome.notice, + notchHeight: chrome.notchSize.height) + .frame(width: NotchChrome.bodySize(.banner, notch: chrome.notchSize, panel: chrome.panelSize, + hasNotch: chrome.hasNotch).width) + .transition(.island) + case .panel: + PanelView(controller: controller, chrome: chrome) + .frame(width: chrome.panelSize.width, height: chrome.panelSize.height) + .transition(.island) + } + } + .modifier(Morph(progress: chrome.reveal)) + .frame(width: size.width, height: size.height, alignment: .top) + .padding(.horizontal, chrome.topRadius) + .clipShape(NotchShape(topRadius: chrome.topRadius, bottomRadius: chrome.bottomRadius)) + .background { + // Shadow from the outline alone, not from everything inside it. + NotchShape(topRadius: chrome.topRadius, bottomRadius: chrome.bottomRadius) + .fill(.black) + .shadow(color: .black.opacity(chrome.presentation == .panel ? 0.5 : 0), radius: 14, y: 6) + } + .offset(x: chrome.offsetX) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .environment(\.colorScheme, .dark) + } +} + +// MARK: - Compact + +private struct CompactView: View { + let controller: SessionController + let state: SessionState + let notchWidth: CGFloat + + var body: some View { + HStack(spacing: 0) { + leading.frame(maxWidth: .infinity) + Color.clear.frame(width: notchWidth) + trailing.frame(maxWidth: .infinity) + } + .font(.system(size: 12, weight: .semibold, design: .rounded)) + .foregroundStyle(.white) + .contentShape(Rectangle()) + .contextMenu { PillMenu(controller: controller, state: state) } + } + + @ViewBuilder private var leading: some View { + switch state { + case .starting, .settingUp: + ProgressView().controlSize(.small).scaleEffect(0.75) + default: + KnotMark(size: 15) + .modifier(Breathing()) + .help("A page is open in a disposable VM") + } + } + + @ViewBuilder private var trailing: some View { + switch state { + case .starting: + Text("Opening").foregroundStyle(.white.opacity(0.7)) + case .settingUp(let issue): + Text(Self.setupWord(issue)).foregroundStyle(.white.opacity(0.7)) + .help("Setting up the browser VM. Hover to see progress.") + default: + TimelineView(.periodic(from: .now, by: 1)) { _ in + let text = Self.format(controller.countdown) + Text(text) + .monospacedDigit() + .foregroundStyle(.white.opacity(0.7)) + .contentTransition(.numericText(countsDown: true)) + .animation(.snappy, value: text) + .help("Destroyed automatically when this reaches 0:00") + } + } + } + + private static func setupWord(_ issue: SetupIssue) -> String { + switch issue { + case .imageMissing, .imageOutdated: "Building" + case .imageStale, .cliOutdated: "Updating" + case .cliMissing: "Installing" + default: "Starting" + } + } + + private static func format(_ remaining: Duration?) -> String { + guard let remaining else { return "" } + let seconds = Int(remaining.components.seconds) + return String(format: "%d:%02d", seconds / 60, seconds % 60) + } +} + +// MARK: - Banner + +private struct BannerView: View { + let controller: SessionController + let state: SessionState + let notice: Notice? + let notchHeight: CGFloat + + var body: some View { + VStack(spacing: 0) { + Color.clear.frame(height: notchHeight) + ZStack { + row.id(kind).transition(.swap) + } + .frame(maxWidth: .infinity) + .frame(height: NotchChrome.bannerRowHeight - 4) + } + .font(.system(size: 12, weight: .semibold, design: .rounded)) + .foregroundStyle(.white) + .contentShape(Rectangle()) + .contextMenu { PillMenu(controller: controller, state: state) } + } + + private enum Kind: Hashable { case noWebLink, destroying, destroyed, ended, stuck, other } + + private var kind: Kind { + if notice == .noWebLink, state == .idle { return .noWebLink } + switch state { + case .destroying: return .destroying + case .destroyed: return .destroyed + case .failed(.ended): return .ended + case .failed(.destroyStuck): return .stuck + default: return .other + } + } + + @ViewBuilder private var row: some View { + HStack(spacing: 7) { + switch kind { + case .noWebLink: + Image(systemName: "link").foregroundStyle(Palette.amber) + Text("No web link found") + case .destroying: + ProgressView().controlSize(.small).scaleEffect(0.7).frame(width: 16, height: 16) + Text("Destroying the VM…").foregroundStyle(.white.opacity(0.8)) + case .destroyed: + BouncingSymbol(name: "checkmark.circle.fill", color: Palette.green) + Text("VM destroyed") + case .ended: + Image(systemName: "xmark.octagon.fill").foregroundStyle(Palette.red) + Text("Session ended") + case .stuck: + Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(Palette.amber) + Text("VM may still be running") + Button("Retry") { controller.retry() } + .buttonStyle(CapsuleButtonStyle(kind: .secondary, small: true)) + .padding(.leading, 4) + case .other: + EmptyView() + } + } + } +} + +private struct PillMenu: View { + let controller: SessionController + let state: SessionState + + var body: some View { + if case .live = state { Button("Destroy Now") { controller.destroy() } } + Button("Quit Knotchy") { NSApp.terminate(nil) } + } +} + +private struct BouncingSymbol: View { + let name: String + let color: Color + @State private var appeared = false + + var body: some View { + Image(systemName: name) + .foregroundStyle(color) + .symbolEffect(.bounce, value: appeared) + .onAppear { appeared = true } + } +} + +// MARK: - Panel + +private struct PanelView: View { + let controller: SessionController + let chrome: NotchChrome + + private var innerRadius: CGFloat { NotchChrome.bottomRadius(.panel) - NotchGeometry.margin } + + var body: some View { + VStack(spacing: 0) { + TopStrip(controller: controller, state: chrome.state, notchWidth: chrome.notchSize.width, + routeLabel: chrome.routeLabel) + .frame(height: chrome.notchSize.height) + Toolbar(controller: controller, state: chrome.state) + .frame(height: NotchGeometry.toolbarHeight) + ZStack(alignment: .bottom) { + content + .id(ContentKind(chrome.state)) + .transition(.swap) + if let stale = controller.staleImage, case .live = chrome.state { + StaleBanner(controller: controller, stale: stale) + .transition(.move(edge: .bottom).combined(with: .opacity)) + } + } + .overlay(alignment: .top) { + if case .live = chrome.state { ProgressLine(progress: controller.progress) } + } + .clipShape(UnevenRoundedRectangle(topLeadingRadius: 8, bottomLeadingRadius: innerRadius, + bottomTrailingRadius: innerRadius, topTrailingRadius: 8, + style: .continuous)) + .padding([.horizontal, .bottom], NotchGeometry.margin) + } + .foregroundStyle(.white) + } + + private enum ContentKind: Hashable { + case starting, live, failed, setup, settingUp, other + + init(_ state: SessionState) { + switch state { + case .starting: self = .starting + case .live: self = .live + case .failed(.start): self = .failed + case .setupRequired: self = .setup + case .settingUp: self = .settingUp + default: self = .other + } + } + } + + @ViewBuilder private var content: some View { + switch chrome.state { + case .live: + VideoSurface(controller: controller) + case .starting(let step, _): + StartingView(step: step, host: controller.openedURL?.host()) + case .failed(.start(let message)): + MessageCard(symbol: "exclamationmark.triangle.fill", tint: Palette.amber, + title: "Couldn't open the link", message: message) { + Button("Dismiss") { controller.dismiss() } + .buttonStyle(CapsuleButtonStyle(kind: .secondary)) + Button("Try Again") { controller.retry() } + .buttonStyle(CapsuleButtonStyle(kind: .primary)) + .keyboardShortcut(.defaultAction) + } + case .setupRequired(let issue, let error): + MessageCard(symbol: Setup.symbol(issue), tint: error == nil ? .white : Palette.amber, + title: Setup.title(issue), message: error ?? Setup.text(issue)) { + Button("Not Now") { controller.dismiss() } + .buttonStyle(CapsuleButtonStyle(kind: .secondary)) + if let action = Setup.action(issue) { + Button(action) { controller.performSetupAction() } + .buttonStyle(CapsuleButtonStyle(kind: .primary)) + .keyboardShortcut(.defaultAction) + } + } + case .settingUp(let issue): + MessageCard(symbol: Setup.symbol(issue), tint: .white, title: Setup.progressTitle(issue), + message: Self.lastLine(controller.setupLog) ?? "Working…", monospaced: true) { + ProgressView().controlSize(.small) + Button("Hide") { controller.foldSetup() } + .buttonStyle(CapsuleButtonStyle(kind: .secondary)) + .help("Keep going in the background; hover over the notch to check on it") + } + default: + Color.black + } + } + + private static func lastLine(_ log: String?) -> String? { + log?.split(whereSeparator: \.isNewline).last.map(String.init) + } +} + +private struct TopStrip: View { + let controller: SessionController + let state: SessionState + let notchWidth: CGFloat + let routeLabel: String? + + var body: some View { + HStack(spacing: 0) { + HStack(spacing: 5) { + KnotMark(size: 13) + Text("Isolated").foregroundStyle(.white.opacity(0.75)) + if let routeLabel { + Text("· via \(routeLabel)").foregroundStyle(.white.opacity(0.45)).lineLimit(1) + } + } + .font(.system(size: 11, weight: .semibold, design: .rounded)) + .help("This page runs in a disposable Linux VM, cut off from your Mac and your network") + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 14) + + Color.clear.frame(width: notchWidth) + + Group { + if Self.canDestroy(state) { + Button { controller.destroy() } label: { + HStack(spacing: 4) { + Image(systemName: "power").font(.system(size: 9, weight: .bold)) + Text("Destroy") + } + } + .buttonStyle(CapsuleButtonStyle(kind: .destructive, small: true)) + .help("Destroy the VM and everything in it (⌘W)") + .transition(.opacity) + } + } + .frame(maxWidth: .infinity, alignment: .trailing) + .padding(.trailing, 10) + } + } + + static func canDestroy(_ state: SessionState) -> Bool { + switch state { + case .starting, .live: true + default: false + } + } +} + +private struct Toolbar: View { + let controller: SessionController + let state: SessionState + + private var isLive: Bool { + if case .live = state { true } else { false } + } + + var body: some View { + HStack(spacing: 2) { + IconButton(symbol: "chevron.left", help: "Back (⌘[)") { controller.goBack() } + IconButton(symbol: "chevron.right", help: "Forward (⌘])") { controller.goForward() } + IconButton(symbol: "arrow.clockwise", help: "Reload (⌘R)") { controller.reload() } + AddressField(url: controller.pageURL ?? controller.openedURL) + .padding(.horizontal, 6) + if state == .live(.expanded) { + IconButton(symbol: "arrow.down.right.and.arrow.up.left", help: "Make smaller (⇧⌘E)") { controller.shrink() } + } else { + IconButton(symbol: "arrow.up.left.and.arrow.down.right", help: "Make larger (⇧⌘E)") { controller.expand() } + } + } + .disabled(!isLive) + .padding(.horizontal, NotchGeometry.margin) + } +} + +private struct IconButton: View { + let symbol: String + let help: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Image(systemName: symbol) + .font(.system(size: 12, weight: .semibold)) + .frame(width: 28, height: 24) + } + .buttonStyle(IconButtonStyle()) + .help(help) + } +} + +private struct AddressField: View { + let url: URL? + + var body: some View { + HStack(spacing: 6) { + Image(systemName: url?.scheme == "https" ? "lock.fill" : "globe") + .font(.system(size: 9, weight: .semibold)) + .foregroundStyle(.white.opacity(0.45)) + Text(Self.display(url)) + .lineLimit(1) + .truncationMode(.tail) + } + .font(.system(size: 12)) + .padding(.horizontal, 10) + .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: 24) + .background(RoundedRectangle(cornerRadius: 7, style: .continuous).fill(.white.opacity(0.08))) + .help(url?.absoluteString ?? "") + } + + static func display(_ url: URL?) -> AttributedString { + guard let url else { return AttributedString("") } + var host = AttributedString(url.host() ?? url.absoluteString) + host.foregroundColor = .white + var rest = url.path() + if let query = url.query() { rest += "?" + query } + if rest == "/" { rest = "" } + var tail = AttributedString(rest) + tail.foregroundColor = .white.opacity(0.4) + return host + tail + } +} + +private struct ProgressLine: View { + let progress: Int? + @State private var shown: Int = 0 + + var body: some View { + GeometryReader { proxy in + Capsule() + .fill(LinearGradient(colors: [Color(red: 0.35, green: 0.7, blue: 1), Color(red: 0.2, green: 0.5, blue: 1)], + startPoint: .leading, endPoint: .trailing)) + .frame(width: max(4, proxy.size.width * CGFloat(shown) / 100), height: 3) + } + .frame(height: 3) + .opacity(progress == nil || progress == 100 ? 0 : 1) + .animation(.easeOut(duration: 0.35), value: progress == nil || progress == 100) + .onChange(of: progress, initial: true) { _, value in + guard let value else { return } + withAnimation(value < shown ? nil : .easeOut(duration: 0.25)) { shown = value } + } + } +} + +private struct StartingView: View { + let step: StartStep + let host: String? + + var body: some View { + VStack(spacing: 18) { + ZStack { + Circle().fill(Palette.coral.opacity(0.14)).frame(width: 64, height: 64) + KnotMark(size: 32) + .modifier(Breathing()) + } + VStack(spacing: 4) { + Text("Opening in a disposable VM").font(.system(size: 15, weight: .semibold)) + if let host { + Text(host).font(.system(size: 12)).foregroundStyle(.white.opacity(0.5)) + } + } + VStack(alignment: .leading, spacing: 9) { + StepRow(title: "Starting the VM", done: step != .booting, active: step == .booting) + StepRow(title: "Connecting to its browser", done: step == .loading, active: step == .connecting) + StepRow(title: "Loading the page", done: false, active: step == .loading) + } + .padding(.top, 2) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Palette.surface) + } +} + +private struct StepRow: View { + let title: String + let done: Bool + let active: Bool + + var body: some View { + HStack(spacing: 9) { + ZStack { + if done { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(Palette.green) + .transition(.scale(scale: 0.4).combined(with: .opacity)) + } else if active { + ProgressView().controlSize(.small).scaleEffect(0.75) + .transition(.opacity) + } else { + Image(systemName: "circle").foregroundStyle(.white.opacity(0.22)) + } + } + .frame(width: 16, height: 16) + Text(title) + .foregroundStyle(.white.opacity(done || active ? 0.9 : 0.4)) + } + .font(.system(size: 13)) + .animation(.spring(duration: 0.35, bounce: 0.4), value: done) + .animation(.easeOut(duration: 0.2), value: active) + } +} + +private struct MessageCard: View { + let symbol: String + let tint: Color + let title: String + let message: String + var monospaced = false + @ViewBuilder let actions: Actions + + var body: some View { + VStack(spacing: 14) { + ZStack { + Circle().fill(tint.opacity(0.14)).frame(width: 52, height: 52) + Image(systemName: symbol).font(.system(size: 21, weight: .semibold)).foregroundStyle(tint) + } + VStack(spacing: 6) { + Text(title).font(.system(size: 15, weight: .semibold)) + Text(message) + .font(monospaced ? .system(size: 11, design: .monospaced) : .system(size: 12)) + .foregroundStyle(.white.opacity(0.6)) + .multilineTextAlignment(.center) + .lineLimit(5) + .frame(maxWidth: 400) + } + HStack(spacing: 8) { actions } + .padding(.top, 4) + } + .padding(24) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Palette.surface) + } +} + +private struct StaleBanner: View { + let controller: SessionController + let stale: StaleImage + + var body: some View { + HStack(spacing: 10) { + Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(Palette.amber) + Text(stale.builtDaysAgo.map { "The browser VM is \($0) days old." } ?? "The browser VM's age is unknown.") + .foregroundStyle(.white.opacity(0.85)) + Spacer(minLength: 8) + Button("Later") { controller.dismissStaleImageWarning() } + .buttonStyle(CapsuleButtonStyle(kind: .secondary, small: true)) + Button("Update Now") { controller.requestImageUpdate() } + .buttonStyle(CapsuleButtonStyle(kind: .primary, small: true)) + } + .font(.system(size: 11, weight: .medium)) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color(white: 0.1).opacity(0.94))) + .padding(8) + } +} + +private enum Setup { + static func symbol(_ issue: SetupIssue) -> String { + switch issue { + case .cliMissing, .cliOutdated: "shippingbox" + case .serviceStopped, .kernelMissing: "power" + case .imageMissing, .imageOutdated: "square.and.arrow.down" + case .imageStale: "arrow.triangle.2.circlepath" + case .lowDisk: "internaldrive" + } + } + + static func title(_ issue: SetupIssue) -> String { + switch issue { + case .cliMissing: "Knotchy needs Apple's container runtime" + case .cliOutdated: "Knotchy needs a newer container runtime" + case .serviceStopped, .kernelMissing: "The container service isn't running" + case .imageMissing, .imageOutdated: "The browser VM image needs to be built" + case .imageStale: "Updating the browser VM" + case .lowDisk: "Not enough disk space" + } + } + + static func progressTitle(_ issue: SetupIssue) -> String { + switch issue { + case .cliMissing: "Installing the container runtime…" + case .cliOutdated: "Updating the container runtime…" + case .serviceStopped, .kernelMissing: "Starting the container runtime…" + default: title(issue) + } + } + + static func text(_ issue: SetupIssue) -> String { + switch issue { + case .cliMissing: "Set it up in one step; the link opens when it's done." + case .cliOutdated: "Update it in one step; the link opens when it's done." + case .serviceStopped, .kernelMissing: "Start it to run the isolated browser." + case .imageMissing, .imageOutdated: "This takes about 3 minutes and downloads about 225 MB." + case .imageStale: "Rebuilding gets the latest WebKit security fixes." + case .lowDisk(let free): "Only \(free / 1_000_000_000) GB free; a session needs about 2 GB." + } + } + + static func action(_ issue: SetupIssue) -> String? { + switch issue { + case .cliMissing, .cliOutdated: "Set Up…" + case .serviceStopped, .kernelMissing: "Start Service" + case .imageMissing, .imageOutdated: "Build Image" + case .imageStale: "Update Now" + case .lowDisk: nil + } + } +} diff --git a/Sources/Knotchy/NotchWindow.swift b/Sources/Knotchy/NotchWindow.swift new file mode 100644 index 0000000..30a4b55 --- /dev/null +++ b/Sources/Knotchy/NotchWindow.swift @@ -0,0 +1,418 @@ +import AppKit +import KnotchyCore +import Observation +import SwiftUI + +final class NotchPanel: NSPanel { + init() { + super.init(contentRect: .zero, styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false) + level = NSWindow.Level(rawValue: NSWindow.Level.mainMenu.rawValue + 3) + collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary, .ignoresCycle] + isOpaque = false + backgroundColor = .clear + hasShadow = false + becomesKeyOnlyIfNeeded = true + isMovable = false + isReleasedWhenClosed = false + hidesOnDeactivate = false + } + + override var canBecomeKey: Bool { true } + override var canBecomeMain: Bool { false } +} + +enum Presentation: Equatable { + case hidden + case compact + case banner + case panel +} + +@MainActor +@Observable +final class NotchChrome { + static let earWidth: CGFloat = 64 + static let bannerWidth: CGFloat = 320 + static let bannerRowHeight: CGFloat = 34 + + var presentation: Presentation = .hidden + /// Lags `presentation` while retracting so the content shrinks into the notch with the shape. + var content: Presentation = .hidden + var reveal: CGFloat = 0 + var state: SessionState = .idle + var notice: Notice? + var notchSize = CGSize(width: 200, height: 32) + var hasNotch = true + var panelSize = CGSize(width: 652, height: 472) + var panelOffsetX: CGFloat = 0 + var routeLabel: String? + + var bodySize: CGSize { Self.bodySize(presentation, notch: notchSize, panel: panelSize, hasNotch: hasNotch) } + var topRadius: CGFloat { Self.topRadius(presentation) } + var bottomRadius: CGFloat { Self.bottomRadius(presentation) } + var offsetX: CGFloat { presentation == .panel ? panelOffsetX : 0 } + + static func bodySize(_ presentation: Presentation, notch: CGSize, panel: CGSize, hasNotch: Bool) -> CGSize { + switch presentation { + case .hidden: CGSize(width: notch.width, height: hasNotch ? notch.height : 0) + case .compact: CGSize(width: notch.width + 2 * earWidth, height: notch.height) + case .banner: CGSize(width: max(notch.width + 2 * earWidth, bannerWidth), height: notch.height + bannerRowHeight) + case .panel: panel + } + } + + static func topRadius(_ presentation: Presentation) -> CGFloat { + switch presentation { + case .hidden: 0 + case .compact: 6 + case .banner: 8 + case .panel: 10 + } + } + + static func bottomRadius(_ presentation: Presentation) -> CGFloat { + switch presentation { + case .hidden: 10 + case .compact: 12 + case .banner: 18 + case .panel: 22 + } + } +} + +/// Tracks the shape, not the window, which is larger while the shape animates. +final class TrackingHostingView: NSHostingView { + var onEnter: () -> Void = {} + var onExit: () -> Void = {} + var trackingRect: CGRect = .zero { + didSet { if trackingRect != oldValue { updateTrackingAreas() } } + } + /// Remove only this one; NSHostingView's own areas drive SwiftUI hover effects. + private var shapeArea: NSTrackingArea? + + override func updateTrackingAreas() { + super.updateTrackingAreas() + if let shapeArea { removeTrackingArea(shapeArea) } + shapeArea = nil + guard !trackingRect.isEmpty else { return } + let area = NSTrackingArea(rect: trackingRect, options: [.mouseEnteredAndExited, .activeAlways], owner: self) + addTrackingArea(area) + shapeArea = area + } + + override func mouseEntered(with event: NSEvent) { + if event.trackingArea === shapeArea { onEnter() } else { super.mouseEntered(with: event) } + } + + override func mouseExited(with event: NSEvent) { + if event.trackingArea === shapeArea { onExit() } else { super.mouseExited(with: event) } + } + + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } +} + +@MainActor +final class NotchWindowController { + private static let shadowPadding: CGFloat = 36 + + private let controller: SessionController + private let chrome = NotchChrome() + private let panel = NotchPanel() + private let host: TrackingHostingView + private var screen: NSScreen? + private var anchor = CGPoint.zero + private var clickMonitor: Any? + private var lastState: SessionState = .idle + /// A finished animation only tidies up if no newer one has started. + private var generation = 0 + private var trackingExtent: Extent? + private let currentRouteLabel: () -> String? + private var sessionRouteLabel: String? + + init(controller: SessionController, routeLabel: @escaping () -> String? = { nil }) { + self.controller = controller + currentRouteLabel = routeLabel + host = TrackingHostingView(rootView: NotchRootView(controller: controller, chrome: chrome)) + host.sizingOptions = [] + host.onEnter = { [weak self] in self?.pointerEntered() } + host.onExit = { [weak self] in self?.pointerExited() } + panel.contentView = host + // Global mouse monitors need no Accessibility permission. + clickMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown, .otherMouseDown]) { _ in + MainActor.assumeIsolated { controller.clickedOutside() } + } + NotificationCenter.default.addObserver(forName: NSApplication.didChangeScreenParametersNotification, + object: nil, queue: .main) { [weak self] _ in + MainActor.assumeIsolated { + guard let self else { return } + self.screen = nil + self.panel.orderOut(nil) + self.update() + } + } + observe() + update() + } + + func hide() { + panel.orderOut(nil) + } + + /// So crossing the notch on the way to the menu bar doesn't open the folded pill. + private static let hoverIntent: Duration = .milliseconds(150) + private var pendingEnter: Task? + + private func pointerEntered() { + pendingEnter?.cancel() + guard chrome.presentation == .compact else { + controller.pointerEntered() + return + } + pendingEnter = Task { [weak self] in + try? await Task.sleep(for: Self.hoverIntent) + guard !Task.isCancelled else { return } + self?.controller.pointerEntered() + } + } + + private func pointerExited() { + pendingEnter?.cancel() + pendingEnter = nil + controller.pointerExited() + } + + /// A key non-activating panel keeps the keyboard until it leaves the screen; `resignKey()` only notifies. + private func relinquishKey() { + guard panel.isKeyWindow else { return } + panel.orderOut(nil) + panel.orderFrontRegardless() + } + + private func observe() { + withObservationTracking { + _ = controller.state + _ = controller.notice + _ = controller.setupFolded + } onChange: { [weak self] in + Task { @MainActor in + self?.update() + self?.observe() + } + } + } + + static func presentation(for state: SessionState, notice: Notice?, setupFolded: Bool) -> Presentation { + switch state { + case .settingUp where setupFolded: + .compact + case .idle: + notice == nil ? .hidden : .banner + case .starting(_, folded: true), .live(.collapsed): + .compact + case .destroying, .destroyed, .failed(.ended), .failed(.destroyStuck): + .banner + case .starting(_, folded: false), .live(.peek), .live(.expanded), .failed(.start), .setupRequired, .settingUp: + .panel + } + } + + private func update() { + let state = controller.state + let notice = controller.notice + defer { lastState = state } + let target = Self.presentation(for: state, notice: notice, setupFolded: controller.setupFolded) + guard target != .hidden else { + retract() + return + } + + if screen == nil || (state == .starting(.booting, folded: false) && lastState != state) { + let next = Self.screenUnderPointer() + if next != screen { panel.orderOut(nil) } + screen = next + } + guard let screen else { return } + // The route is chosen as the VM boots, so read it once connecting. + if case .starting(.booting, _) = state, lastState != state { sessionRouteLabel = nil } + if case .starting(.connecting, _) = state, lastState != state { sessionRouteLabel = currentRouteLabel() } + let routeLabel = sessionRouteLabel + let info = ScreenInfo(screen) + let large: Bool + switch state { + case .live(.expanded): large = true + case .starting(_, folded: false): large = controller.opensExpanded + default: large = false + } + let layout = NotchGeometry.layout(for: info, mode: large ? .expanded : .peek) + let notchSize = layout.notch.size + let panelSize = layout.panel.size + let panelOffsetX = layout.panel.midX - layout.notch.midX + anchor = CGPoint(x: layout.notch.midX, y: info.frame.maxY) + + generation += 1 + let current = generation + let apply = { [chrome] in + chrome.presentation = target + chrome.content = target + chrome.reveal = 1 + chrome.state = state + chrome.notice = notice + chrome.notchSize = notchSize + chrome.panelSize = panelSize + chrome.panelOffsetX = panelOffsetX + chrome.hasNotch = info.hasNotch + chrome.routeLabel = routeLabel + } + let targetExtent = Self.extent(target, notch: notchSize, panel: panelSize, offset: panelOffsetX, hasNotch: info.hasNotch) + setTracking(targetExtent) + + if !panel.isVisible { + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + apply() + chrome.presentation = .hidden + chrome.reveal = 0 + } + let hiddenExtent = Self.extent(.hidden, notch: notchSize, panel: panelSize, offset: 0, hasNotch: info.hasNotch) + fitWindow(hiddenExtent, targetExtent) + panel.orderFrontRegardless() + // Draw while invisible and start ~2 frames later, or the first draw (fonts, symbols) stalls the animation. + withTransaction(transaction) { host.layoutSubtreeIfNeeded() } + panel.displayIfNeeded() + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(35)) { [weak self] in + guard let self, self.generation == current else { return } + self.animate(from: .hidden, to: target, apply: apply, generation: current, extent: targetExtent) + } + } else { + let from = chrome.presentation + let currentExtent = Self.extent(from, notch: chrome.notchSize, panel: chrome.panelSize, + offset: chrome.offsetX, hasNotch: chrome.hasNotch) + fitWindow(currentExtent, targetExtent) + animate(from: from, to: target, apply: apply, generation: current, extent: targetExtent) + } + + // Only a click into the page (VideoView) makes the panel key; opening never does. + if target != .panel { relinquishKey() } + } + + private func animate(from: Presentation, to target: Presentation, apply: @escaping () -> Void, + generation current: Int, extent: Extent) { + withAnimation(Self.animation(from: from, to: target), completionCriteria: .removed) { + apply() + } completion: { [weak self] in + guard let self, self.generation == current else { return } + self.fitWindow(extent) + } + } + + private func retract() { + guard panel.isVisible else { + screen = nil + return + } + generation += 1 + let current = generation + setTracking(nil) + relinquishKey() + withAnimation(Motion.hide, completionCriteria: .removed) { + chrome.presentation = .hidden + chrome.reveal = 0 + } completion: { [weak self] in + guard let self, self.generation == current else { return } + self.panel.orderOut(nil) + self.screen = nil + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { self.chrome.content = .hidden } + } + } + + private static func animation(from: Presentation, to target: Presentation) -> Animation { + switch (from, target) { + case (.panel, .panel): Motion.resize + case (_, .panel): Motion.open + case (.panel, _): Motion.fold + default: Motion.small + } + } + + // MARK: - Geometry + + struct Extent { + /// Reach on the wider side of the notch's centre line. + var halfWidth: CGFloat + var height: CGFloat + var offset: CGFloat = 0 + var width: CGFloat = 0 + } + + static func extent(_ presentation: Presentation, notch: CGSize, panel: CGSize, offset: CGFloat, hasNotch: Bool) -> Extent { + let body = NotchChrome.bodySize(presentation, notch: notch, panel: panel, hasNotch: hasNotch) + let width = body.width + 2 * NotchChrome.topRadius(presentation) + let shift = presentation == .panel ? offset : 0 + return Extent(halfWidth: width / 2 + abs(shift), height: body.height, offset: shift, width: width) + } + + private func fitWindow(_ extents: Extent...) { + let half = (extents.map(\.halfWidth).max() ?? 0) + Self.shadowPadding + let height = (extents.map(\.height).max() ?? 0) + Self.shadowPadding + let frame = CGRect(x: (anchor.x - half).rounded(), y: anchor.y - height.rounded(.up), + width: (2 * half).rounded(.up), height: height.rounded(.up)) + if panel.frame != frame { + panel.setFrame(frame, display: false) + // Lay out outside any animation, or the shape animates across from the old window position. + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { host.layoutSubtreeIfNeeded() } + } + refreshTracking() + } + + private func setTracking(_ extent: Extent?) { + trackingExtent = extent + refreshTracking() + } + + private func refreshTracking() { + guard let extent = trackingExtent else { + host.trackingRect = .zero + return + } + let bounds = host.bounds + let y = host.isFlipped ? bounds.minY : bounds.maxY - extent.height + host.trackingRect = CGRect(x: bounds.midX + extent.offset - extent.width / 2, y: y, + width: extent.width, height: extent.height) + } + + #if DEBUG + func snapshot(to directory: URL, name: String) { + guard panel.isVisible else { + FileHandle.standardError.write(Data("snapshot \(name): window hidden\n".utf8)) + return + } + DebugSnapshot.write(host, to: directory.appendingPathComponent("\(name).png")) + } + #endif + + private static func screenUnderPointer() -> NSScreen? { + let pointer = NSEvent.mouseLocation + return NSScreen.screens.first { NSMouseInRect(pointer, $0.frame, false) } ?? NSScreen.main + } +} + +extension ScreenInfo { + /// The only code that reads `NSScreen`. + @MainActor + init(_ screen: NSScreen) { + func global(_ rect: CGRect?) -> CGRect { + guard let rect, !rect.isEmpty else { return .zero } + // The auxiliary areas are documented in screen coordinates; make them global. + return screen.frame.contains(rect.origin) ? rect : rect.offsetBy(dx: screen.frame.minX, dy: screen.frame.minY) + } + self.init(frame: screen.frame, visibleFrame: screen.visibleFrame, + safeAreaTop: screen.safeAreaInsets.top, + auxLeft: global(screen.auxiliaryTopLeftArea), auxRight: global(screen.auxiliaryTopRightArea), + menuBarThickness: NSStatusBar.system.thickness) + } +} diff --git a/Sources/Knotchy/SettingsView.swift b/Sources/Knotchy/SettingsView.swift new file mode 100644 index 0000000..8197665 --- /dev/null +++ b/Sources/Knotchy/SettingsView.swift @@ -0,0 +1,291 @@ +import KnotchyCore +import SwiftUI +import UniformTypeIdentifiers + +enum SettingsTab: String, CaseIterable, Identifiable { + case general, network, vm + + var id: Self { self } + + var title: String { + switch self { + case .general: "General" + case .network: "Network" + case .vm: "VM" + } + } + + var symbol: String { + switch self { + case .general: "gearshape" + case .network: "network" + case .vm: "shippingbox" + } + } +} + +@MainActor +@Observable +final class SettingsNavigation { + var section: SettingsTab? = .general +} + +@MainActor +final class SettingsWindowController { + static let size = CGSize(width: 700, height: 470) + + private let app: AppDelegate + private let navigation = SettingsNavigation() + private(set) var window: NSWindow? + + init(app: AppDelegate) { + self.app = app + } + + func show(_ section: SettingsTab? = nil) { + if let section { navigation.section = section } + let window = self.window ?? makeWindow() + self.window = window + // An accessory app's windows open behind the frontmost app unless it activates first. + NSApp.activate() + window.makeKeyAndOrderFront(nil) + } + + private func makeWindow() -> NSWindow { + let host = NSHostingController(rootView: SettingsView(app: app, settings: app.settings, navigation: navigation)) + host.sceneBridgingOptions = [.title, .toolbars] + let window = NSWindow(contentViewController: host) + window.styleMask = [.titled, .closable, .miniaturizable, .fullSizeContentView] + window.toolbarStyle = .unified + window.title = "Knotchy Settings" + window.isReleasedWhenClosed = false + window.setContentSize(Self.size) + window.center() + window.setFrameAutosaveName("KnotchySettings") + return window + } +} + +struct SettingsView: View { + let app: AppDelegate + @Bindable var settings: AppSettings + @Bindable var navigation: SettingsNavigation + + var body: some View { + NavigationSplitView { + List(SettingsTab.allCases, selection: $navigation.section) { section in + Label(section.title, systemImage: section.symbol) + } + .navigationSplitViewColumnWidth(180) + .toolbar(removing: .sidebarToggle) + } detail: { + Group { + switch navigation.section ?? .general { + case .general: GeneralSettings(settings: settings) + case .network: NetworkSettings(settings: settings) + case .vm: VMSettings(controller: app.controller, status: app.vmStatus) + } + } + .navigationTitle((navigation.section ?? .general).title) + } + .frame(width: SettingsWindowController.size.width, height: SettingsWindowController.size.height) + } +} + +private struct GeneralSettings: View { + @Bindable var settings: AppSettings + @State private var opensAtLogin = LoginItem.isEnabled + @State private var loginError: String? + + var body: some View { + Form { + Section { + Picker("Destroy folded pages", selection: $settings.autoDestroyMinutes) { + ForEach(AppSettings.autoDestroyChoices, id: \.self) { minutes in + Text(minutes == 1 ? "After 1 minute" : "After \(minutes) minutes").tag(minutes) + } + } + Picker("Open pages in", selection: $settings.opensLarge) { + Text("Small panel").tag(false) + Text("Large panel").tag(true) + } + } footer: { + Text("A page folded into the notch keeps running until it's destroyed.") + } + + Section { + Toggle("Open copied link with \(GlobalHotKey.openCopiedLinkLabel)", isOn: $settings.copiedLinkShortcut) + } footer: { + Text("Copy a link anywhere (in Chrome: right-click › Copy Link Address), then press \(GlobalHotKey.openCopiedLinkLabel).") + } + + Section { + Toggle("Open Knotchy at login", isOn: Binding { + opensAtLogin + } set: { enabled in + loginError = LoginItem.set(enabled) + opensAtLogin = LoginItem.isEnabled + }) + } footer: { + if let loginError { Text(loginError).foregroundStyle(.red) } + } + } + .formStyle(.grouped) + .scrollDisabled(true) + .onAppear { opensAtLogin = LoginItem.isEnabled } + } +} + +// Every mode's form stays laid out, inactive ones hidden, so switching crossfades instead of resizing the window. +private struct NetworkSettings: View { + @Bindable var settings: AppSettings + @State private var importing = false + @State private var importMessage: String? + + var body: some View { + ZStack(alignment: .top) { + ForEach([AppSettings.NetworkMode.direct, .proxy, .vpn], id: \.self) { mode in + let active = settings.networkMode == mode + NetworkForm(settings: settings, mode: mode, importMessage: importMessage, importing: $importing) + .opacity(active ? 1 : 0) + .allowsHitTesting(active) + .accessibilityHidden(!active) + .animation(active ? .easeOut(duration: 0.2).delay(0.05) : .easeOut(duration: 0.1), value: active) + } + } + .fileImporter(isPresented: $importing, + allowedContentTypes: [UTType(filenameExtension: "conf") ?? .data, .zip], + allowsMultipleSelection: true) { result in + if case .success(let urls) = result { + Task { importMessage = await settings.importWireGuard(from: urls) } + } + } + } +} + +private struct NetworkForm: View { + @Bindable var settings: AppSettings + let mode: AppSettings.NetworkMode + let importMessage: String? + @Binding var importing: Bool + + var body: some View { + Form { + Section { + Picker("Connection", selection: $settings.networkMode) { + Text("Direct").tag(AppSettings.NetworkMode.direct) + Text("Proxy").tag(AppSettings.NetworkMode.proxy) + Text("WireGuard VPN").tag(AppSettings.NetworkMode.vpn) + } + .pickerStyle(.radioGroup) + } footer: { + Text(description) + } + + details + .disabled(settings.networkMode != mode) // No tabbing into a hidden form. + } + .formStyle(.grouped) + .scrollDisabled(true) + } + + private var description: String { + switch mode { + case .direct: "Pages go out through this Mac's internet connection, and can reach nothing else." + case .proxy: "Pages can only go out through the proxy. If it's down, they don't load." + case .vpn: "Pages can only go out through the VPN. If it's down, they don't load. The rest of your Mac isn't affected." + } + } + + @ViewBuilder private var details: some View { + switch mode { + case .direct: + Section { + LabeledContent("Internet", value: "This Mac's connection") + LabeledContent("Blocked", value: "Your Mac, your network, IPv6") + LabeledContent("DNS", value: "1.1.1.1 (Cloudflare)") + } header: { + Text("Direct") + } + case .proxy: + Section { + Picker("Type", selection: $settings.proxyKind) { + Text("HTTP").tag(ProxySettings.Kind.http) + Text("SOCKS5").tag(ProxySettings.Kind.socks5) + } + TextField("Server", text: $settings.proxyHost, prompt: Text("proxy.example.com")) + TextField("Port", value: $settings.proxyPort, format: .number.grouping(.never), prompt: Text("8080")) + TextField("Username", text: $settings.proxyUsername, prompt: Text("Optional")) + SecureField("Password", text: $settings.proxyPassword, prompt: Text("Optional")) + } header: { + Text("Proxy") + } footer: { + if settings.proxyIsLocal { + Text("Proxies on this Mac or your network aren't allowed: pages could reach your network through them.") + .foregroundStyle(.orange) + } else { + Text("The password is kept in your keychain.") + } + } + case .vpn: + Section { + if settings.wireGuardNames.isEmpty { + LabeledContent("Server", value: "None") + LabeledContent("Endpoint", value: "—") + LabeledContent("DNS", value: "—") + LabeledContent("No configs imported") { + Button("Import…") { importing = true } + } + } else { + Picker("Server", selection: $settings.wireGuardServer) { + ForEach(settings.wireGuardNames, id: \.self) { Text($0).tag($0) } + } + let config = settings.selectedWireGuard + LabeledContent("Endpoint", value: config.map { "\($0.endpointHost):\(String($0.endpointPort))" } ?? "—") + LabeledContent("DNS", value: config.map { $0.dns.isEmpty ? "1.1.1.1" : $0.dns.joined(separator: ", ") } ?? "—") + LabeledContent(settings.wireGuardNames.count == 1 ? "1 config" : "\(settings.wireGuardNames.count) configs") { + HStack { + Button("Remove All") { settings.removeWireGuardConfigs() } + Button("Import…") { importing = true } + } + } + } + } header: { + Text("WireGuard") + } footer: { + if let importMessage { + Text(importMessage).foregroundStyle(.orange).lineLimit(2) + } else { + Text("Import the .conf files, or the .zip, from Mullvad, Proton VPN, IVPN or others.") + } + } + } + } +} + +private struct VMSettings: View { + let controller: SessionController + let status: VMStatus + + var body: some View { + Form { + Section { + LabeledContent("Container runtime", value: status.runtimeSummary) + LabeledContent("VM image", value: status.summary) + LabeledContent("Rebuild") { + Button(status.image == .missing ? "Build Now" : "Rebuild Now") { controller.rebuildImage() } + .disabled(isSettingUp || status.image == .noContainerTool) + } + } footer: { + Text("Rebuilding takes about 3 minutes and picks up the latest WebKit security fixes. Progress shows in the notch.") + } + } + .formStyle(.grouped) + .scrollDisabled(true) + .onAppear { status.refresh() } + } + + private var isSettingUp: Bool { + if case .settingUp = controller.state { true } else { false } + } +} diff --git a/Sources/Knotchy/SetupWindow.swift b/Sources/Knotchy/SetupWindow.swift new file mode 100644 index 0000000..589d149 --- /dev/null +++ b/Sources/Knotchy/SetupWindow.swift @@ -0,0 +1,245 @@ +import AppKit +import KnotchyCore +import SwiftUI + +@MainActor +final class SetupWindowController: NSObject, NSWindowDelegate { + enum Content { + case setup(SetupFlow) + case unsupported(Unsupported) + } + + private let content: Content + private(set) var window: NSWindow? + + init(_ content: Content) { + self.content = content + } + + func show() { + let window = self.window ?? makeWindow() + self.window = window + // An accessory app's windows open behind the frontmost app unless it activates first. + NSApp.activate() + window.makeKeyAndOrderFront(nil) + if case .setup(let flow) = content { Task { await flow.refresh() } } + } + + private func makeWindow() -> NSWindow { + let root = switch content { + case .setup(let flow): AnyView(SetupView(flow: flow) { [weak self] in self?.window?.close() }) + case .unsupported(let reason): AnyView(UnsupportedView(reason: reason)) + } + let host = NSHostingController(rootView: root) + let window = NSWindow(contentViewController: host) + window.styleMask = [.titled, .closable, .fullSizeContentView] + window.titlebarAppearsTransparent = true + window.titleVisibility = .hidden + window.title = "Welcome to Knotchy" + window.isReleasedWhenClosed = false + window.setContentSize(host.view.fittingSize) + window.center() + window.delegate = self + return window + } + + func windowWillClose(_ notification: Notification) { + switch content { + case .unsupported: + NSApp.terminate(nil) + case .setup(let flow): + if flow.allRequiredDone { UserDefaults.standard.set(true, forKey: SetupLaunchRule.completedKey) } + } + } +} + +struct SetupView: View { + let flow: SetupFlow + let close: () -> Void + @State private var browser = ChromeExtension.installedBrowser() + @State private var addedToBrowser = false + + var body: some View { + VStack(alignment: .leading, spacing: 20) { + HStack(spacing: 14) { + Image(nsImage: NSApp.applicationIconImage) + .resizable() + .frame(width: 56, height: 56) + VStack(alignment: .leading, spacing: 2) { + Text("Welcome to Knotchy").font(.title2.weight(.semibold)) + Text("A few things to set up before your first link.").foregroundStyle(.secondary) + } + } + GroupBox { + VStack(spacing: 0) { + SetupRow(title: "This Mac", status: flow.mac) { Task { await flow.refresh() } } + Divider() + SetupRow(title: "Container runtime", status: flow.runtime) { flow.start() } + Divider() + SetupRow(title: "Browser VM", status: flow.vm) { flow.start() } + if let browser { + Divider() + BrowserRow(browser: browser, added: $addedToBrowser) + } + } + .padding(.horizontal, 6) + } + HStack { + Spacer() + if flow.allRequiredDone { + Button("Done", action: close) + .keyboardShortcut(.defaultAction) + } else { + Button("Not Now", action: close) + Button("Set Up") { flow.start() } + .keyboardShortcut(.defaultAction) + .disabled(!flow.canSetUp) + } + } + } + .padding(24) + .frame(width: 540) + } +} + +private struct SetupRow: View { + let title: String + let status: SetupFlow.Status + let tryAgain: () -> Void + @State private var showsDetails = false + + // Buttons sit on the title line and the detail reserves two lines, so no state changes the height. + var body: some View { + HStack(alignment: .top, spacing: 12) { + symbol.frame(width: 20, height: 22) + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 8) { + Text(title) + Spacer(minLength: 8) + if case .failed(_, let details) = status { + if !details.isEmpty { + Button("Show Details") { showsDetails = true } + .popover(isPresented: $showsDetails, arrowEdge: .bottom) { DetailsView(lines: details) } + } + Button("Try Again", action: tryAgain) + } + } + .controlSize(.small) + .frame(minHeight: 22) + Text(detail) + .font(.callout) + .foregroundStyle(isFailed ? AnyShapeStyle(.red) : AnyShapeStyle(.secondary)) + .lineLimit(2, reservesSpace: true) + } + } + .padding(.vertical, 10) + } + + private var detail: String { + switch status { + case .pending(let text), .running(let text, _), .done(let text), .failed(let text, _): text + } + } + + private var isFailed: Bool { + if case .failed = status { true } else { false } + } + + @ViewBuilder private var symbol: some View { + switch status { + case .pending: + Image(systemName: "circle.dashed").imageScale(.large).foregroundStyle(.tertiary) + case .running(_, let fraction?): + ProgressView(value: fraction).progressViewStyle(.circular).controlSize(.small) + case .running: + ProgressView().controlSize(.small) + case .done: + Image(systemName: "checkmark.circle.fill").imageScale(.large).foregroundStyle(.green) + case .failed: + Image(systemName: "exclamationmark.circle.fill").imageScale(.large).foregroundStyle(.red) + } + } +} + +private struct BrowserRow: View { + let browser: ChromeExtension.Browser + @Binding var added: Bool + + var body: some View { + HStack(alignment: .top, spacing: 12) { + Image(systemName: "puzzlepiece.extension") + .imageScale(.large) + .foregroundStyle(.secondary) + .frame(width: 20, height: 22) + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 6) { + Text("Open links from \(browser.name)") + Text("Optional").font(.caption).foregroundStyle(.tertiary) + Spacer(minLength: 8) + Button("Add to \(browser.name)") { + ChromeExtension.open(in: browser) + added = true + } + .controlSize(.small) + } + .frame(minHeight: 22) + Text(added ? "In \(browser.name), right-click a link and choose Open in Knotchy." + : "Safari and Mail already work from the right-click menu.") + .font(.callout) + .foregroundStyle(.secondary) + .lineLimit(2, reservesSpace: true) + } + } + .padding(.vertical, 10) + } +} + +private struct DetailsView: View { + let lines: [String] + + var body: some View { + ScrollView { + Text(lines.joined(separator: "\n")) + .font(.system(.caption, design: .monospaced)) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + } + .frame(width: 440, height: 220) + } +} + +private struct UnsupportedView: View { + let reason: Unsupported + + var body: some View { + VStack(spacing: 14) { + Image(nsImage: NSApp.applicationIconImage) + .resizable() + .frame(width: 72, height: 72) + Text(title).font(.title2.weight(.semibold)) + Text(message) + .multilineTextAlignment(.center) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + Button("Quit") { NSApp.terminate(nil) } + .keyboardShortcut(.defaultAction) + .padding(.top, 6) + } + .padding(32) + .frame(width: 440) + } + + private var title: String { + switch reason { + case .noVirtualization: "This Mac can't run virtual machines right now" + } + } + + private var message: String { + switch reason { + case .noVirtualization: + "Knotchy opens every link in its own virtual machine. If macOS is itself running in a virtual machine, run Knotchy on the host Mac instead." + } + } +} diff --git a/Sources/Knotchy/VideoSurface.swift b/Sources/Knotchy/VideoSurface.swift new file mode 100644 index 0000000..1276362 --- /dev/null +++ b/Sources/Knotchy/VideoSurface.swift @@ -0,0 +1,234 @@ +import AVFoundation +import AppKit +import CoreMedia +import KnotchyCore +import SwiftUI +import Synchronization + +final class LatestFrame: Sendable { + private let frame = Mutex(nil) + + func put(_ new: DecodedFrame) -> Bool { + frame.withLock { slot in + defer { slot = new } + return slot == nil + } + } + + func take() -> DecodedFrame? { + frame.withLock { slot in + defer { slot = nil } + return slot + } + } +} + +struct VideoSurface: NSViewRepresentable { + let controller: SessionController + + func makeNSView(context: Context) -> VideoView { + let view = VideoView() + view.controller = controller + return view + } + + func updateNSView(_ view: VideoView, context: Context) { + view.attach(controller.connection) + } +} + +final class VideoView: NSView { + weak var controller: SessionController? + private var connection: (any StreamConnection)? + private let displayLayer = AVSampleBufferDisplayLayer() + private var scrollRemainder = CGPoint.zero + /// WebKit scrolls about 40 px per wheel tick. + private let scrollStep: CGFloat = 40 + + override init(frame: NSRect) { + super.init(frame: frame) + wantsLayer = true + layer?.backgroundColor = NSColor.black.cgColor + displayLayer.videoGravity = .resizeAspect + displayLayer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable] + layer?.addSublayer(displayLayer) + } + + required init?(coder: NSCoder) { fatalError("not used") } + + override var isFlipped: Bool { true } + override var acceptsFirstResponder: Bool { true } + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } + + override func layout() { + super.layout() + CATransaction.begin() + CATransaction.setDisableActions(true) + displayLayer.frame = bounds + CATransaction.commit() + #if DEBUG + if ProcessInfo.processInfo.environment["KNOTCHY_LOG_LAYOUT"] != nil { + print(String(format: "%.3f video %.0fx%.0f", CACurrentMediaTime(), bounds.width, bounds.height)) + } + #endif + } + + func attach(_ connection: (any StreamConnection)?) { + guard self.connection !== connection else { return } + self.connection = connection + displayLayer.sampleBufferRenderer.flush() + let slot = LatestFrame() + connection?.setFrameHandler { [weak self] frame in + // Keep only the newest frame so a busy main thread drops frames instead of queueing multi-MB buffers. + guard slot.put(frame) else { return } + DispatchQueue.main.async { + guard let frame = slot.take() else { return } + MainActor.assumeIsolated { self?.show(frame.pixelBuffer) } + } + } + } + + private func show(_ pixelBuffer: CVPixelBuffer) { + var format: CMVideoFormatDescription? + CMVideoFormatDescriptionCreateForImageBuffer(allocator: nil, imageBuffer: pixelBuffer, formatDescriptionOut: &format) + guard let format else { return } + var timing = CMSampleTimingInfo(duration: .invalid, presentationTimeStamp: .zero, decodeTimeStamp: .invalid) + var sample: CMSampleBuffer? + CMSampleBufferCreateReadyWithImageBuffer(allocator: nil, imageBuffer: pixelBuffer, formatDescription: format, + sampleTiming: &timing, sampleBufferOut: &sample) + guard let sample else { return } + if let attachments = CMSampleBufferGetSampleAttachmentsArray(sample, createIfNecessary: true), + CFArrayGetCount(attachments) > 0 { + let dictionary = unsafeBitCast(CFArrayGetValueAtIndex(attachments, 0), to: CFMutableDictionary.self) + CFDictionarySetValue(dictionary, + Unmanaged.passUnretained(kCMSampleAttachmentKey_DisplayImmediately).toOpaque(), + Unmanaged.passUnretained(kCFBooleanTrue).toOpaque()) + } + let renderer = displayLayer.sampleBufferRenderer + if renderer.status == .failed { renderer.flush() } + renderer.enqueue(sample) + } + + // MARK: - Mouse + + override func updateTrackingAreas() { + super.updateTrackingAreas() + for area in trackingAreas where area.owner === self { removeTrackingArea(area) } + addTrackingArea(NSTrackingArea(rect: .zero, options: [.mouseMoved, .activeAlways, .inVisibleRect], owner: self)) + } + + private func videoPoint(_ event: NSEvent) -> (x: UInt16, y: UInt16)? { + NotchGeometry.videoPoint(convert(event.locationInWindow, from: nil), surfaceSize: bounds.size, + renderSize: SessionController.renderSize) + } + + private func mods(_ event: NSEvent) -> Modifiers { + var mods: Modifiers = [] + let flags = event.modifierFlags + if flags.contains(.shift) { mods.insert(.shift) } + // ⌘ maps to Ctrl so ⌘A/C/V/X/Z work like Linux shortcuts in the VM. + if flags.contains(.control) || flags.contains(.command) { mods.insert(.control) } + if flags.contains(.option) { mods.insert(.alt) } + let buttons = NSEvent.pressedMouseButtons + if buttons & 1 != 0 { mods.insert(.leftButton) } + if buttons & 2 != 0 { mods.insert(.rightButton) } + if buttons & 4 != 0 { mods.insert(.middleButton) } + return mods + } + + private func sendMove(_ event: NSEvent) { + guard let point = videoPoint(event) else { return } + connection?.send(.pointerMove(x: point.x, y: point.y, mods: mods(event))) + } + + private func sendButton(_ event: NSEvent, _ button: PointerButton, pressed: Bool) { + guard let point = videoPoint(event) else { return } + connection?.send(.pointerButton(button, pressed: pressed, x: point.x, y: point.y, mods: mods(event))) + } + + override func mouseMoved(with event: NSEvent) { sendMove(event) } + override func mouseDragged(with event: NSEvent) { sendMove(event) } + override func rightMouseDragged(with event: NSEvent) { sendMove(event) } + override func otherMouseDragged(with event: NSEvent) { sendMove(event) } + + override func mouseDown(with event: NSEvent) { + controller?.panelClicked() + window?.makeKey() + window?.makeFirstResponder(self) + sendButton(event, .left, pressed: true) + } + + override func mouseUp(with event: NSEvent) { sendButton(event, .left, pressed: false) } + override func rightMouseDown(with event: NSEvent) { sendButton(event, .right, pressed: true) } + override func rightMouseUp(with event: NSEvent) { sendButton(event, .right, pressed: false) } + override func otherMouseDown(with event: NSEvent) { sendButton(event, .middle, pressed: true) } + override func otherMouseUp(with event: NSEvent) { sendButton(event, .middle, pressed: false) } + + override func scrollWheel(with event: NSEvent) { + var dx = event.scrollingDeltaX + var dy = event.scrollingDeltaY + if event.hasPreciseScrollingDeltas { + scrollRemainder.x += dx + scrollRemainder.y += dy + dx = (scrollRemainder.x / scrollStep).rounded(.towardZero) + dy = (scrollRemainder.y / scrollStep).rounded(.towardZero) + scrollRemainder.x -= dx * scrollStep + scrollRemainder.y -= dy * scrollStep + } else { + dx = dx == 0 ? 0 : (dx > 0 ? max(1, dx.rounded()) : min(-1, dx.rounded())) + dy = dy == 0 ? 0 : (dy > 0 ? max(1, dy.rounded()) : min(-1, dy.rounded())) + } + guard dx != 0 || dy != 0, let point = videoPoint(event) else { return } + connection?.send(.scroll(ticksX: Int16(clamping: Int(dx)), ticksY: Int16(clamping: Int(dy)), x: point.x, y: point.y)) + } + + // MARK: - Keyboard + + private static let specialKeys: [UInt16: String] = [ + 36: "Return", 76: "KP_Enter", 48: "Tab", 51: "BackSpace", 117: "Delete", 53: "Escape", + 49: "space", 123: "Left", 124: "Right", 125: "Down", 126: "Up", + 115: "Home", 119: "End", 116: "Page_Up", 121: "Page_Down", + 122: "F1", 120: "F2", 99: "F3", 118: "F4", 96: "F5", 97: "F6", + 98: "F7", 100: "F8", 101: "F9", 109: "F10", 103: "F11", 111: "F12", + ] + + private func keyName(_ event: NSEvent) -> String? { + if let name = Self.specialKeys[event.keyCode] { return name } + let flags = event.modifierFlags + let characters = flags.contains(.command) || flags.contains(.control) + ? event.charactersIgnoringModifiers?.lowercased() + : event.characters + guard let scalars = characters?.unicodeScalars, scalars.count == 1, let scalar = scalars.first, + scalar.value >= 0x20, scalar.value != 0x7F, !(0xF700...0xF8FF).contains(scalar.value) + else { return nil } + return String(scalar) + } + + private func sendKey(_ event: NSEvent, pressed: Bool) { + guard let name = keyName(event) else { return } + connection?.send(.key(pressed: pressed, mods: mods(event), name: name)) + } + + override func keyDown(with event: NSEvent) { sendKey(event, pressed: true) } + override func keyUp(with event: NSEvent) { sendKey(event, pressed: false) } + + override func performKeyEquivalent(with event: NSEvent) -> Bool { + guard event.type == .keyDown, event.modifierFlags.contains(.command), let controller else { + return super.performKeyEquivalent(with: event) + } + let key = event.charactersIgnoringModifiers?.lowercased() ?? "" + let shift = event.modifierFlags.contains(.shift) + switch (key, shift) { + case ("w", false), (".", false): controller.destroy() + case ("e", true): controller.state == .live(.expanded) ? controller.shrink() : controller.expand() + case ("[", false): controller.goBack() + case ("]", false): controller.goForward() + case ("r", false): controller.reload() + case ("q", false): NSApp.terminate(nil) + default: + sendKey(event, pressed: true) + sendKey(event, pressed: false) + } + return true + } +} diff --git a/Sources/KnotchyCore/Backend/CLIRunner.swift b/Sources/KnotchyCore/Backend/CLIRunner.swift new file mode 100644 index 0000000..ddf4579 --- /dev/null +++ b/Sources/KnotchyCore/Backend/CLIRunner.swift @@ -0,0 +1,115 @@ +import Foundation +import Synchronization + +public struct CLIRunner: Sendable { + public struct Output: Sendable { + public var status: Int32 + public var text: String + + public var lastLine: String { + text.split(whereSeparator: \.isNewline).last.map(String.init) ?? "exit status \(status)" + } + } + + public enum Failure: Error, Equatable { + case notInstalled + case timedOut([String]) + case launchFailed(String) + } + + // Fixed paths: apps launched from the Services menu get a minimal PATH. + public static let searchPaths = ["/usr/local/bin/container", "/opt/homebrew/bin/container"] + public let searchPaths: [String] + + public init(searchPaths: [String] = CLIRunner.searchPaths) { + self.searchPaths = searchPaths + } + + /// Looked up on every call, so a runtime installed while the app runs is found. + public var executable: String? { + searchPaths.first { FileManager.default.isExecutableFile(atPath: $0) } + } + + public func run(_ args: [String], timeout: TimeInterval, + line: (@Sendable (String) -> Void)? = nil) async throws -> Output { + try await withCheckedThrowingContinuation { continuation in + DispatchQueue.global(qos: .userInitiated).async { + continuation.resume(with: Result { try self.runBlocking(args, timeout: timeout, line: line) }) + } + } + } + + static let outputLimit = 1024 * 1024 + + private static let builds = Mutex(Set()) + + /// Builds would outlive the app. Other commands are left to the quit path, which waits for them. + public static func stopBuilds() { + for pid in builds.withLock({ $0 }) { kill(pid, SIGTERM) } + } + + public func runBlocking(_ args: [String], timeout: TimeInterval, + line: (@Sendable (String) -> Void)? = nil) throws -> Output { + guard let executable else { throw Failure.notInstalled } + let process = Process() + process.executableURL = URL(fileURLWithPath: executable) + process.arguments = args + let pipe = Pipe() + process.standardOutput = pipe + process.standardError = pipe + + // Only the tail is kept: a noisy build can print for minutes. + let limit = Self.outputLimit + let collected = Mutex(Data()) + let pending = Mutex(Data()) + pipe.fileHandleForReading.readabilityHandler = { handle in + let chunk = handle.availableData + guard !chunk.isEmpty else { return } + collected.withLock { buffer in + buffer.append(chunk) + if buffer.count > limit { buffer.removeFirst(buffer.count - limit) } + } + guard let line else { return } + let lines: [String] = pending.withLock { buffer in + buffer.append(chunk) + var out: [String] = [] + while let newline = buffer.firstIndex(of: UInt8(ascii: "\n")) { + out.append(String(decoding: buffer[buffer.startIndex.. 16 * 1024 { // A line that never ends. + out.append(String(decoding: buffer.prefix(16 * 1024), as: UTF8.self)) + buffer.removeAll() + } + return out + } + for text in lines where !text.trimmingCharacters(in: .whitespaces).isEmpty { line(text) } + } + + let finished = DispatchSemaphore(value: 0) + process.terminationHandler = { _ in finished.signal() } + do { + try process.run() + } catch { + pipe.fileHandleForReading.readabilityHandler = nil + throw Failure.launchFailed(error.localizedDescription) + } + let pid = process.processIdentifier + let isBuild = args.first == "build" + if isBuild { Self.builds.withLock { _ = $0.insert(pid) } } + defer { if isBuild { Self.builds.withLock { _ = $0.remove(pid) } } } + if finished.wait(timeout: .now() + timeout) == .timedOut { + process.terminate() + if finished.wait(timeout: .now() + 1) == .timedOut { + kill(process.processIdentifier, SIGKILL) + finished.wait() + } + pipe.fileHandleForReading.readabilityHandler = nil + throw Failure.timedOut(args) + } + pipe.fileHandleForReading.readabilityHandler = nil + let rest = pipe.fileHandleForReading.readDataToEndOfFile() + let data = collected.withLock { ($0 + rest).suffix(limit) } + return Output(status: process.terminationStatus, text: String(decoding: data, as: UTF8.self)) + } +} diff --git a/Sources/KnotchyCore/Backend/ContainerCLIBackend.swift b/Sources/KnotchyCore/Backend/ContainerCLIBackend.swift new file mode 100644 index 0000000..464bf70 --- /dev/null +++ b/Sources/KnotchyCore/Backend/ContainerCLIBackend.swift @@ -0,0 +1,311 @@ +import Foundation +import Synchronization + +public final class ContainerCLIBackend: VMBackend { + public static let image = "local/knotchy-vm:latest" + public static let staleAfterDays = 14 + + private let cli: CLIRunner + private let vmSources: URL? + private let runDirectory: URL + private let startsInFlight = Mutex(0) + private let route = Mutex(NetworkRoute.direct) + private let startedRoute = Mutex(NetworkRoute.direct) + + public var lastStartedRoute: NetworkRoute { startedRoute.withLock { $0 } } + + public func setNetworkRoute(_ newRoute: NetworkRoute) { + route.withLock { $0 = newRoute } + } + + /// Test-only (knotchyctl --allow-local-proxy); the app never sets it. + private let localProxyAllowed = Mutex(false) + + public func allowLocalProxy() { + localProxyAllowed.withLock { $0 = true } + } + + public init(vmSources: URL?, cli: CLIRunner = CLIRunner()) { + self.vmSources = vmSources + self.cli = cli + runDirectory = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("knotchy", isDirectory: true) + } + + // MARK: Preflight + + public func preflight() async -> PreflightResult { + guard cli.executable != nil else { return .issue(.cliMissing) } + if case .outdated(let found) = await runtimeStatus() { return .issue(.cliOutdated(found: found)) } + guard let status = try? await cli.run(["system", "status"], timeout: 15), status.status == 0, + field("status", in: status.text) == "running" + else { return .issue(.serviceStopped) } + let appRoot = field("paths.appRoot", in: status.text) + ?? NSHomeDirectory() + "/Library/Application Support/com.apple.container/" + let kernel = URL(fileURLWithPath: appRoot).appendingPathComponent("kernels/default.kernel-arm64") + guard FileManager.default.fileExists(atPath: kernel.path) else { return .issue(.kernelMissing) } + + guard let inspect = try? await cli.run(["image", "inspect", Self.image], timeout: 15), inspect.status == 0 + else { return .issue(.imageMissing) } + let labels = Self.labels(in: inspect.text) + let found = labels["aculix.knotchy.protocol"].flatMap(Int.init) ?? 0 + guard found == WireProtocol.version else { + return .issue(.imageOutdated(found: found, need: WireProtocol.version)) + } + prepareRunDirectory() + return .ready(appRoot: appRoot, staleImage: Self.staleness(built: labels["aculix.knotchy.built"])) + } + + /// Resolved on the Mac: a proxy route gives the VM no DNS, and a VPN's DNS needs the tunnel up. + static func resolveIPv4(_ host: String) throws -> String { + var address = host + if !WireGuardConfig.isIPv4(host) { + var hints = addrinfo() + hints.ai_family = AF_INET + hints.ai_socktype = SOCK_STREAM + var result: UnsafeMutablePointer? + guard getaddrinfo(host, nil, &hints, &result) == 0, let first = result, let sockaddr = first.pointee.ai_addr else { + throw VMBackendError.failed("Couldn't find the server “\(host)”. Check Settings → Network.") + } + defer { freeaddrinfo(result) } + var buffer = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) + var inAddr = sockaddr.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { $0.pointee.sin_addr } + guard inet_ntop(AF_INET, &inAddr, &buffer, socklen_t(buffer.count)) != nil else { + throw VMBackendError.failed("Couldn't find the server “\(host)”.") + } + address = String(cString: buffer) + } + if address.hasPrefix("127.") || address == "0.0.0.0" { + throw VMBackendError.failed("The VM can't reach this Mac's localhost (“\(host)”). A proxy on this Mac must listen on the Mac's network address instead.") + } + return address + } + + public func imageStatus() async -> ImageStatus { + guard cli.executable != nil else { return .noContainerTool } + guard let inspect = try? await cli.run(["image", "inspect", Self.image], timeout: 15), inspect.status == 0 + else { return .missing } + let built = Self.labels(in: inspect.text)["aculix.knotchy.built"].flatMap { ISO8601DateFormatter().date(from: $0) } + return .built(built) + } + + public enum ImageStatus: Equatable, Sendable { + case noContainerTool + case missing + case built(Date?) + } + + public func runtimeStatus() async -> RuntimeStatus { + guard cli.executable != nil else { return .missing } + let output = try? await cli.run(["--version"], timeout: 10) + return ContainerRuntime.status(versionOutput: output?.text ?? "") + } + + public func stopService() async throws { + let result = try await cli.run(["system", "stop"], timeout: 60) + guard result.status == 0 else { throw VMBackendError.failed("Couldn't stop the container service: \(result.lastLine)") } + } + + private func field(_ name: String, in text: String) -> String? { + for line in text.split(whereSeparator: \.isNewline) { + let parts = line.split(separator: " ", maxSplits: 1, omittingEmptySubsequences: true) + if parts.count == 2, parts[0] == name { + return parts[1].trimmingCharacters(in: .whitespaces) + } + } + return nil + } + + static func labels(in json: String) -> [String: String] { + var found: [String: String] = [:] + func walk(_ value: Any) { + if let dict = value as? [String: Any] { + for (key, child) in dict { + if key.hasPrefix("aculix.knotchy."), let text = child as? String { found[key] = text } + walk(child) + } + } else if let array = value as? [Any] { + array.forEach(walk) + } + } + if let object = try? JSONSerialization.jsonObject(with: Data(json.utf8)) { walk(object) } + return found + } + + static func staleness(built: String?, now: Date = Date()) -> StaleImage? { + let formatter = ISO8601DateFormatter() + guard let built, let date = formatter.date(from: built) else { return StaleImage(builtDaysAgo: nil) } + let days = Int(now.timeIntervalSince(date) / 86_400) + return days > staleAfterDays ? StaleImage(builtDaysAgo: days) : nil + } + + private func prepareRunDirectory() { + let fm = FileManager.default + let path = runDirectory.path + var isDirectory: ObjCBool = false + let attributes = try? fm.attributesOfItem(atPath: path) + let isSymlink = (attributes?[.type] as? FileAttributeType) == .typeSymbolicLink + let owner = (attributes?[.ownerAccountID] as? NSNumber)?.uint32Value + let mode = (attributes?[.posixPermissions] as? NSNumber)?.intValue + if isSymlink || !fm.fileExists(atPath: path, isDirectory: &isDirectory) || !isDirectory.boolValue + || owner != getuid() || mode != 0o700 { + try? fm.removeItem(atPath: path) + try? fm.createDirectory(atPath: path, withIntermediateDirectories: true, + attributes: [.posixPermissions: 0o700]) + } + // Secrets left by a crash; a VM's init deletes its own within a second of booting. + let stale = Date().addingTimeInterval(-120) + for name in (try? fm.contentsOfDirectory(atPath: path)) ?? [] where name.hasSuffix(".secrets") || name.hasSuffix(".env") { + let item = runDirectory.appendingPathComponent(name) + if let modified = (try? fm.attributesOfItem(atPath: item.path))?[.modificationDate] as? Date, modified < stale { + try? fm.removeItem(at: item) + } + } + } + + // MARK: Start and destroy + + public func start(id: String, url: URL, renderSize: PixelSize, blockCIDRs: [String]) async throws -> VMSession { + startsInFlight.withLock { $0 += 1 } + defer { startsInFlight.withLock { $0 -= 1 } } + prepareRunDirectory() + let socket = runDirectory.appendingPathComponent("\(id).sock").path + unlink(socket) + let chosenRoute = route.withLock { $0 } + let launch = try chosenRoute.launch(resolve: Self.resolveIPv4, hostNetworks: blockCIDRs, + allowLocalProxy: localProxyAllowed.withLock { $0 }) + var args = [ + "run", "-d", "--rm", "--name", id, + "--cpus", "4", "--memory", "2g", "--shm-size", "512m", + "--dns", "1.1.1.1", + "--cap-add", "NET_ADMIN", + // WebKit's bwrap can't mount procfs over a masked /proc, and init writes /proc/sys. + "--masked-path", "NONE", "--read-only-path", "NONE", + "--publish-socket", "\(socket):/tmp/knotchy/knotchy.sock", + "-e", "KNOTCHY_URL=\(url.absoluteString)", + "-e", "KNOTCHY_SIZE=\(renderSize)", + "-e", "KNOTCHY_BLOCK_CIDRS=\(blockCIDRs.joined(separator: " "))", + ] + for setting in launch.environment { args += ["-e", setting] } + if !launch.secrets.isEmpty { + // Init reads, deletes and unmounts this (needs SYS_ADMIN), then drops all capabilities. + let share = secretsDirectory(id) + try? FileManager.default.removeItem(at: share) + guard (try? FileManager.default.createDirectory(at: share, withIntermediateDirectories: false, + attributes: [.posixPermissions: 0o700])) != nil, + FileManager.default.createFile(atPath: share.appendingPathComponent("route").path, + contents: Data((launch.secrets.joined(separator: "\n") + "\n").utf8), + attributes: [.posixPermissions: 0o600]) + else { + try? FileManager.default.removeItem(at: share) + throw VMBackendError.failed("Couldn't pass the network settings to the VM.") + } + args += ["--cap-add", "SYS_ADMIN", "--mount", "type=bind,source=\(share.path),target=/run/knotchy-secrets"] + } + args.append(Self.image) + let output: CLIRunner.Output + do { + output = try await cli.run(args, timeout: 60) + } catch { + throw VMBackendError.failed("Starting the VM timed out.") + } + guard output.status == 0 else { + cleanUp(id) + throw VMBackendError.failed("Couldn't start the VM: \(output.lastLine)") + } + startedRoute.withLock { $0 = chosenRoute } + return VMSession(id: id, socketPath: socket) + } + + public func destroy(id: String) async throws { + _ = try? await cli.run(["stop", "-t", "2", id], timeout: 10) + if await waitUntilGone(id, for: 5) { return cleanUp(id) } + _ = try? await cli.run(["delete", "--force", id], timeout: 10) + if await waitUntilGone(id, for: 5) { return cleanUp(id) } + throw VMBackendError.destroyStuck(id: id) + } + + public func forceDestroy(id: String) async throws { + _ = try? await cli.run(["delete", "--force", id], timeout: 10) + if await waitUntilGone(id, for: 5) { return cleanUp(id) } + throw VMBackendError.destroyStuck(id: id) + } + + public func sweepOrphans(keeping: Set) async { + guard let listed = try? await cli.run(["ls", "-a", "-q"], timeout: 10) else { return } + for id in Self.ids(in: listed.text) where Self.isSessionID(id) && !keeping.contains(id) { + _ = try? await cli.run(["delete", "--force", id], timeout: 10) + cleanUp(id) + } + } + + static func isSessionID(_ id: String) -> Bool { + id.count == 16 && id.hasPrefix("knotchy-") && id.dropFirst(8).allSatisfy(\.isHexDigit) + } + + static func ids(in text: String) -> [String] { + text.split(whereSeparator: \.isNewline).map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty } + } + + private func isListed(_ id: String) async -> Bool? { + guard let listed = try? await cli.run(["ls", "-a", "-q"], timeout: 10), listed.status == 0 else { return nil } + return Self.ids(in: listed.text).contains(id) + } + + private func waitUntilGone(_ id: String, for seconds: Double) async -> Bool { + let deadline = Date().addingTimeInterval(seconds) + repeat { + if await isListed(id) == false { return true } + try? await Task.sleep(for: .milliseconds(100)) + } while Date() < deadline + return false + } + + private func cleanUp(_ id: String) { + unlink(runDirectory.appendingPathComponent("\(id).sock").path) + try? FileManager.default.removeItem(at: secretsDirectory(id)) + } + + private func secretsDirectory(_ id: String) -> URL { + runDirectory.appendingPathComponent("\(id).secrets", isDirectory: true) + } + + // MARK: Setup actions + + public func fix(_ issue: SetupIssue, output: @escaping @Sendable (String) -> Void) async throws { + switch issue { + case .serviceStopped, .kernelMissing: + let result = try await cli.run(["system", "start", "--enable-kernel-install"], timeout: 120, line: output) + guard result.status == 0 else { throw VMBackendError.failed("Couldn't start the service: \(result.lastLine)") } + case .imageMissing, .imageOutdated, .imageStale: + guard let vmSources else { throw VMBackendError.failed("The VM sources are missing from the app.") } + let now = Date() + let day = DateFormatter() + day.dateFormat = "yyyy-MM-dd" + let args = [ + "build", "--cpus", "4", "--memory", "4g", "--dns", "1.1.1.1", "--pull", + "--build-arg", "APT_REFRESH=\(day.string(from: now))", + "--build-arg", "BUILD_DATE=\(ISO8601DateFormatter().string(from: now))", + "-f", vmSources.appendingPathComponent("Containerfile").path, + "-t", Self.image, vmSources.path, + ] + let result = try await cli.run(args, timeout: 15 * 60, line: output) + guard result.status == 0 else { throw VMBackendError.failed("The image build failed: \(result.lastLine)") } + case .cliMissing, .cliOutdated, .lowDisk: + throw VMBackendError.failed("There's no automatic fix for this.") + } + } + + // MARK: Quit path + + public func destroyBlocking(ids: [String], deadline: Duration) { + let end = Date().addingTimeInterval(TimeInterval(deadline.components.seconds)) + // Wait for in-flight starts so a VM created at the last moment is destroyed too. + while startsInFlight.withLock({ $0 }) > 0, Date() < end { usleep(50_000) } + for id in ids { + let left = end.timeIntervalSinceNow + guard left > 0.5 else { return } + _ = try? cli.runBlocking(["delete", "--force", id], timeout: min(10, left)) + cleanUp(id) + } + } +} diff --git a/Sources/KnotchyCore/Backend/HostEnvironment.swift b/Sources/KnotchyCore/Backend/HostEnvironment.swift new file mode 100644 index 0000000..f1c9303 --- /dev/null +++ b/Sources/KnotchyCore/Backend/HostEnvironment.swift @@ -0,0 +1,35 @@ +import Darwin +import Foundation + +public enum HostNetworks { + /// Blocked in the VM on top of the private ranges, for LANs and VPNs on public addresses. + public static func current() -> [String] { + var head: UnsafeMutablePointer? + guard getifaddrs(&head) == 0, let first = head else { return [] } + defer { freeifaddrs(head) } + var networks = Set() + for pointer in sequence(first: first, next: { $0.pointee.ifa_next }) { + let interface = pointer.pointee + guard let address = interface.ifa_addr, let mask = interface.ifa_netmask, + address.pointee.sa_family == UInt8(AF_INET), + interface.ifa_flags & UInt32(IFF_LOOPBACK) == 0 else { continue } + let ip = address.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { UInt32(bigEndian: $0.pointee.sin_addr.s_addr) } + let bits = mask.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { UInt32(bigEndian: $0.pointee.sin_addr.s_addr) } + let prefix = bits.nonzeroBitCount + guard prefix >= 8 else { continue } // Never let a bogus /0 block the whole internet. + let network = ip & bits + networks.insert("\(network >> 24).\((network >> 16) & 0xFF).\((network >> 8) & 0xFF).\(network & 0xFF)/\(prefix)") + } + return networks.sorted() + } +} + +public struct SystemDiskSpace: DiskSpaceChecking { + public init() {} + + @concurrent + public func bytesFree(atPath path: String) async -> Int64? { + let values = try? URL(fileURLWithPath: path).resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey]) + return values?.volumeAvailableCapacityForImportantUsage + } +} diff --git a/Sources/KnotchyCore/Backend/NetworkRoute.swift b/Sources/KnotchyCore/Backend/NetworkRoute.swift new file mode 100644 index 0000000..d5d5ff2 --- /dev/null +++ b/Sources/KnotchyCore/Backend/NetworkRoute.swift @@ -0,0 +1,245 @@ +import Foundation + +/// Proxy and VPN routes fail closed: the VM's firewall lets nothing else out. +public enum NetworkRoute: Equatable, Sendable { + case direct + case proxy(ProxySettings) + case wireGuard(WireGuardConfig) + /// Sessions fail to start rather than quietly browsing directly. + case unusable(reason: String) +} + +public struct ProxySettings: Equatable, Sendable { + public enum Kind: String, CaseIterable, Sendable { + case http + case socks5 + } + + public var kind: Kind + public var host: String + public var port: Int + public var username: String + public var password: String + + public init(kind: Kind, host: String, port: Int, username: String = "", password: String = "") { + self.kind = kind + self.host = host + self.port = port + self.username = username + self.password = password + } + + public func url(address: String) -> String { + var userinfo = "" + if !username.isEmpty { + userinfo = Self.encode(username) + (password.isEmpty ? "" : ":" + Self.encode(password)) + "@" + } + return "\(kind.rawValue)://\(userinfo)\(address):\(port)" + } + + /// Must match the VM firewall's list. + static let localRanges = ["0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "169.254.0.0/16", + "172.16.0.0/12", "192.168.0.0/16", "198.18.0.0/15"] + + /// A local proxy is refused: pages could reach the Mac and its network through it. + public static func isLocal(_ address: String, hostNetworks: [String]) -> Bool { + guard let ip = ipv4Value(address) else { return true } + return (localRanges + hostNetworks).contains { cidr in + let parts = cidr.split(separator: "/") + guard parts.count == 2, let network = ipv4Value(String(parts[0])), let bits = Int(parts[1]), (0...32).contains(bits) + else { return false } + let mask: UInt32 = bits == 0 ? 0 : ~UInt32(0) << (32 - bits) + return ip & mask == network & mask + } + } + + static func ipv4Value(_ text: String) -> UInt32? { + guard WireGuardConfig.isIPv4(text) else { return nil } + return text.split(separator: ".").reduce(UInt32(0)) { $0 << 8 | UInt32($1)! } + } + + static func encode(_ text: String) -> String { + let unreserved = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~") + return text.addingPercentEncoding(withAllowedCharacters: unreserved) ?? "" + } +} + +/// IPv4 only (the VM has IPv6 off). All traffic uses the tunnel, whatever AllowedIPs says. +public struct WireGuardConfig: Equatable, Sendable { + public var privateKey: String + public var addresses: [String] + /// Empty means 1.1.1.1 through the tunnel. + public var dns: [String] + public var mtu: Int? + public var peerPublicKey: String + public var presharedKey: String? + public var endpointHost: String + public var endpointPort: Int + public var persistentKeepalive: Int? + public var name: String? + + public enum ParseError: Error, Equatable, Sendable { + case missing(String) + case invalid(String) + case multiplePeers + } + + public static func parse(_ text: String) throws(ParseError) -> WireGuardConfig { + var section = "" + var interface: [String: String] = [:] + var peers: [[String: String]] = [] + for rawLine in text.split(whereSeparator: \.isNewline) { + let line = rawLine.split(separator: "#", maxSplits: 1, omittingEmptySubsequences: false)[0] + .trimmingCharacters(in: .whitespaces) + if line.isEmpty { continue } + if line.hasPrefix("[") { + section = line.lowercased() + if section == "[peer]" { peers.append([:]) } + continue + } + guard let equals = line.firstIndex(of: "=") else { throw .invalid(line) } + let key = line[.. Int in + guard let mtu = Int(text), (576...9000).contains(mtu) else { throw .invalid("MTU") } + return mtu + } + let keepalive = try peer["persistentkeepalive"].flatMap { (text: String) throws(ParseError) -> Int? in + if text.lowercased() == "off" { return nil } + guard let seconds = Int(text), (0...65535).contains(seconds) else { throw .invalid("PersistentKeepalive") } + return seconds == 0 ? nil : seconds + } + + guard let endpoint = peer["endpoint"] else { throw .missing("Endpoint") } + guard !endpoint.hasPrefix("["), let colon = endpoint.lastIndex(of: ":"), + let port = Int(endpoint[endpoint.index(after: colon)...]), (1...65535).contains(port) + else { throw .invalid("Endpoint (an IPv4 address or host name, and a port)") } + let host = String(endpoint[.. String { + var lines = ["[Interface]", "PrivateKey = \(privateKey)", "", "[Peer]", "PublicKey = \(peerPublicKey)"] + if let presharedKey { lines.append("PresharedKey = \(presharedKey)") } + lines.append("Endpoint = \(address):\(endpointPort)") + lines.append("AllowedIPs = 0.0.0.0/0") + lines.append("PersistentKeepalive = \(persistentKeepalive ?? 25)") + return lines.joined(separator: "\n") + "\n" + } + + // MARK: Validation + + /// Curve25519: 32 bytes in base64. + static func isKey(_ text: String) -> Bool { + text.count == 44 && Data(base64Encoded: text)?.count == 32 + } + + static func list(_ text: String?) -> [String] { + (text ?? "").split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty } + } + + static func isIPv4(_ text: String) -> Bool { + let parts = text.split(separator: ".", omittingEmptySubsequences: false) + return parts.count == 4 && parts.allSatisfy { part in + !part.isEmpty && part.count <= 3 && part.allSatisfy(\.isASCII) && part.allSatisfy(\.isNumber) + && Int(part).map { $0 <= 255 } == true + } + } + + static func ipv4CIDR(_ text: String) -> String? { + let parts = text.split(separator: "/", omittingEmptySubsequences: false) + guard parts.count <= 2, isIPv4(String(parts[0])) else { return nil } + guard parts.count == 2 else { return text + "/32" } + guard let prefix = Int(parts[1]), (0...32).contains(prefix), parts[1].allSatisfy(\.isNumber) else { return nil } + return text + } +} + +extension NetworkRoute { + public var label: String? { + switch self { + case .direct, .unusable: nil + case .proxy(let proxy): proxy.kind == .socks5 ? "SOCKS5 proxy" : "HTTP proxy" + case .wireGuard(let config): config.name ?? "VPN" + } + } +} + +extension WireGuardConfig.ParseError { + /// Finishes "The WireGuard config …". + public var message: String { + switch self { + case .missing(let what): "has no \(what)" + case .invalid(let what): "has an invalid \(what)" + case .multiplePeers: "has more than one peer; Knotchy needs one server per config" + } + } +} + +public struct RouteLaunch: Equatable, Sendable { + /// Always includes KNOTCHY_ROUTE, so a VM missing its proxy or VPN settings stops. + public var environment: [String] + /// Never passed with `-e`: `container` saves the environment to disk and shows it in `inspect`. + public var secrets: [String] +} + +extension NetworkRoute { + public func launch(resolve: (String) throws -> String, hostNetworks: [String] = [], + allowLocalProxy: Bool = false) throws -> RouteLaunch { + switch self { + case .direct: + return RouteLaunch(environment: ["KNOTCHY_ROUTE=direct"], secrets: []) + case .unusable(let reason): + throw VMBackendError.failed(reason) + case .proxy(let proxy): + let address = try resolve(proxy.host) + if !allowLocalProxy, ProxySettings.isLocal(address, hostNetworks: hostNetworks) { + let which = proxy.host == address ? address : "\(proxy.host) is \(address)" + throw VMBackendError.failed( + "Knotchy won't use a proxy on this Mac or your local network (\(which)): pages could reach your network through it.") + } + return RouteLaunch(environment: ["KNOTCHY_ROUTE=proxy", "KNOTCHY_PROXY_ADDR=\(address):\(proxy.port)"], + secrets: ["KNOTCHY_PROXY_URL=\(proxy.url(address: address))"]) + case .wireGuard(let config): + let address = try resolve(config.endpointHost) + return RouteLaunch( + environment: [ + "KNOTCHY_ROUTE=wireguard", + "KNOTCHY_WG_ENDPOINT=\(address):\(config.endpointPort)", + "KNOTCHY_WG_ADDRESS=\(config.addresses.joined(separator: " "))", + "KNOTCHY_WG_DNS=\(config.dns.joined(separator: " "))", + "KNOTCHY_WG_MTU=\(config.mtu ?? 1420)", + ], + secrets: ["KNOTCHY_WG_SETCONF=\(Data(config.setconf(endpointAddress: address).utf8).base64EncodedString())"] + ) + } + } +} diff --git a/Sources/KnotchyCore/Backend/VMBackend.swift b/Sources/KnotchyCore/Backend/VMBackend.swift new file mode 100644 index 0000000..de71a86 --- /dev/null +++ b/Sources/KnotchyCore/Backend/VMBackend.swift @@ -0,0 +1,65 @@ +import Foundation + +/// The controller serializes all calls except `destroyBlocking`, which the quit path may make +/// from any thread while an async call is still running. +public protocol VMBackend: Sendable { + func preflight() async -> PreflightResult + /// Must register as in flight before its first suspension so `destroyBlocking` waits for it. + func start(id: String, url: URL, renderSize: PixelSize, blockCIDRs: [String]) async throws -> VMSession + /// Returns only once the VM is confirmed gone; otherwise throws `.destroyStuck`. + func destroy(id: String) async throws + func forceDestroy(id: String) async throws + func sweepOrphans(keeping: Set) async + func fix(_ issue: SetupIssue, output: @escaping @Sendable (String) -> Void) async throws + /// Not serialized by the controller: `container --version` doesn't use the service. + func runtimeStatus() async -> RuntimeStatus + func stopService() async throws + /// Blocking. Waits for in-flight starts first so a VM created at the last moment is destroyed too. + func destroyBlocking(ids: [String], deadline: Duration) +} + +public struct VMSession: Equatable, Sendable { + public let id: String + public let socketPath: String + + public init(id: String, socketPath: String) { + self.id = id + self.socketPath = socketPath + } +} + +public enum SetupIssue: Equatable, Sendable { + case cliMissing + case cliOutdated(found: String) + case serviceStopped + case kernelMissing + case imageMissing + case imageOutdated(found: Int, need: Int) + case imageStale(builtDaysAgo: Int?) + case lowDisk(bytesFree: Int64) +} + +public enum PreflightResult: Equatable, Sendable { + case ready(appRoot: String, staleImage: StaleImage?) + /// Never `.imageStale` (that rides on `.ready`) or `.lowDisk` (the controller checks disk). + case issue(SetupIssue) +} + +public struct StaleImage: Equatable, Sendable { + public var builtDaysAgo: Int? + + public init(builtDaysAgo: Int?) { + self.builtDaysAgo = builtDaysAgo + } +} + +public enum VMBackendError: Error, Equatable, Sendable { + case destroyStuck(id: String) + /// The message is shown to the user. + case failed(String) +} + +public protocol DiskSpaceChecking: Sendable { + /// Async: the query can block for tens of milliseconds and stall the panel's opening animation. + func bytesFree(atPath path: String) async -> Int64? +} diff --git a/Sources/KnotchyCore/Geometry/NotchGeometry.swift b/Sources/KnotchyCore/Geometry/NotchGeometry.swift new file mode 100644 index 0000000..421de3c --- /dev/null +++ b/Sources/KnotchyCore/Geometry/NotchGeometry.swift @@ -0,0 +1,117 @@ +import CoreGraphics + +/// Rects are in global screen coordinates (AppKit's: origin bottom left, y up). +public struct ScreenInfo: Equatable, Sendable { + public var frame: CGRect + public var visibleFrame: CGRect + public var safeAreaTop: CGFloat + public var auxLeft: CGRect + public var auxRight: CGRect + public var menuBarThickness: CGFloat + + public init(frame: CGRect, visibleFrame: CGRect, safeAreaTop: CGFloat, + auxLeft: CGRect, auxRight: CGRect, menuBarThickness: CGFloat) { + self.frame = frame + self.visibleFrame = visibleFrame + self.safeAreaTop = safeAreaTop + self.auxLeft = auxLeft + self.auxRight = auxRight + self.menuBarThickness = menuBarThickness + } + + public var hasNotch: Bool { + safeAreaTop > 0 && !auxLeft.isEmpty && !auxRight.isEmpty + } +} + +public enum PanelMode: Sendable { + case peek + case expanded +} + +/// Global screen coordinates, y up; convert them for SwiftUI views and tracking areas. +public struct PanelLayout: Equatable, Sendable { + public var notch: CGRect + public var pill: CGRect + /// Full size for its mode so the shape never clips; a Peek↔Expanded animation needs the larger. + public var panel: CGRect + public var toolbar: CGRect + public var content: CGRect + public var contentScale: CGFloat +} + +public enum NotchGeometry { + public static let toolbarHeight: CGFloat = 34 + public static let margin: CGFloat = 6 + public static let peekContent = CGSize(width: 640, height: 400) + public static let expandedContent = CGSize(width: 1024, height: 640) + public static let renderWidth: CGFloat = 1024 + public static let virtualNotchWidth: CGFloat = 200 + public static let minimumNotchHeight: CGFloat = 24 + public static let notchWidthRange: ClosedRange = 120...300 + public static let pillEarWidth: CGFloat = 72 + public static let screenInset: CGFloat = 8 + public static let minimumContentHeight: CGFloat = 50 + + public static func layout(for screen: ScreenInfo, mode: PanelMode) -> PanelLayout { + let notch = notchRect(for: screen) + let height = notch.height + + let base = mode == .peek ? peekContent : expandedContent + // Ignores a side Dock: the panel may cover its top. + let maxWidth = screen.frame.width - 2 * screenInset - 2 * margin + let maxHeight = (screen.frame.maxY - screen.visibleFrame.minY) - height - toolbarHeight - margin - screenInset + // Keep exactly 16:10 in whole points: the height is a multiple of 5, the width 8/5 of it. + let fittingHeight = min(base.height, maxHeight, maxWidth * 5 / 8) + let contentHeight = max(minimumContentHeight, (fittingHeight / 5).rounded(.down) * 5) + let contentWidth = contentHeight * 8 / 5 + + let panelWidth = contentWidth + 2 * margin + let panelHeight = height + toolbarHeight + contentHeight + margin + let centeredX = (notch.midX - panelWidth / 2).rounded() + let panelX = min(max(centeredX, screen.frame.minX), screen.frame.maxX - panelWidth) + let panel = CGRect(x: panelX, y: screen.frame.maxY - panelHeight, width: panelWidth, height: panelHeight) + + return PanelLayout( + notch: notch, + pill: notch.insetBy(dx: -pillEarWidth, dy: 0), + panel: panel, + toolbar: CGRect(x: panel.minX + margin, y: panel.maxY - height - toolbarHeight, + width: contentWidth, height: toolbarHeight), + content: CGRect(x: panel.minX + margin, y: panel.minY + margin, + width: contentWidth, height: contentHeight), + contentScale: contentWidth / renderWidth + ) + } + + /// `point` is in a flipped view. Nil on degenerate input: drop the event rather than click at (0, 0). + public static func videoPoint(_ point: CGPoint, surfaceSize: CGSize, renderSize: PixelSize) -> (x: UInt16, y: UInt16)? { + let pixels = 1...65536 + guard point.x.isFinite, point.y.isFinite, surfaceSize.width.isFinite, surfaceSize.height.isFinite, + surfaceSize.width > 0, surfaceSize.height > 0, + pixels.contains(renderSize.width), pixels.contains(renderSize.height) + else { return nil } + let video = CGSize(width: renderSize.width, height: renderSize.height) + let scale = min(surfaceSize.width / video.width, surfaceSize.height / video.height) + guard scale > 0 else { return nil } + let originX = (surfaceSize.width - video.width * scale) / 2 + let originY = (surfaceSize.height - video.height * scale) / 2 + let x = min(max((point.x - originX) / scale, 0), video.width - 1) + let y = min(max((point.y - originY) / scale, 0), video.height - 1) + return (UInt16(x), UInt16(y)) + } + + private static func notchRect(for screen: ScreenInfo) -> CGRect { + if screen.hasNotch { + let left = screen.auxLeft.maxX + let right = screen.auxRight.minX + let width = min(max(right - left, notchWidthRange.lowerBound), notchWidthRange.upperBound) + return CGRect(x: (left + right) / 2 - width / 2, y: screen.frame.maxY - screen.safeAreaTop, + width: width, height: screen.safeAreaTop) + } + // The minimum keeps a virtual notch visible when the menu bar auto-hides. + let height = max(screen.frame.maxY - screen.visibleFrame.maxY, screen.menuBarThickness, minimumNotchHeight) + return CGRect(x: (screen.frame.midX - virtualNotchWidth / 2).rounded(), y: screen.frame.maxY - height, + width: virtualNotchWidth, height: height) + } +} diff --git a/Sources/KnotchyCore/Links/LinkIntake.swift b/Sources/KnotchyCore/Links/LinkIntake.swift new file mode 100644 index 0000000..ff9c450 --- /dev/null +++ b/Sources/KnotchyCore/Links/LinkIntake.swift @@ -0,0 +1,131 @@ +import Foundation + +/// Input is untrusted and scanned inside the synchronous Services call, so work is linear and capped. +public enum LinkIntake { + /// UTF-8 bytes. Links reach the VM in an env var and in NAVIGATE messages (64 KiB limit). + public static let maxURLBytes = 16 * 1024 + public static let maxScannedCharacters = 1024 * 1024 + + /// The first http(s) URL in `text`, without surrounding prose punctuation, quotes or Markdown. + public static func extractURL(from text: String) -> URL? { + let startLimit = text.index(text.startIndex, offsetBy: maxScannedCharacters, limitedBy: text.endIndex) + ?? text.endIndex + // One past the limit, so an over-long link is rejected rather than truncated. + let readLimit = text.index(startLimit, offsetBy: maxURLBytes + 1, limitedBy: text.endIndex) ?? text.endIndex + var from = text.startIndex + while from < startLimit, + let match = text.range(of: "http", options: .caseInsensitive, range: from.. text.startIndex ? text[text.index(before: match.lowerBound)] : nil + var parts = [(token, afterParenthesis: before == "(")] + if let markdown = token.range(of: "](") { + parts = [(token[.. URL? { + let text = String(html.prefix(maxScannedCharacters)) + let range = NSRange(text.startIndex..., in: text) + for match in hrefPattern.matches(in: text, range: range) { + for group in 1...3 { + guard let found = Range(match.range(at: group), in: text) else { continue } + let target = decodingEntities(text[found]).trimmingCharacters(in: .whitespacesAndNewlines) + if target.utf8.count <= maxURLBytes, let url = URL(string: target), let valid = validate(url) { + return valid + } + } + } + return extractURL(from: text) + } + + private static let hrefPattern = try! NSRegularExpression( + pattern: #"href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))"#, options: [.caseInsensitive]) + + private static func decodingEntities(_ value: Substring) -> String { + var result = String(value) + for (entity, character) in [(""", "\""), (""", "\""), ("'", "'"), ("'", "'"), + ("<", "<"), (">", ">"), ("&", "&"), ("&", "&")] { + result = result.replacingOccurrences(of: entity, with: character, options: .caseInsensitive) + } + return result + } + + public static func validate(_ url: URL) -> URL? { + guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https", + let host = url.host(percentEncoded: false), !host.isEmpty, + url.absoluteString.utf8.count <= maxURLBytes + else { return nil } + return url + } + + /// Missed by the category check. IDNA would turn these full stops into dots in the host. + private static let cjkPunctuation: Set = ["。", ".", "。", "、", ",", "…", "!", "?", ":", ";"] + + private static func endsURL(_ c: Character) -> Bool { + if c.isWhitespace || c == "<" || c == ">" || c == "\"" || c == "`" { return true } + guard let scalar = c.unicodeScalars.first, !scalar.isASCII else { return false } + switch scalar.properties.generalCategory { + case .initialPunctuation, .finalPunctuation, .openPunctuation, .closePunctuation: + return true + default: + return cjkPunctuation.contains(c) + } + } + + private static func upToUnbalancedClosingParenthesis(_ part: Substring) -> Substring { + var depth = 0 + for i in part.indices { + switch part[i] { + case "(": + depth += 1 + case ")": + if depth == 0 { return part[.. Substring { + // Closing minus opening brackets, keyed by the closing one. + var unbalanced: [Character: Int] = [:] + for c in token { + if openerOf[c] != nil { + unbalanced[c, default: 0] += 1 + } else if let closer = closerOf[c] { + unbalanced[closer, default: 0] -= 1 + } + } + var s = token + while let last = s.last { + if punctuation.contains(last) { + s.removeLast() + } else if openerOf[last] != nil, unbalanced[last, default: 0] > 0 { + unbalanced[last, default: 0] -= 1 + s.removeLast() + } else { + break + } + } + return s + } +} diff --git a/Sources/KnotchyCore/Session/BackendChain.swift b/Sources/KnotchyCore/Session/BackendChain.swift new file mode 100644 index 0000000..8df6b2c --- /dev/null +++ b/Sources/KnotchyCore/Session/BackendChain.swift @@ -0,0 +1,49 @@ +import Synchronization + +/// Runs async jobs one at a time, in order, so backend operations never overlap. +@MainActor +final class BackendChain { + private var tail: Task? + private(set) var isClosed = false + + func enqueue(_ job: @escaping @MainActor () async -> Void) { + guard !isClosed else { return } + let previous = tail + tail = Task { + await previous?.value + guard !self.isClosed else { return } + await job() + } + } + + func close() { + isClosed = true + } + + func waitUntilIdle() async { + while let current = tail { + await current.value + if tail == current { return } + } + } +} + +/// Every VM ID from its `start` being queued until its destroy is confirmed. +/// Lock-protected: the quit path reads it off the main actor. +public final class LiveIDs: Sendable { + private let ids = Mutex>([]) + + public init() {} + + public var snapshot: Set { + ids.withLock { $0 } + } + + func insert(_ id: String) { + _ = ids.withLock { $0.insert(id) } + } + + func remove(_ id: String) { + _ = ids.withLock { $0.remove(id) } + } +} diff --git a/Sources/KnotchyCore/Session/SessionController.swift b/Sources/KnotchyCore/Session/SessionController.swift new file mode 100644 index 0000000..edbb982 --- /dev/null +++ b/Sources/KnotchyCore/Session/SessionController.swift @@ -0,0 +1,956 @@ +import Foundation +import Observation + +/// The single-session state machine behind the notch UI. Inputs never block; backend calls run serially. +@MainActor +@Observable +public final class SessionController { + // MARK: - Timings and limits + + public static let renderSize = PixelSize(width: 1024, height: 640) + public static let foldGrace: Duration = .milliseconds(400) + public static let neverEnteredFoldDelay: Duration = .seconds(30) + public static let defaultIdleDestroyDelay: Duration = .seconds(5 * 60) + public static let idleQuitDelay: Duration = .seconds(60) + public static let destroyedDisplay: Duration = .milliseconds(1500) + public static let endedDisplay: Duration = .seconds(3) + public static let destroyStuckDisplay: Duration = .seconds(30) + public static let noticeDisplay: Duration = .seconds(2) + public static let progressLinger: Duration = .seconds(2) + public static let minimumFreeBytes: Int64 = 2_000_000_000 + public static let setupOutputLimit = 200 + public static let stillBrokenMessage = "Setup finished, but the runtime still reports a problem." + + // MARK: - Outputs + + public private(set) var state: SessionState = .idle + + // MARK: - Preferences + + /// Takes effect the next time a session folds. + public var idleDestroyDelay: Duration = SessionController.defaultIdleDestroyDelay + public var opensExpanded = false + public var quitsWhenIdle = true + /// Opens the setup window; the app sets it. + @ObservationIgnored public var showSetup: @MainActor () -> Void = {} + + public private(set) var setupFolded = false + @ObservationIgnored private var setupOpenedByHover = false + + public private(set) var notice: Notice? + public private(set) var progress: Int? + public private(set) var openedURL: URL? + /// Reported by the VM, so untrusted: shown, never opened or retried. + public private(set) var pageURL: URL? + public private(set) var setupLog: String? + /// The latest setup action's last lines, for the setup window's details. + public private(set) var setupOutput: [String] = [] + public private(set) var connection: (any StreamConnection)? + public private(set) var staleImage: StaleImage? + public private(set) var idleDeadline: ContinuousClock.Instant? + + public var countdown: Duration? { + idleDeadline.map { max(.zero, $0 - clock.now) } + } + + public nonisolated let liveIDs = LiveIDs() + + // MARK: - Dependencies + + private let backend: any VMBackend + private let connector: any StreamConnector + private let diskSpace: any DiskSpaceChecking + private let clock: any KnotchyClock + private let hostNetworks: () -> [String] + private let requestQuit: @MainActor () -> Void + private let makeSessionID: () -> String + + // MARK: - Internal state + + @MainActor + private final class Session { + let id: String + let url: URL + /// `backend.start` has been called, so a VM may exist. + var startBegan = false + var retired = false + var contentReady = false + var connection: (any StreamConnection)? + var tasks: [Task] = [] + + init(id: String, url: URL) { + self.id = id + self.url = url + } + } + + private enum TimerKey: Hashable, Sendable { + case foldGrace, neverEnteredFold, idleDestroy, transient, notice, progressHide, idleQuit + } + + private enum RunMode: Equatable { + /// From the notch or the menu: stops at the next card, as before. + case link + /// From the setup window. + case window(continuing: Bool) + /// The setup window's check: starts the service, never builds. + case check + } + + private struct SetupRun { + var mode: RunMode + /// The loop guard: each issue is fixed at most once per run. + var fixed: [SetupIssue] + var waiter: CheckedContinuation? + } + + private let chain = BackendChain() + private let timers: DeadlineTimers + // Invariants: at most one session isn't retired, and it is `current`; only `transition(to:)` + // writes `state` (and owns its timers); while `settingUp`, only the setup outcome changes it. + @ObservationIgnored private var current: Session? + @ObservationIgnored private var pendingURL: URL? + /// IDs whose destroy failed; they stay in `liveIDs`. + @ObservationIgnored private var pendingCleanup: [String] = [] + @ObservationIgnored private var requestedImageUpdate: StaleImage? + @ObservationIgnored private var lastProgressAt: ContinuousClock.Instant? + @ObservationIgnored private var entered = false + @ObservationIgnored private var pointerInside = false + @ObservationIgnored private var focused = false + @ObservationIgnored private var run: SetupRun? + @ObservationIgnored private var settleWaiters: [CheckedContinuation] = [] + /// The issues started automatically for the link now waiting; each is tried once per request. + @ObservationIgnored private var autoFixed: (url: URL, issues: [SetupIssue])? + + public init( + backend: any VMBackend, + connector: any StreamConnector, + diskSpace: any DiskSpaceChecking, + clock: any KnotchyClock = SystemClock(), + hostNetworks: @escaping () -> [String], + requestQuit: @escaping @MainActor () -> Void, + makeSessionID: @escaping () -> String = SessionController.randomSessionID + ) { + self.backend = backend + self.connector = connector + self.diskSpace = diskSpace + self.clock = clock + self.hostNetworks = hostNetworks + self.requestQuit = requestQuit + self.makeSessionID = makeSessionID + timers = DeadlineTimers(clock: clock) + timers.onFire = { [weak self] key in self?.timerFired(key) } + enqueue { [backend] in await backend.sweepOrphans(keeping: []) } + timers.set(.idleQuit, after: Self.idleQuitDelay) + } + + /// `knotchy-<8 hex>`: the only names the launch sweep deletes. + public nonisolated static func randomSessionID() -> String { + "knotchy-" + String(format: "%08x", UInt32.random(in: .min ... .max)) + } + + // MARK: - Links + + /// A new link always wins. Anything but http/https shows "No web link found". + public func open(_ url: URL) { + guard let url = LinkIntake.validate(url) else { return show(.noWebLink) } + guard !chain.isClosed else { return } + autoFixed = nil + if case .settingUp = state { + pendingURL = url + setupFolded = false + } else { + startSession(url) + } + } + + public func open(text: String) { + guard let url = LinkIntake.extractURL(from: text) else { return show(.noWebLink) } + open(url) + } + + // MARK: - Toolbar + + public func navigate(_ url: URL) { + guard let url = LinkIntake.validate(url) else { return show(.noWebLink) } + guard case .live = state, let connection else { return } + openedURL = url + pageURL = nil + connection.send(.navigate(url.absoluteString)) + } + + public func goBack() { runScript("history.back()") } + public func goForward() { runScript("history.forward()") } + public func reload() { runScript("location.reload()") } + + private func runScript(_ javaScript: String) { + guard case .live = state else { return } + connection?.send(.script(javaScript)) + } + + // MARK: - Panel + + public func expand() { + guard state == .live(.peek) else { return } + entered = true + focused = true + transition(to: .live(.expanded)) + } + + public func shrink() { + guard state == .live(.expanded) else { return } + transition(to: .live(.peek)) + } + + public func pointerEntered() { + pointerInside = true + timers.cancel(.foldGrace) + switch state { + case .settingUp: + if setupFolded { setupOpenedByHover = true } + setupFolded = false + case .starting(let step, folded: true): + unfold(to: .starting(step, folded: false)) + case .live(.collapsed): + unfold(to: .live(.peek)) + case .starting, .live: + entered = true + timers.cancel(.neverEnteredFold) + default: + break + } + } + + public func pointerExited() { + pointerInside = false + if isPeekLike, entered, !focused { + timers.set(.foldGrace, after: Self.foldGrace) + } + if case .settingUp = state, setupOpenedByHover { + timers.set(.foldGrace, after: Self.foldGrace) + } + } + + public func panelClicked() { + guard isOpenPanel else { return } + entered = true + focused = true + timers.cancel(.foldGrace) + timers.cancel(.neverEnteredFold) + } + + public func clickedOutside() { + if isOpenPanel { fold() } + if case .settingUp = state { setupFolded = true } + } + + public func foldSetup() { + if case .settingUp = state { setupFolded = true } + setupOpenedByHover = false + } + + private var isPeekLike: Bool { + switch state { + case .starting(_, folded: false), .live(.peek): true + default: false + } + } + + private var isOpenPanel: Bool { + isPeekLike || state == .live(.expanded) + } + + private var isFolded: Bool { + if case .starting(_, folded: true) = state { true } else { false } + } + + private func fold() { + focused = false + pointerInside = false + switch state { + case .starting(let step, folded: false): + transition(to: .starting(step, folded: true)) + case .live(.peek), .live(.expanded): + transition(to: .live(.collapsed)) + default: + break + } + } + + private func unfold(to newState: SessionState) { + entered = true + focused = false + transition(to: newState) + } + + // MARK: - Destroy, retry, dismiss + + public func destroy() { + guard let session = current else { return } + switch state { + case .starting, .live: + transition(to: .destroying) + retire(session) { [weak self] gone in self?.destroyFinished(session, gone: gone) } + default: + break + } + } + + public func retry() { + switch state { + case .failed(.start): + autoFixed = nil // A retry is a new request: it gets its own automatic start. + if let url = current?.url { startSession(url, pointerStays: true) } + case .failed(.destroyStuck): + guard let session = current else { return } + pendingCleanup.removeAll { $0 == session.id } + transition(to: .destroying) + enqueue { [weak self] in + await self?.runDestroy(session) { gone in self?.destroyFinished(session, gone: gone) } + } + default: + break + } + } + + public func dismiss() { + switch state { + case .failed(.start): + enterIdle() + case .setupRequired: + pendingURL = nil + enterIdle() + default: + break + } + } + + // MARK: - Setup + + public func performSetupAction() { + guard case .setupRequired(let issue, _) = state else { return } + switch issue { + case .cliMissing, .cliOutdated: + showSetup() + case .lowDisk: + break + default: + startRun(issue, mode: .link, folded: false) + } + } + + /// Rebuilds the image once this session ends. + public func requestImageUpdate() { + guard let stale = staleImage else { return } + staleImage = nil + requestedImageUpdate = stale + } + + public func rebuildImage() { + switch state { + case .idle: + startRun(.imageStale(builtDaysAgo: nil), mode: .link, folded: false) + case .settingUp: + break + default: + staleImage = nil + requestedImageUpdate = StaleImage(builtDaysAgo: nil) + } + } + + public func showPanel() { + switch state { + case .settingUp: + setupFolded = false + setupOpenedByHover = false + case .starting(let step, folded: true): unfold(to: .starting(step, folded: false)) + case .live(.collapsed): unfold(to: .live(.peek)) + default: break + } + } + + public func dismissStaleImageWarning() { + staleImage = nil + } + + /// Quit path: queued backend jobs never start after this. One already running may still call + /// the backend, which is safe because `destroyBlocking` then blocks the main thread until exit. + public func closeBackendChain() { + chain.close() + } + + // MARK: - Setup window + + /// Runs `issue`'s fix folded into the notch; when `continuing`, also each fixable issue preflight + /// finds next, each at most once. A waiting link opens only once everything is ready. nil when busy. + public func runSetup(_ issue: SetupIssue, continuing: Bool) async -> SetupRunOutcome? { + guard acceptsSetupWindow else { return nil } + return await withCheckedContinuation { continuation in + startRun(issue, mode: .window(continuing: continuing), folded: true, waiter: continuation) + } + } + + /// Preflight for the setup window: starts a stopped service or missing kernel, never builds. nil when busy. + public func checkSetup() async -> SetupRunOutcome? { + guard acceptsSetupWindow else { return nil } + let preflight = await onChain { [backend] in await backend.preflight() } + guard acceptsSetupWindow else { return nil } + switch preflight { + case .ready: + // Fixed outside Knotchy while a card held a link: open it. + if case .setupRequired = state, let url = pendingURL { startSession(url, pointerStays: true) } + return .ready + case .issue(let issue) where Self.fixes(issue, in: .check): + return await withCheckedContinuation { continuation in + startRun(issue, mode: .check, folded: true, waiter: continuation) + } + case .issue(let issue): + return .needs(issue) + } + } + + /// Around a runtime install: links wait, and an update stops the service first. + public func beginRuntimeInstall(replacing found: String?) async -> Result { + guard acceptsSetupWindow else { + if case .settingUp = state { return .failure(.setupRunning) } + return .failure(.sessionOpen) + } + setupLog = nil + setupOutput = [] + transition(to: .settingUp(found.map { .cliOutdated(found: $0) } ?? .cliMissing)) + setupFolded = true + guard found != nil else { return .success(()) } + let stopped: String? = await onChain { [backend] in + do { + try await backend.stopService() + return nil + } catch { + return Self.message(for: error) + } + } + guard let stopped else { return .success(()) } + _ = await finishRuntimeInstall(.failed(stopped)) + return .failure(.stopFailed(stopped)) + } + + /// After an install, starts the service and builds, like `runSetup(continuing: true)`. + public func finishRuntimeInstall(_ result: RuntimeInstallResult) async -> SetupRunOutcome { + guard case .settingUp(let issue) = state, Self.isRuntimeInstall(issue), run == nil else { + return .failed(.cliMissing, "No runtime install was in progress.") + } + switch result { + case .installed: + return await withCheckedContinuation { continuation in + startRun(.serviceStopped, mode: .window(continuing: true), folded: false, waiter: continuation) + } + case .cancelled: + endHold(issue, error: nil) + return .needs(issue) + case .failed(let message): + endHold(issue, error: message) + return .failed(issue, message) + } + } + + private func endHold(_ issue: SetupIssue, error: String?) { + if pendingURL != nil { transition(to: .setupRequired(issue, error: error)) } else { enterIdle() } + } + + private static func isRuntimeInstall(_ issue: SetupIssue) -> Bool { + switch issue { + case .cliMissing, .cliOutdated: true + default: false + } + } + + public func waitWhileSettingUp() async { + guard case .settingUp = state else { return } + await withCheckedContinuation { settleWaiters.append($0) } + } + + public func runtimeStatus() async -> RuntimeStatus { + await backend.runtimeStatus() + } + + /// The states where no VM exists. A stuck destroy may still have one. + private var acceptsSetupWindow: Bool { + switch state { + case .idle, .setupRequired, .failed(.start), .failed(.ended), .destroyed: true + default: false + } + } + + /// After `closeBackendChain()` the job never runs and this never returns; that only happens while quitting. + private func onChain(_ job: @escaping @MainActor () async -> T) async -> T { + await withCheckedContinuation { continuation in + enqueue { continuation.resume(returning: await job()) } + } + } + + // MARK: - Session lifecycle + + private func startSession(_ url: URL, pointerStays: Bool = false) { + let pointerOnPanel = pointerStays && pointerInside + let startsFolded: Bool + if case .settingUp = state { startsFolded = setupFolded } else { startsFolded = false } + if let old = current { retire(old) } + let session = Session(id: makeSessionID(), url: url) + current = session + pendingURL = nil + setupLog = nil + openedURL = url + pageURL = nil + connection = nil + staleImage = nil + progress = nil + lastProgressAt = nil + entered = pointerOnPanel + pointerInside = pointerOnPanel + focused = false + for key in [TimerKey.foldGrace, .neverEnteredFold, .progressHide] { timers.cancel(key) } + liveIDs.insert(session.id) + transition(to: .starting(.booting, folded: startsFolded)) + enqueue { [weak self] in await self?.runStart(session) } + } + + private func retire(_ session: Session, onGone: (@MainActor (Bool) -> Void)? = nil) { + guard !session.retired else { return } + session.retired = true + for task in session.tasks { task.cancel() } + session.connection?.close() + session.connection = nil + if current === session { + connection = nil + progress = nil + timers.cancel(.progressHide) + } + enqueue { [weak self] in await self?.runDestroy(session, onGone: onGone) } + } + + private func runStart(_ session: Session) async { + guard !session.retired else { return } + let preflight = await backend.preflight() + guard !session.retired else { return } + switch preflight { + case .issue(let issue): + return needsSetup(issue, for: session) + case .ready(let appRoot, let stale): + let free = await diskSpace.bytesFree(atPath: appRoot) + guard !session.retired else { return } + if let free, free < Self.minimumFreeBytes { + return needsSetup(.lowDisk(bytesFree: free), for: session) + } + if requestedImageUpdate == nil { staleImage = stale } + } + + session.startBegan = true + do { + let vm = try await backend.start(id: session.id, url: session.url, + renderSize: Self.renderSize, blockCIDRs: hostNetworks()) + guard !session.retired else { return } // Superseded mid-start: its destroy job runs next. + transition(to: .starting(.connecting, folded: isFolded)) + session.tasks.append(Task { [weak self] in await self?.connect(session, socketPath: vm.socketPath) }) + } catch { + guard !session.retired else { return } + retire(session) + transition(to: .failed(.start(Self.message(for: error)))) + } + } + + /// No VM exists yet, so there is nothing to destroy. Starting the service needs no password, so it just happens. + private func needsSetup(_ issue: SetupIssue, for session: Session) { + session.retired = true + liveIDs.remove(session.id) + pendingURL = session.url + guard issue == .serviceStopped || issue == .kernelMissing else { + return transition(to: .setupRequired(issue, error: nil)) + } + var tried = autoFixed?.url == session.url ? autoFixed?.issues ?? [] : [] + guard !tried.contains(where: { Self.sameFix($0, issue) }) else { + return transition(to: .setupRequired(issue, error: Self.stillBrokenMessage)) + } + tried.append(issue) + autoFixed = (session.url, tried) + startRun(issue, mode: .link, folded: false) + } + + private func connect(_ session: Session, socketPath: String) async { + let outcome: Result + do { + outcome = .success(try await connector.connect(socketPath: socketPath)) + } catch { + outcome = .failure(error) + } + guard !session.retired else { + if case .success(let late) = outcome { late.close() } + return + } + switch outcome { + case .success(let stream): + session.connection = stream + connection = stream + transition(to: .starting(.loading, folded: isFolded)) + session.tasks.append(Task { [weak self] in + for await event in stream.events { self?.handle(event, from: session) } + // A stream that finishes without `.ended` has still ended. + self?.handle(.ended(.closed), from: session) + }) + case .failure(StreamError.versionMismatch(let found, let need)): + retire(session) + pendingURL = session.url + transition(to: .setupRequired(.imageOutdated(found: found, need: need), error: nil)) + case .failure(let error): + retire(session) + transition(to: .failed(.start(Self.message(for: error)))) + } + } + + private func handle(_ event: StreamEvent, from session: Session) { + guard current === session, !session.retired else { return } + switch event { + case .hello: + break + case .progress(let percent): + progressChanged(percent) + case .pageURL(let url): + pageURL = url + case .contentReady: + contentReady(session) + case .ended(let reason): + let wasStarting = if case .starting = state { true } else { false } + retire(session) + transition(to: .failed(wasStarting ? .start(Self.message(for: reason)) : .ended(reason))) + } + } + + private func contentReady(_ session: Session) { + guard case .starting(_, let folded) = state, !session.contentReady else { return } + session.contentReady = true + if folded { + transition(to: .live(.collapsed)) + } else if opensExpanded { + transition(to: .live(.expanded)) + } else { + transition(to: .live(.peek)) + if !entered { timers.set(.neverEnteredFold, after: Self.neverEnteredFoldDelay) } + } + scheduleProgressHide() + } + + private func runDestroy(_ session: Session, onGone: (@MainActor (Bool) -> Void)?) async { + guard session.startBegan else { + liveIDs.remove(session.id) + onGone?(true) + return + } + do { + try await backend.destroy(id: session.id) + pendingCleanup.removeAll { $0 == session.id } + liveIDs.remove(session.id) + onGone?(true) + } catch { + if !pendingCleanup.contains(session.id) { pendingCleanup.append(session.id) } + onGone?(false) + } + } + + private func destroyFinished(_ session: Session, gone: Bool) { + guard current === session, state == .destroying else { return } + transition(to: gone ? .destroyed : .failed(.destroyStuck)) + } + + private func enterIdle() { + openedURL = nil + pageURL = nil + progress = nil + staleImage = nil + setupLog = nil + connection = nil + entered = false + pointerInside = false + focused = false + if let update = requestedImageUpdate { + requestedImageUpdate = nil + return startRun(.imageStale(builtDaysAgo: update.builtDaysAgo), mode: .link, folded: false) + } + transition(to: .idle) + } + + // MARK: - Progress bar + + private func progressChanged(_ percent: Int) { + lastProgressAt = clock.now + if percent >= 100 { + progress = nil + timers.cancel(.progressHide) + } else { + progress = percent + scheduleProgressHide() + } + } + + /// Hides the bar 2 s after the last PROGRESS, so a load that never reports 100 doesn't leave it up. + private func scheduleProgressHide() { + guard current?.contentReady == true, progress != nil, let last = lastProgressAt else { return } + let hideAt = last + Self.progressLinger + if hideAt <= clock.now { + progress = nil + } else { + timers.set(.progressHide, at: hideAt) + } + } + + // MARK: - Setup actions + + private func runSetupAction(_ issue: SetupIssue, folded: Bool = false) { + setupLog = nil + setupOutput = [] + transition(to: .settingUp(issue)) + if folded { setupFolded = true } + let (lines, sink) = AsyncStream.makeStream(of: String.self) + let showLines = Task { [weak self] in + for await line in lines { self?.record(line) } + } + enqueue { [weak self, backend] in + let outcome: Result + do { + try await backend.fix(issue) { sink.yield($0) } + outcome = .success(()) + } catch { + outcome = .failure(error) + } + sink.finish() + await showLines.value // Every line is shown before the outcome. + await self?.setupFinished(issue, outcome) + } + } + + private func record(_ line: String) { + setupLog = line + setupOutput.append(line) + if setupOutput.count > Self.setupOutputLimit { + setupOutput.removeFirst(setupOutput.count - Self.setupOutputLimit) + } + } + + private func startRun(_ issue: SetupIssue, mode: RunMode, folded: Bool, + waiter: CheckedContinuation? = nil) { + if run != nil { endRun(.needs(issue)) } // Never happens today; it keeps "every waiter resumes once" true. + run = SetupRun(mode: mode, fixed: [issue], waiter: waiter) + runSetupAction(issue, folded: folded) + } + + private func endRun(_ outcome: SetupRunOutcome) { + let waiter = run?.waiter + run = nil + waiter?.resume(returning: outcome) + } + + /// Link runs leave a card, as before; window runs leave one only for a waiting link. + private func stop(_ outcome: SetupRunOutcome, card: SessionState, mode: RunMode) { + endRun(outcome) + if mode == .link || pendingURL != nil { transition(to: card) } else { enterIdle() } + } + + private static func fixes(_ issue: SetupIssue, in mode: RunMode) -> Bool { + switch (mode, issue) { + case (.window(continuing: true), .serviceStopped), (.window(continuing: true), .kernelMissing), + (.window(continuing: true), .imageMissing), (.window(continuing: true), .imageOutdated), + (.check, .serviceStopped), (.check, .kernelMissing): + true + default: + false + } + } + + /// The backend runs one command for each group, so another issue from the same group means it didn't help. + private static func sameFix(_ a: SetupIssue, _ b: SetupIssue) -> Bool { + func group(_ issue: SetupIssue) -> Int? { + switch issue { + case .serviceStopped, .kernelMissing: 0 + case .imageMissing, .imageOutdated, .imageStale: 1 + default: nil + } + } + guard let first = group(a) else { return a == b } + return first == group(b) + } + + private func setupFinished(_ issue: SetupIssue, _ outcome: Result) async { + guard state == .settingUp(issue) else { return } + let mode = run?.mode ?? .link + if case .failure(let error) = outcome { + let message = Self.message(for: error) + // A failed "Update now" rebuild never blocks a link. + if case .imageStale = issue, let url = pendingURL { + endRun(.failed(issue, message)) + return startSession(url, pointerStays: true) + } + return stop(.failed(issue, message), card: .setupRequired(issue, error: message), mode: mode) + } + switch issue { + case .imageMissing, .imageOutdated, .imageStale: requestedImageUpdate = nil + default: break + } + if mode == .link, let url = pendingURL { + endRun(.ready) + return startSession(url, pointerStays: true) + } + let preflight = await backend.preflight() + guard state == .settingUp(issue) else { return } + switch preflight { + case .ready: + endRun(.ready) + if let url = pendingURL { return startSession(url, pointerStays: true) } + enterIdle() + case .issue(let next) where mode == .link: + endRun(.needs(next)) + if let url = pendingURL { return startSession(url, pointerStays: true) } + transition(to: .setupRequired(next, error: nil)) + case .issue(let next) where run?.fixed.contains(where: { Self.sameFix($0, next) }) == true: + stop(.failed(next, Self.stillBrokenMessage), card: .setupRequired(next, error: Self.stillBrokenMessage), mode: mode) + case .issue(let next) where Self.fixes(next, in: mode): + run?.fixed.append(next) + runSetupAction(next) + case .issue(let next): + stop(.needs(next), card: .setupRequired(next, error: nil), mode: mode) + } + } + + // MARK: - Backend chain + + /// After each job, one VM left by an earlier failed destroy gets a retry, taking turns. + private func enqueue(_ job: @escaping @MainActor () async -> Void) { + chain.enqueue { [weak self] in + let stuckBefore = self?.pendingCleanup ?? [] + await job() + await self?.retryPendingCleanup(among: stuckBefore) + } + } + + private func retryPendingCleanup(among candidates: [String]) async { + guard let id = candidates.first(where: pendingCleanup.contains) else { return } + do { + try await backend.forceDestroy(id: id) + pendingCleanup.removeAll { $0 == id } + liveIDs.remove(id) + } catch { + // Move it to the back so a VM stuck for good doesn't starve the others. + pendingCleanup.removeAll { $0 == id } + pendingCleanup.append(id) + } + } + + // MARK: - State and timers + + private func transition(to newState: SessionState) { + let oldState = state + guard newState != oldState else { return } + state = newState + if case .settingUp = newState {} else { + setupFolded = false + setupOpenedByHover = false + } + + if !isPeekLike { timers.cancel(.foldGrace) } + if newState != .live(.peek) { timers.cancel(.neverEnteredFold) } + + if newState == .live(.collapsed) { + timers.set(.idleDestroy, after: idleDestroyDelay) + idleDeadline = timers.deadline(of: .idleDestroy) + connection?.decodeEnabled = false + } else if oldState == .live(.collapsed) { + timers.cancel(.idleDestroy) + idleDeadline = nil + if case .live = newState { connection?.decodeEnabled = true } + } + + switch newState { + case .destroyed: timers.set(.transient, after: Self.destroyedDisplay) + case .failed(.ended): timers.set(.transient, after: Self.endedDisplay) + case .failed(.destroyStuck): timers.set(.transient, after: Self.destroyStuckDisplay) + default: timers.cancel(.transient) + } + + if newState == .idle { + timers.set(.idleQuit, after: Self.idleQuitDelay) + } else { + timers.cancel(.idleQuit) + } + + if case .settingUp(let issue) = oldState { + if case .settingUp = newState {} else { settled(after: issue) } + } + } + + /// So a waiting setup window never hangs, whatever ended the setup. + private func settled(after issue: SetupIssue) { + if run != nil { endRun(.needs(issue)) } + let waiters = settleWaiters + settleWaiters = [] + for waiter in waiters { waiter.resume() } + } + + private func show(_ newNotice: Notice) { + notice = newNotice + timers.set(.notice, after: Self.noticeDisplay) + } + + private func timerFired(_ key: TimerKey) { + switch key { + case .foldGrace: + if isPeekLike, entered, !pointerInside, !focused { fold() } + if case .settingUp = state, setupOpenedByHover, !pointerInside { + setupOpenedByHover = false + setupFolded = true + } + case .neverEnteredFold: + if state == .live(.peek), !entered { fold() } + case .idleDestroy: + destroy() + case .transient: + enterIdle() + case .notice: + notice = nil + case .progressHide: + progress = nil + case .idleQuit: + if quitsWhenIdle { requestQuit() } + } + } + + nonisolated static func message(for error: any Error) -> String { + switch error { + case VMBackendError.failed(let message): message + case StreamError.protocolViolation(let detail): "The VM sent a malformed message: \(detail)." + case StreamError.timedOut: "The VM didn't answer in time." + case StreamError.refused: "Couldn't connect to the VM." + default: String(describing: error) + } + } + + nonisolated static func message(for reason: EndReason) -> String { + switch reason { + case .closed: "The VM closed the connection." + case .protocolViolation(let detail): "The VM sent a malformed message: \(detail)." + case .noFirstFrame: "The VM didn't send a first frame in time." + case .decodeErrors: "The video stream couldn't be decoded." + case .socketError(let detail): "The connection to the VM failed: \(detail)." + } + } + + // MARK: - Test hooks + + func fireDueTimers() { + timers.fireDue() + } + + func waitForBackendIdle() async { + await chain.waitUntilIdle() + } + + /// Call once the session is retired: a live session's events task runs until its stream ends. + func waitForSessionTasks() async { + for task in current?.tasks ?? [] { await task.value } + } +} diff --git a/Sources/KnotchyCore/Session/SessionState.swift b/Sources/KnotchyCore/Session/SessionState.swift new file mode 100644 index 0000000..3c3c5a3 --- /dev/null +++ b/Sources/KnotchyCore/Session/SessionState.swift @@ -0,0 +1,36 @@ +public enum SessionState: Equatable, Sendable { + case idle + case starting(StartStep, folded: Bool) + case live(LiveMode) + case destroying + case destroyed + case failed(SessionFailure) + case setupRequired(SetupIssue, error: String?) + case settingUp(SetupIssue) +} + +public enum StartStep: Sendable { + case booting + /// Waiting for HELLO. + case connecting + /// Waiting for `ContentReadiness`. + case loading +} + +public enum LiveMode: Sendable { + case peek + case expanded + case collapsed +} + +public enum SessionFailure: Equatable, Sendable { + case start(String) + case ended(EndReason) + /// The VM stays on the pending-cleanup list. + case destroyStuck +} + +/// A transient pill, shown whatever the state. +public enum Notice: Equatable, Sendable { + case noWebLink +} diff --git a/Sources/KnotchyCore/Session/SetupRun.swift b/Sources/KnotchyCore/Session/SetupRun.swift new file mode 100644 index 0000000..ce55d85 --- /dev/null +++ b/Sources/KnotchyCore/Session/SetupRun.swift @@ -0,0 +1,20 @@ +/// How a setup run started by the setup window ended. +public enum SetupRunOutcome: Equatable, Sendable { + /// Preflight is ready; a waiting link may have started. + case ready + /// Stopped at an issue this kind of run doesn't fix. + case needs(SetupIssue) + case failed(SetupIssue, String) +} + +public enum RuntimeHoldRefusal: Error, Equatable, Sendable { + case sessionOpen + case setupRunning + case stopFailed(String) +} + +public enum RuntimeInstallResult: Equatable, Sendable { + case installed + case cancelled + case failed(String) +} diff --git a/Sources/KnotchyCore/Setup/ContainerRuntime.swift b/Sources/KnotchyCore/Setup/ContainerRuntime.swift new file mode 100644 index 0000000..c7d2efd --- /dev/null +++ b/Sources/KnotchyCore/Setup/ContainerRuntime.swift @@ -0,0 +1,55 @@ +import Foundation + +/// Apple's container runtime: the release Knotchy installs, and how an installed one is judged. +public enum ContainerRuntime { + public struct Release: Equatable, Sendable { + public let version: String + public let url: URL + public let sha256: String + public let size: Int64 + + public init(version: String, url: URL, sha256: String, size: Int64) { + self.version = version + self.url = url + self.sha256 = sha256 + self.size = size + } + } + + /// The only package Knotchy ever installs. Bump it only after testing a new release. + public static let pinned = Release( + version: "1.4.1", + url: URL(string: "https://github.com/apple/container/releases/download/1.4.1/container-1.4.1-installer-signed.pkg")!, + sha256: "c0d2716afefbb194c93fae662e9cae7cc186bcbcf746816608ec673dd648a6a4", + size: 117_773_865) + public static let minimumVersion = "1.4.0" + + public static func version(in output: String) -> String? { + output.firstMatch(of: #/container CLI version (\d+(?:\.\d+)+)/#).map { String($0.1) } + } + + /// Output Knotchy can't read counts as current, so a new format can never cause a downgrade. + public static func status(versionOutput: String?) -> RuntimeStatus { + guard let versionOutput else { return .missing } + guard let found = version(in: versionOutput) else { return .current(nil) } + return isOlder(found, than: minimumVersion) ? .outdated(found) : .current(found) + } + + public static func isOlder(_ version: String, than other: String) -> Bool { + let lhs = version.split(separator: ".").map { Int($0) ?? 0 } + let rhs = other.split(separator: ".").map { Int($0) ?? 0 } + for index in 0.. HostFacts +} + +public struct SystemHost: HostChecking { + public init() {} + + /// Synchronous, so the app can decide on minimal mode before it registers anything. + @MainActor + public static func unsupported() -> Unsupported? { + VZVirtualMachine.isSupported ? nil : .noVirtualization + } + + public func facts() async -> HostFacts { + let unsupported = await Self.unsupported() + let version = ProcessInfo.processInfo.operatingSystemVersion + var os = "macOS \(version.majorVersion).\(version.minorVersion)" + if version.patchVersion > 0 { os += ".\(version.patchVersion)" } + // The runtime keeps its data under the home folder. + let values = try? URL(fileURLWithPath: NSHomeDirectory()) + .resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey]) + return HostFacts(unsupported: unsupported, osVersion: os, bytesFree: values?.volumeAvailableCapacityForImportantUsage) + } +} diff --git a/Sources/KnotchyCore/Setup/RuntimeInstaller.swift b/Sources/KnotchyCore/Setup/RuntimeInstaller.swift new file mode 100644 index 0000000..f1c893c --- /dev/null +++ b/Sources/KnotchyCore/Setup/RuntimeInstaller.swift @@ -0,0 +1,209 @@ +import AppKit +import CryptoKit +import Foundation + +public struct DownloadedPackage: Equatable, Sendable { + public let directory: URL + /// Apple's own file name, which Installer shows. + public let name: String + public var file: URL { directory.appendingPathComponent(name) } + + public init(directory: URL, name: String) { + self.directory = directory + self.name = name + } +} + +public enum DownloadError: Error, Equatable, Sendable { + case failed(String) + /// Size or SHA-256 differ from the pinned release; the download is already deleted. + case mismatch +} + +@MainActor +public protocol InstallerSession: AnyObject { + var hasQuit: Bool { get } +} + +public enum InstallerOpening { + case opened(any InstallerSession) + /// Size or SHA-256 no longer match; the download is already deleted. + case changed + case failed(String) +} + +public protocol RuntimeInstalling: Sendable { + func download(_ release: ContainerRuntime.Release, + progress: @escaping @Sendable (Int64) -> Void) async throws -> DownloadedPackage + /// Re-checks the size and hash off the main thread, then opens the package in Installer. + @MainActor func openInInstaller(_ package: DownloadedPackage, release: ContainerRuntime.Release) async -> InstallerOpening + func discard(_ package: DownloadedPackage) +} + +public struct SystemRuntimeInstaller: RuntimeInstalling { + static let installerApp = URL(fileURLWithPath: "/System/Library/CoreServices/Installer.app") + + public init() {} + + public func download(_ release: ContainerRuntime.Release, + progress: @escaping @Sendable (Int64) -> Void) async throws -> DownloadedPackage { + Self.removeLeftovers() + let directory = URL(fileURLWithPath: NSTemporaryDirectory()) + .appendingPathComponent("knotchy-runtime-\(UUID().uuidString)", isDirectory: true) + do { + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, + attributes: [.posixPermissions: 0o700]) + } catch { + throw DownloadError.failed(error.localizedDescription) + } + let package = DownloadedPackage(directory: directory, name: release.url.lastPathComponent) + do { + try await PackageDownload.fetch(release.url, to: package.file, progress: progress) + } catch { + discard(package) + throw DownloadError.failed(error.localizedDescription) + } + guard Self.matches(package.file, release) else { + discard(package) + throw DownloadError.mismatch + } + return package + } + + public func discard(_ package: DownloadedPackage) { + try? FileManager.default.removeItem(at: package.directory) + } + + /// Folders left by quitting mid-download or a crash. + public static func removeLeftovers() { + let temporary = URL(fileURLWithPath: NSTemporaryDirectory()) + let names = (try? FileManager.default.contentsOfDirectory(atPath: temporary.path)) ?? [] + for name in names where name.hasPrefix("knotchy-runtime-") { + try? FileManager.default.removeItem(at: temporary.appendingPathComponent(name)) + } + } + + @MainActor + public func openInInstaller(_ package: DownloadedPackage, release: ContainerRuntime.Release) async -> InstallerOpening { + let file = package.file + guard await Task.detached(operation: { Self.matches(file, release) }).value else { + discard(package) + return .changed + } + let configuration = NSWorkspace.OpenConfiguration() + configuration.activates = true + do { + let app = try await NSWorkspace.shared.open([file], withApplicationAt: Self.installerApp, + configuration: configuration) + return .opened(RunningInstaller(app: app)) + } catch { + return .failed(error.localizedDescription) + } + } + + static func matches(_ file: URL, _ release: ContainerRuntime.Release) -> Bool { + guard let size = (try? FileManager.default.attributesOfItem(atPath: file.path))?[.size] as? NSNumber, + size.int64Value == release.size, + let handle = try? FileHandle(forReadingFrom: file) else { return false } + defer { try? handle.close() } + var hash = SHA256() + while let chunk = try? handle.read(upToCount: 1 << 20), !chunk.isEmpty { hash.update(data: chunk) } + return hash.finalize().map { String(format: "%02x", $0) }.joined() == release.sha256 + } +} + +@MainActor +private final class RunningInstaller: InstallerSession { + let app: NSRunningApplication + + init(app: NSRunningApplication) { + self.app = app + } + + var hasQuit: Bool { app.isTerminated } +} + +/// A data task streamed into the package file, so the partial download lives only in its own folder. +private final class PackageDownload: NSObject, URLSessionDataDelegate, @unchecked Sendable { + // Touched only on the session's serial delegate queue. + private var continuation: CheckedContinuation? + /// A completion that arrived before anyone waited: a task cancelled before it started. + private var outcome: Result? + private var handle: FileHandle? + private var written: Int64 = 0 + private var writeError: (any Error)? + private let destination: URL + private let progress: @Sendable (Int64) -> Void + + private init(destination: URL, progress: @escaping @Sendable (Int64) -> Void) { + self.destination = destination + self.progress = progress + } + + static func fetch(_ url: URL, to destination: URL, progress: @escaping @Sendable (Int64) -> Void) async throws { + let delegate = PackageDownload(destination: destination, progress: progress) + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + let session = URLSession(configuration: .ephemeral, delegate: delegate, delegateQueue: queue) + defer { session.finishTasksAndInvalidate() } + // Created up front: cancelling before it exists would invalidate the session, and creating it then throws. + let task = session.dataTask(with: url) + try await withTaskCancellationHandler { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + queue.addOperation { + if let outcome = delegate.outcome { + continuation.resume(with: outcome) + } else { + delegate.continuation = continuation + task.resume() + } + } + } + } onCancel: { + task.cancel() + } + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, + completionHandler: @escaping @Sendable (URLSession.ResponseDisposition) -> Void) { + if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) { + writeError = URLError(.badServerResponse) + return completionHandler(.cancel) + } + do { + guard FileManager.default.createFile(atPath: destination.path, contents: nil) else { + throw CocoaError(.fileWriteUnknown, userInfo: [NSFilePathErrorKey: destination.path]) + } + handle = try FileHandle(forWritingTo: destination) + } catch { + writeError = error + return completionHandler(.cancel) + } + completionHandler(.allow) + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + guard let handle, writeError == nil else { return } + do { + try handle.write(contentsOf: data) + written += Int64(data.count) + progress(written) + } catch { + writeError = error + dataTask.cancel() + } + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { + try? handle?.close() + handle = nil + // Ours comes first: cancelling after it adds a "cancelled" error. + let result: Result = if let failure = writeError ?? error { .failure(failure) } else { .success(()) } + if let waiting = continuation { + continuation = nil + waiting.resume(with: result) + } else { + outcome = result + } + } +} diff --git a/Sources/KnotchyCore/Setup/SetupDriving.swift b/Sources/KnotchyCore/Setup/SetupDriving.swift new file mode 100644 index 0000000..87dacae --- /dev/null +++ b/Sources/KnotchyCore/Setup/SetupDriving.swift @@ -0,0 +1,15 @@ +/// What the setup flow needs from the session controller. +@MainActor +public protocol SetupDriving: AnyObject { + var state: SessionState { get } + var setupLog: String? { get } + var setupOutput: [String] { get } + func runtimeStatus() async -> RuntimeStatus + func checkSetup() async -> SetupRunOutcome? + func runSetup(_ issue: SetupIssue, continuing: Bool) async -> SetupRunOutcome? + func beginRuntimeInstall(replacing found: String?) async -> Result + func finishRuntimeInstall(_ result: RuntimeInstallResult) async -> SetupRunOutcome + func waitWhileSettingUp() async +} + +extension SessionController: SetupDriving {} diff --git a/Sources/KnotchyCore/Setup/SetupFlow.swift b/Sources/KnotchyCore/Setup/SetupFlow.swift new file mode 100644 index 0000000..6cb7603 --- /dev/null +++ b/Sources/KnotchyCore/Setup/SetupFlow.swift @@ -0,0 +1,360 @@ +import Foundation +import Observation + +/// The setup window's rows and the order of its steps. The window binds only to this. +@MainActor +@Observable +public final class SetupFlow { + public enum Status: Equatable, Sendable { + case pending(String) + case running(String, fraction: Double?) + case done(String) + case failed(String, details: [String]) + } + + public enum RuntimeAction: Equatable, Sendable { + case install + case update + } + + public static let requiredFreeBytes: Int64 = 3_000_000_000 + static let vmDetail = "About 3 minutes · downloads about 225 MB" + + public private(set) var mac: Status = .pending("Checking…") + public private(set) var unsupported: Unsupported? + public private(set) var runtimeAction: RuntimeAction? + public private(set) var isRunning = false + /// Quitting now leaves a partial download behind. + @ObservationIgnored public private(set) var isDownloading = false + private var runtimeBase: Status = .pending("") + private var vmBase: Status = .pending(SetupFlow.vmDetail) + + @ObservationIgnored private let host: any HostChecking + @ObservationIgnored private let installer: any RuntimeInstalling + @ObservationIgnored private let driver: any SetupDriving + @ObservationIgnored private let release: ContainerRuntime.Release + @ObservationIgnored private let retryDelay: Duration + @ObservationIgnored private var facts: HostFacts? + @ObservationIgnored private var runtimeStatus: RuntimeStatus? + + public init(host: any HostChecking, installer: any RuntimeInstalling, driver: any SetupDriving, + release: ContainerRuntime.Release = ContainerRuntime.pinned, retryDelay: Duration = .milliseconds(500)) { + self.host = host + self.installer = installer + self.driver = driver + self.release = release + self.retryDelay = retryDelay + runtimeBase = .pending(Self.installDetail(release)) + } + + // MARK: - Rows + + /// With the controller's live progress on top, so a setup the notch started shows here too. + public var runtime: Status { + if case .settingUp(let issue) = driver.state, issue == .serviceStopped || issue == .kernelMissing { + return .running("Starting the runtime…", fraction: nil) + } + return runtimeBase + } + + public var vm: Status { + if case .settingUp(let issue) = driver.state, Self.isImageIssue(issue) { + return .running(driver.setupLog ?? "Starting the build…", fraction: nil) + } + return vmBase + } + + public var allRequiredDone: Bool { + if case .done = mac, case .done = runtime, case .done = vm { return true } + return false + } + + public var canSetUp: Bool { + guard !isRunning, unsupported == nil, !allRequiredDone else { return false } + if case .failed = mac { return false } + return true + } + + // MARK: - Actions + + @ObservationIgnored private var work: Task? + + /// For the Set Up and Try Again buttons: runs `setUp()` where quitting can cancel it. + public func start() { + guard !isRunning else { return } + work = Task { await setUp() } + } + + /// Quitting: stops a download. An open Installer is left to finish. + public func cancel() { + work?.cancel() + } + + /// Re-checks everything. Starts a stopped service, as a link would, but never installs or builds. + public func refresh() async { + guard !isRunning else { return } + isRunning = true + defer { isRunning = false } + await inspect() + } + + /// The Set Up and Try Again buttons: runs whatever is left, in order. + public func setUp() async { + guard !isRunning else { return } + isRunning = true + defer { isRunning = false } + await inspect() + guard unsupported == nil else { return } + // The service didn't start: building would only try again. + if case .failed = runtimeBase { return } + if case .failed = mac { return } + if let action = runtimeAction { + await installRuntime(action) + } else if case .done = vmBase { + return + } else { + await buildVM() + } + updateMac() + } + + // MARK: - Steps + + private func inspect() async { + let facts = await host.facts() + self.facts = facts + unsupported = facts.unsupported + guard unsupported == nil else { return } + let status = await driver.runtimeStatus() + runtimeStatus = status + switch status { + case .missing: + runtimeAction = .install + runtimeBase = .pending(Self.installDetail(release)) + vmBase = .pending(Self.vmDetail) + case .outdated(let found): + runtimeAction = .update + runtimeBase = .pending("container \(found) is too old · updates to \(release.version) with Apple's Installer") + vmBase = .pending(Self.vmDetail) + case .current(let version): + runtimeAction = nil + runtimeBase = .done(Self.installedText(version)) + switch await driver.checkSetup() { + case .ready?: vmBase = .done("Ready") + case .needs(let issue)? where Self.isImageIssue(issue): vmBase = .pending(Self.vmDetail) + case .failed(let issue, let message)?: fail(issue, message) + case nil where Self.isBrowserOpen(driver.state): vmBase = .done("Ready") // A running VM has an image. + case .needs?, nil: break // Busy, or the runtime row's business: the live rows show it. + } + } + updateMac() + } + + private func installRuntime(_ action: RuntimeAction) async { + switch driver.state { + case .starting, .live: + runtimeBase = .failed("Close the Knotchy browser first.", details: []) + return + case .destroying, .failed(.destroyStuck): + runtimeBase = .failed("Wait for the Knotchy browser to finish closing, then try again.", details: []) + return + default: + break + } + runtimeBase = .running(Self.downloadText(0, of: release.size), fraction: 0) + let package: DownloadedPackage + do { + package = try await download() + } catch DownloadError.mismatch { + runtimeBase = .failed("The download didn't match the expected package, so it wasn't installed.", details: []) + return + } catch { + runtimeBase = .failed("Couldn't download the runtime. Check your connection and try again.", + details: [String(describing: error)]) + return + } + let found: String? = if case .outdated(let version) = runtimeStatus { version } else { nil } + let hold = await driver.beginRuntimeInstall(replacing: found) + if case .failure = hold { installer.discard(package) } + switch hold { + case .success: + break + case .failure(.sessionOpen): + runtimeBase = .failed("Close the Knotchy browser first.", details: []) + return + case .failure(.setupRunning): + runtimeBase = .failed("Wait for the current setup step to finish, then try again.", details: []) + return + case .failure(.stopFailed(let message)): + runtimeBase = .failed("Couldn't stop the runtime for the update.", details: [message]) + return + } + + runtimeBase = .running("Opening Installer…", fraction: nil) + let session: any InstallerSession + switch await installer.openInInstaller(package, release: release) { + case .opened(let opened): + session = opened + case .changed: + return await failInstall("The download changed after it was checked, so it wasn't opened.", details: []) + case .failed(let message): + installer.discard(package) + return await failInstall("Couldn't open Installer.", details: [message]) + } + + runtimeBase = .running("Finish the install in Installer…", fraction: nil) + let status = await waitForInstall(session) + // Installer reads the package until it quits. + Task { [installer] in + while !session.hasQuit { try? await Task.sleep(for: .seconds(2)) } + installer.discard(package) + } + // Quitting: Installer carries on by itself. + if Task.isCancelled { return } + guard case .current(let version)? = status else { + _ = await driver.finishRuntimeInstall(.cancelled) + runtimeBase = .pending("Installing was cancelled.") + return + } + runtimeStatus = status + runtimeAction = nil + runtimeBase = .done(Self.installedText(version)) + apply(await driver.finishRuntimeInstall(.installed)) + } + + /// The current runtime once it appears, or nil when Installer quits without it. + private func waitForInstall(_ session: any InstallerSession) async -> RuntimeStatus? { + while !Task.isCancelled { + let status = await driver.runtimeStatus() + if case .current = status { return status } + if session.hasQuit { + // Installer may have finished between the two checks. + let last = await driver.runtimeStatus() + if case .current = last { return last } + return nil + } + try? await Task.sleep(for: retryDelay) + } + return nil + } + + private func download() async throws -> DownloadedPackage { + isDownloading = true + defer { isDownloading = false } + return try await installer.download(release) { [weak self, release] bytes in + Task { @MainActor in self?.downloaded(bytes, of: release.size) } + } + } + + private func failInstall(_ message: String, details: [String]) async { + _ = await driver.finishRuntimeInstall(.failed(message)) + runtimeBase = .failed(message, details: details) + } + + private func buildVM() async { + // A setup the notch started, or a session winding down, gets up to a minute to finish. + for _ in 0..<120 { + if Task.isCancelled { return } + guard let checked = await driver.checkSetup() else { + if case .settingUp = driver.state { + await driver.waitWhileSettingUp() + } else { + try? await Task.sleep(for: retryDelay) + } + continue + } + switch checked { + case .needs(let issue) where Self.isImageIssue(issue): + guard let result = await driver.runSetup(issue, continuing: true) else { continue } + return apply(result) + case .needs: + return await inspect() + default: + return apply(checked) + } + } + vmBase = .failed("Close the Knotchy browser first, then try again.", details: []) + } + + private func apply(_ outcome: SetupRunOutcome) { + switch outcome { + case .ready: + vmBase = .done("Ready") + case .needs(let issue): + if Self.isImageIssue(issue) { vmBase = .pending(Self.vmDetail) } + case .failed(let issue, let message): + fail(issue, message) + } + } + + private func fail(_ issue: SetupIssue, _ message: String) { + if Self.isImageIssue(issue) { + vmBase = .failed(message, details: driver.setupOutput) + } else { + runtimeBase = .failed(message, details: driver.setupOutput) + } + } + + private func downloaded(_ bytes: Int64, of total: Int64) { + // Late callbacks mustn't overwrite what came after the download. + guard case .running(let text, _) = runtimeBase, text.hasPrefix("Downloading") else { return } + if bytes >= total { + runtimeBase = .running("Verifying…", fraction: nil) + } else { + runtimeBase = .running(Self.downloadText(bytes, of: total), fraction: Double(bytes) / Double(max(total, 1))) + } + } + + private func updateMac() { + guard let facts else { return } + let free = facts.bytesFree.map { "\(Self.gigabytes($0)) GB free" } + if needsSpace, let bytes = facts.bytesFree, bytes < Self.requiredFreeBytes { + mac = .failed("Setting up needs about 3 GB free. \(Self.gigabytes(bytes)) GB is free.", details: []) + } else { + mac = .done([facts.osVersion, free].compactMap { $0 }.joined(separator: " · ")) + } + } + + /// Only the install and the build use space; starting the service doesn't. + private var needsSpace: Bool { + if runtimeAction != nil { return true } + if case .done = vmBase { return false } + return true + } + + // MARK: - Text + + static func installDetail(_ release: ContainerRuntime.Release) -> String { + "Apple's container \(release.version) · \(megabytes(release.size)) MB · installs with Apple's Installer" + } + + static func installedText(_ version: String?) -> String { + version.map { "container \($0) installed" } ?? "container installed" + } + + static func downloadText(_ bytes: Int64, of total: Int64) -> String { + "Downloading… \(megabytes(bytes)) of \(megabytes(total)) MB" + } + + static func megabytes(_ bytes: Int64) -> Int { + Int((Double(bytes) / 1_000_000).rounded()) + } + + static func gigabytes(_ bytes: Int64) -> String { + bytes >= 10_000_000_000 ? "\(bytes / 1_000_000_000)" : String(format: "%.1f", Double(bytes) / 1_000_000_000) + } + + static func isBrowserOpen(_ state: SessionState) -> Bool { + switch state { + case .starting, .live: true + default: false + } + } + + static func isImageIssue(_ issue: SetupIssue) -> Bool { + switch issue { + case .imageMissing, .imageOutdated, .imageStale: true + default: false + } + } +} diff --git a/Sources/KnotchyCore/Setup/SetupLaunchRule.swift b/Sources/KnotchyCore/Setup/SetupLaunchRule.swift new file mode 100644 index 0000000..bb0842b --- /dev/null +++ b/Sources/KnotchyCore/Setup/SetupLaunchRule.swift @@ -0,0 +1,12 @@ +/// When the setup window opens by itself at launch. +public enum SetupLaunchRule { + public static let completedKey = "setupCompleted" + + public static func shouldOpen(hostSupported: Bool, setupCompleted: Bool, runtime: RuntimeStatus) -> Bool { + guard hostSupported, setupCompleted else { return true } + switch runtime { + case .missing, .outdated: return true + case .current: return false + } + } +} diff --git a/Sources/KnotchyCore/Stream/ContentReadiness.swift b/Sources/KnotchyCore/Stream/ContentReadiness.swift new file mode 100644 index 0000000..f7fc688 --- /dev/null +++ b/Sources/KnotchyCore/Stream/ContentReadiness.swift @@ -0,0 +1,39 @@ +/// Decides when Starting ends. Frames and progress before NAVIGATED come from the agent's +/// about:blank bootstrap and never count; `afterHello` is a backstop if NAVIGATED never arrives. +public struct ContentReadiness: Sendable { + public static let afterNavigated: Duration = .seconds(15) + public static let afterHello: Duration = .seconds(25) + + public private(set) var isReady = false + private var helloAt: ContinuousClock.Instant? + private var navigatedAt: ContinuousClock.Instant? + + public init() {} + + public mutating func hello(at now: ContinuousClock.Instant) { + if helloAt == nil { helloAt = now } + } + + public mutating func navigated(at now: ContinuousClock.Instant) { + if navigatedAt == nil { navigatedAt = now } + } + + public mutating func frame(isUniform: Bool) { + if navigatedAt != nil, !isUniform { isReady = true } + } + + public mutating func progress(_ percent: Int) { + if navigatedAt != nil, percent >= 100 { isReady = true } + } + + public mutating func tick(now: ContinuousClock.Instant) { + if let deadline = nextDeadline, now >= deadline { isReady = true } + } + + public var nextDeadline: ContinuousClock.Instant? { + guard !isReady else { return nil } + return [navigatedAt.map { $0 + Self.afterNavigated }, helloAt.map { $0 + Self.afterHello }] + .compactMap { $0 } + .min() + } +} diff --git a/Sources/KnotchyCore/Stream/FrameSampler.swift b/Sources/KnotchyCore/Stream/FrameSampler.swift new file mode 100644 index 0000000..e1466b9 --- /dev/null +++ b/Sources/KnotchyCore/Stream/FrameSampler.swift @@ -0,0 +1,22 @@ +/// Tells a blank frame from one with page content. +public enum FrameSampler { + public static let grid = 48 + public static let maxDimension = 1 << 15 + public static let maxBytesPerRow = 1 << 20 + + /// Malformed input counts as uniform so it can never pose as content; the caps prevent overflow. + public static func isUniform(_ pixels: UnsafeRawBufferPointer, width: Int, height: Int, bytesPerRow: Int) -> Bool { + guard (1...maxDimension).contains(width), (1...maxDimension).contains(height), + bytesPerRow >= width * 4, bytesPerRow <= maxBytesPerRow, + pixels.count >= (height - 1) * bytesPerRow + width * 4 + else { return true } + let first = pixels.loadUnaligned(fromByteOffset: 0, as: UInt32.self) + for gy in 0.. [Data] { + let bytes = [UInt8](stream) + var starts: [(code: Int, payload: Int)] = [] + var i = 0 + while i + 2 < bytes.count { + if bytes[i] == 0, bytes[i + 1] == 0, bytes[i + 2] == 1 { + starts.append((code: i, payload: i + 3)) + i += 3 + } else { + i += 1 + } + } + return starts.indices.compactMap { k in + var end = k + 1 < starts.count ? starts[k + 1].code : bytes.count + while end > starts[k].payload, bytes[end - 1] == 0 { end -= 1 } + return end > starts[k].payload ? Data(bytes[starts[k].payload.. ParsedAccessUnit { + var unit = ParsedAccessUnit() + var sps: Data? + var pps: Data? + for nal in AnnexB.nalUnits(in: accessUnit) { + let header = nal[nal.startIndex] + guard header & 0x80 == 0 else { continue } // forbidden_zero_bit + let type = header & 0x1F + switch type { + case NALUnitType.sps: + sps = nal + case NALUnitType.pps: + pps = nal + case NALUnitType.slice, NALUnitType.idrSlice: + if type == NALUnitType.idrSlice { unit.isIDR = true } + unit.sample.appendBE32(UInt32(nal.count)) + unit.sample.append(nal) + default: + break + } + } + // SPS and PPS must come from the same access unit; x264 repeats both on every IDR. + if let sps, let pps { + let sets = ParameterSets(sps: sps, pps: pps) + if sets != parameterSets { + parameterSets = sets + unit.newParameterSets = sets + } + } + return unit + } +} diff --git a/Sources/KnotchyCore/Stream/InputEvent.swift b/Sources/KnotchyCore/Stream/InputEvent.swift new file mode 100644 index 0000000..2edb7e0 --- /dev/null +++ b/Sources/KnotchyCore/Stream/InputEvent.swift @@ -0,0 +1,78 @@ +import Foundation + +public struct Modifiers: OptionSet, Hashable, Sendable { + public let rawValue: UInt8 + + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + public static let shift = Modifiers(rawValue: 1 << 0) + public static let control = Modifiers(rawValue: 1 << 1) + public static let alt = Modifiers(rawValue: 1 << 2) + public static let meta = Modifiers(rawValue: 1 << 3) + public static let leftButton = Modifiers(rawValue: 1 << 4) + public static let rightButton = Modifiers(rawValue: 1 << 5) + public static let middleButton = Modifiers(rawValue: 1 << 6) +} + +public enum PointerButton: UInt8, Sendable { + case left = 1 + case right = 2 + case middle = 3 +} + +/// A viewer → agent message. Coordinates are render pixels from the top left; negative scroll is down/right. +public enum InputEvent: Equatable, Sendable { + case pointerMove(x: UInt16, y: UInt16, mods: Modifiers) + case pointerButton(PointerButton, pressed: Bool, x: UInt16, y: UInt16, mods: Modifiers) + case scroll(ticksX: Int16, ticksY: Int16, x: UInt16, y: UInt16) + case key(pressed: Bool, mods: Modifiers, name: String) + case navigate(String) + case script(String) + case keyframe + + /// Nil if the agent can't use it: a payload over 64 KiB, an empty KEY name, or a NUL in a string. + public func encoded() -> Data? { + var payload = Data() + let type: UInt8 + switch self { + case .pointerMove(let x, let y, let mods): + type = MessageType.pointerMove + payload.appendBE16(x) + payload.appendBE16(y) + payload.append(mods.rawValue) + case .pointerButton(let button, let pressed, let x, let y, let mods): + type = MessageType.pointerButton + payload.append(button.rawValue) + payload.append(pressed ? 1 : 0) + payload.appendBE16(x) + payload.appendBE16(y) + payload.append(mods.rawValue) + case .scroll(let ticksX, let ticksY, let x, let y): + type = MessageType.scroll + payload.appendBE16(UInt16(bitPattern: ticksX)) + payload.appendBE16(UInt16(bitPattern: ticksY)) + payload.appendBE16(x) + payload.appendBE16(y) + case .key(let pressed, let mods, let name): + guard !name.isEmpty, !name.utf8.contains(0) else { return nil } + type = MessageType.key + payload.append(pressed ? 1 : 0) + payload.append(mods.rawValue) + payload.append(contentsOf: Array(name.utf8)) + case .navigate(let url): + guard !url.utf8.contains(0) else { return nil } + type = MessageType.navigate + payload.append(contentsOf: Array(url.utf8)) + case .script(let javaScript): + guard !javaScript.utf8.contains(0) else { return nil } + type = MessageType.script + payload.append(contentsOf: Array(javaScript.utf8)) + case .keyframe: + type = MessageType.keyframe + } + guard payload.count <= WireProtocol.maxOtherPayload else { return nil } + return framed(type, payload) + } +} diff --git a/Sources/KnotchyCore/Stream/StreamClient.swift b/Sources/KnotchyCore/Stream/StreamClient.swift new file mode 100644 index 0000000..e8b4c61 --- /dev/null +++ b/Sources/KnotchyCore/Stream/StreamClient.swift @@ -0,0 +1,348 @@ +import CoreVideo +import Darwin +import Foundation + +public struct StreamClient: StreamConnector { + public let renderSize: PixelSize + + public init(renderSize: PixelSize) { + self.renderSize = renderSize + } + + public func connect(socketPath: String) async throws -> any StreamConnection { + let renderSize = renderSize + return try await withCheckedThrowingContinuation { continuation in + DispatchQueue.global(qos: .userInitiated).async { + continuation.resume(with: Result { try Self.connectBlocking(path: socketPath, renderSize: renderSize) }) + } + } + } + + static func connectBlocking(path: String, renderSize: PixelSize) throws -> StreamSession { + let deadline = Date().addingTimeInterval(30) + var connectedOnce = false + while Date() < deadline { + if let fd = openSocket(path) { + connectedOnce = true + setReadTimeout(fd, seconds: 2) + var decoder = AgentMessageDecoder() + switch readHello(fd, into: &decoder) { + case .hello(let version, let size): + guard version == WireProtocol.version else { + Darwin.close(fd) + throw StreamError.versionMismatch(found: version, need: WireProtocol.version) + } + guard size == renderSize else { + Darwin.close(fd) + throw StreamError.protocolViolation("HELLO is \(size), asked for \(renderSize)") + } + setReadTimeout(fd, seconds: 0) + // A VM that stops reading input must not wedge the session's queue. + setSendTimeout(fd, seconds: 2) + return StreamSession(fd: fd, decoder: decoder, size: size) + case .violation(let detail): + Darwin.close(fd) + throw StreamError.protocolViolation(detail) + case .none: + Darwin.close(fd) + } + } + usleep(100_000) + } + throw connectedOnce ? StreamError.timedOut : StreamError.refused + } + + private enum HelloOutcome { + case hello(version: Int, size: PixelSize) + case violation(String) + case none + } + + private static func readHello(_ fd: Int32, into decoder: inout AgentMessageDecoder) -> HelloOutcome { + var buffer = [UInt8](repeating: 0, count: 64 * 1024) + while true { + let n = buffer.withUnsafeMutableBytes { read(fd, $0.baseAddress!, $0.count) } + if n < 0, errno == EINTR { continue } + guard n > 0 else { return .none } + decoder.append(Data(buffer[0.. Int32? { + let fd = socket(AF_UNIX, SOCK_STREAM, 0) + guard fd >= 0 else { return nil } + var address = sockaddr_un() + address.sun_family = sa_family_t(AF_UNIX) + let bytes = Array(path.utf8) + guard bytes.count < MemoryLayout.size(ofValue: address.sun_path) else { + Darwin.close(fd) + return nil + } + withUnsafeMutableBytes(of: &address.sun_path) { $0.copyBytes(from: bytes) } + let size = socklen_t(MemoryLayout.size) + let result = withUnsafePointer(to: &address) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { Darwin.connect(fd, $0, size) } + } + guard result == 0 else { + Darwin.close(fd) + return nil + } + var on: Int32 = 1 + setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, socklen_t(MemoryLayout.size)) + return fd + } + + static func setReadTimeout(_ fd: Int32, seconds: Int) { + var timeout = timeval.init(tv_sec: seconds, tv_usec: 0) + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, socklen_t(MemoryLayout.size)) + } + + static func setSendTimeout(_ fd: Int32, seconds: Int) { + var timeout = timeval.init(tv_sec: seconds, tv_usec: 0) + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, socklen_t(MemoryLayout.size)) + } +} + +/// Its reader thread must never stop (the VM's watchdog needs a connected viewer); all else runs on `queue`. +final class StreamSession: StreamConnection, @unchecked Sendable { + static let firstFrameTimeout: TimeInterval = 20 + static let decodeErrorLimit = 10 + + let events: AsyncStream + private let continuation: AsyncStream.Continuation + private let queue = DispatchQueue(label: "aculix.knotchy.stream") + + private let fd: Int32 + private var decoder: AgentMessageDecoder + private var parser = H264Parser() + private var video: VideoDecoder? + private var readiness = ContentReadiness() + private var announcedReady = false + private var enabled = true + private var waitingForIDR = false + private var frameHandler: (@Sendable (DecodedFrame) -> Void)? + private var readinessTimer: DispatchSourceTimer? + private var firstFrameTimer: DispatchSourceTimer? + private var pendingPageURL: URL? + private var pageURLTimer: DispatchSourceTimer? + private var decodeErrors: [Date] = [] + private var ended = false + + init(fd: Int32, decoder: AgentMessageDecoder, size: PixelSize) { + self.fd = fd + self.decoder = decoder + let (stream, continuation) = AsyncStream.makeStream(of: StreamEvent.self) + events = stream + self.continuation = continuation + continuation.yield(.hello(version: WireProtocol.version, size: size)) + queue.async { self.begin() } + let reader = Thread { [self] in readLoop() } + reader.name = "knotchy stream reader" + reader.start() + } + + // MARK: StreamConnection + + var decodeEnabled: Bool { + get { queue.sync { enabled } } + set { + queue.async { + if newValue, !self.enabled { + self.waitingForIDR = true + self.write(.keyframe) + } + self.enabled = newValue + } + } + } + + func setFrameHandler(_ handler: @escaping @Sendable (DecodedFrame) -> Void) { + queue.async { self.frameHandler = handler } + } + + func send(_ input: InputEvent) { + queue.async { self.write(input) } + } + + func close() { + queue.async { self.end(nil) } + } + + // MARK: Reading + + private func readLoop() { + var buffer = [UInt8](repeating: 0, count: 64 * 1024) + while true { + let n = buffer.withUnsafeMutableBytes { read(fd, $0.baseAddress!, $0.count) } + if n < 0, errno == EINTR { continue } + guard n > 0 else { break } + let chunk = Data(buffer[0.. Self.decodeErrorLimit { return end(.decodeErrors) } + waitingForIDR = true + write(.keyframe) + } + + private static func isUniform(_ buffer: CVPixelBuffer) -> Bool { + CVPixelBufferLockBaseAddress(buffer, .readOnly) + defer { CVPixelBufferUnlockBaseAddress(buffer, .readOnly) } + guard let base = CVPixelBufferGetBaseAddress(buffer) else { return true } + let bytes = UnsafeRawBufferPointer(start: base, count: CVPixelBufferGetDataSize(buffer)) + return FrameSampler.isUniform(bytes, width: CVPixelBufferGetWidth(buffer), + height: CVPixelBufferGetHeight(buffer), + bytesPerRow: CVPixelBufferGetBytesPerRow(buffer)) + } + + // MARK: Readiness + + private func announceIfReady() { + guard readiness.isReady, !announcedReady else { return } + announcedReady = true + readinessTimer?.cancel() + readinessTimer = nil + continuation.yield(.contentReady) + } + + private func scheduleReadinessTick() { + readinessTimer?.cancel() + readinessTimer = nil + guard let deadline = readiness.nextDeadline else { return } + let wait = deadline - .now + let seconds = max(0, Double(wait.components.seconds) + Double(wait.components.attoseconds) / 1e18) + let timer = DispatchSource.makeTimerSource(queue: queue) + timer.schedule(deadline: .now() + seconds) + timer.setEventHandler { [weak self] in + guard let self else { return } + self.readiness.tick(now: .now) + self.announceIfReady() + } + timer.resume() + readinessTimer = timer + } + + /// At most 10 a second, always including the last: a page can change its address in a loop. + private func passOnPageURL() { + guard pageURLTimer == nil, let url = pendingPageURL else { return } + pendingPageURL = nil + continuation.yield(.pageURL(url)) + let timer = DispatchSource.makeTimerSource(queue: queue) + timer.schedule(deadline: .now() + 0.1) + timer.setEventHandler { [weak self] in + guard let self, !self.ended else { return } + self.pageURLTimer = nil + self.passOnPageURL() + } + timer.resume() + pageURLTimer = timer + } + + // MARK: Writing and ending + + private func write(_ input: InputEvent) { + guard !ended, let data = input.encoded() else { return } + let stalled = data.withUnsafeBytes { raw -> Bool in + var offset = 0 + while offset < raw.count { + let n = Darwin.write(fd, raw.baseAddress! + offset, raw.count - offset) + if n > 0 { offset += n } else if n < 0, errno == EINTR { continue } else { + return n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) + } + } + return false + } + if stalled { end(.socketError("The VM stopped taking input.")) } + } + + private func end(_ reason: EndReason?) { + guard !ended else { return } + ended = true + shutdown(fd, SHUT_RDWR) + readinessTimer?.cancel() + firstFrameTimer?.cancel() + pageURLTimer?.cancel() + video = nil + frameHandler = nil + if let reason { continuation.yield(.ended(reason)) } + continuation.finish() + } +} diff --git a/Sources/KnotchyCore/Stream/StreamConnection.swift b/Sources/KnotchyCore/Stream/StreamConnection.swift new file mode 100644 index 0000000..ac5be97 --- /dev/null +++ b/Sources/KnotchyCore/Stream/StreamConnection.swift @@ -0,0 +1,50 @@ +import CoreVideo + +public protocol StreamConnector: Sendable { + /// Returns once a valid HELLO arrives; a version mismatch is reported before any other HELLO problem. + func connect(socketPath: String) async throws -> any StreamConnection +} + +/// `@unchecked`: a decoded buffer is never written after VideoToolbox returns it. +public struct DecodedFrame: @unchecked Sendable { + public let pixelBuffer: CVPixelBuffer + + public init(_ pixelBuffer: CVPixelBuffer) { + self.pixelBuffer = pixelBuffer + } +} + +public protocol StreamConnection: AnyObject, Sendable { + /// Unbounded: reading starts only after `connect` returns, and early events must not be lost. + var events: AsyncStream { get } + var decodeEnabled: Bool { get set } + /// Called on the decode queue. + func setFrameHandler(_ handler: @escaping @Sendable (DecodedFrame) -> Void) + func send(_ input: InputEvent) + func close() +} + +public enum StreamEvent: Equatable, Sendable { + case hello(version: Int, size: PixelSize) + case contentReady + case progress(Int) + /// Untrusted (from the VM): display only. + case pageURL(URL) + case ended(EndReason) +} + +public enum EndReason: Equatable, Sendable { + /// EOF: the browser crashed, the firewall failed, or the VM's watchdog fired. + case closed + case protocolViolation(String) + case noFirstFrame + case decodeErrors + case socketError(String) +} + +public enum StreamError: Error, Equatable, Sendable { + case versionMismatch(found: Int, need: Int) + case protocolViolation(String) + case timedOut + case refused +} diff --git a/Sources/KnotchyCore/Stream/VideoDecoder.swift b/Sources/KnotchyCore/Stream/VideoDecoder.swift new file mode 100644 index 0000000..b6a5424 --- /dev/null +++ b/Sources/KnotchyCore/Stream/VideoDecoder.swift @@ -0,0 +1,74 @@ +import CoreMedia +import CoreVideo +import Foundation +import VideoToolbox + +final class VideoDecoder { + typealias Output = @Sendable (DecodedFrame?, OSStatus) -> Void + + private let session: VTDecompressionSession + private let format: CMVideoFormatDescription + private let output: Output + + init?(parameterSets: ParameterSets, output: @escaping Output) { + var description: CMFormatDescription? + let sps = [UInt8](parameterSets.sps) + let pps = [UInt8](parameterSets.pps) + let status = sps.withUnsafeBufferPointer { s in + pps.withUnsafeBufferPointer { p in + let pointers = [s.baseAddress!, p.baseAddress!] + return CMVideoFormatDescriptionCreateFromH264ParameterSets( + allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: pointers, + parameterSetSizes: [sps.count, pps.count], nalUnitHeaderLength: 4, formatDescriptionOut: &description) + } + } + guard status == noErr, let description else { return nil } + let attributes: [CFString: Any] = [ + kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA, + kCVPixelBufferIOSurfacePropertiesKey: [CFString: Any]() as CFDictionary, + ] + var created: VTDecompressionSession? + let sessionStatus = VTDecompressionSessionCreate( + allocator: kCFAllocatorDefault, formatDescription: description, decoderSpecification: nil, + imageBufferAttributes: attributes as CFDictionary, outputCallback: nil, decompressionSessionOut: &created) + guard sessionStatus == noErr, let created else { return nil } + session = created + format = description + self.output = output + } + + deinit { + VTDecompressionSessionInvalidate(session) + } + + func decode(_ sample: Data) { + var block: CMBlockBuffer? + guard CMBlockBufferCreateWithMemoryBlock( + allocator: kCFAllocatorDefault, memoryBlock: nil, blockLength: sample.count, + blockAllocator: kCFAllocatorDefault, customBlockSource: nil, offsetToData: 0, + dataLength: sample.count, flags: 0, blockBufferOut: &block) == noErr, let block + else { return output(nil, -1) } + sample.withUnsafeBytes { bytes in + _ = CMBlockBufferReplaceDataBytes(with: bytes.baseAddress!, blockBuffer: block, + offsetIntoDestination: 0, dataLength: sample.count) + } + var sampleBuffer: CMSampleBuffer? + var size = sample.count + guard CMSampleBufferCreateReady( + allocator: kCFAllocatorDefault, dataBuffer: block, formatDescription: format, + sampleCount: 1, sampleTimingEntryCount: 0, sampleTimingArray: nil, + sampleSizeEntryCount: 1, sampleSizeArray: &size, sampleBufferOut: &sampleBuffer) == noErr, + let sampleBuffer + else { return output(nil, -1) } + let output = self.output + let status = VTDecompressionSessionDecodeFrame(session, sampleBuffer: sampleBuffer, flags: [], + infoFlagsOut: nil) { status, _, image, _, _ in + if status == noErr, let image { + output(DecodedFrame(image), noErr) + } else if status != noErr { + output(nil, status) + } + } + if status != noErr { output(nil, status) } + } +} diff --git a/Sources/KnotchyCore/Stream/WireProtocol.swift b/Sources/KnotchyCore/Stream/WireProtocol.swift new file mode 100644 index 0000000..eced567 --- /dev/null +++ b/Sources/KnotchyCore/Stream/WireProtocol.swift @@ -0,0 +1,214 @@ +import Foundation + +/// knotchy-agent ↔ viewer framing, both directions: `[u8 type][u32 big-endian payload length][payload]`. +public enum WireProtocol { + /// Must equal `ARG KNOTCHY_PROTOCOL` in vm/Containerfile. 2: proxy/VPN routes. 3: PAGE_URL. + public static let version = 3 + public static let headerSize = 5 + public static let maxVideoPayload = 2 * 1024 * 1024 + /// Also the agent's cap on viewer messages; unknown agent messages up to this size are skipped. + public static let maxOtherPayload = 64 * 1024 + public static let maxPageURL = 16 * 1024 +} + +enum MessageType { + static let hello: UInt8 = 0x01 + static let video: UInt8 = 0x02 + static let progress: UInt8 = 0x03 + static let navigated: UInt8 = 0x04 + static let pageURL: UInt8 = 0x05 + static let pointerMove: UInt8 = 0x10 + static let pointerButton: UInt8 = 0x11 + static let scroll: UInt8 = 0x12 + static let key: UInt8 = 0x13 + static let navigate: UInt8 = 0x14 + static let script: UInt8 = 0x15 + static let keyframe: UInt8 = 0x16 +} + +public struct PixelSize: Hashable, Sendable, CustomStringConvertible { + public var width: Int + public var height: Int + + public init(width: Int, height: Int) { + self.width = width + self.height = height + } + + public var description: String { "\(width)x\(height)" } +} + +public enum AgentMessage: Equatable, Sendable { + /// Version 0 means a pre-v1 image, whose HELLO has no version byte. + case hello(version: Int, size: PixelSize) + case video(size: PixelSize, accessUnit: Data) + case progress(Int) + case navigated + case pageURL(String) + case unknown(type: UInt8) +} + +extension AgentMessage { + /// For tests and fake agents: out-of-range values trap, so build malformed frames by hand. + func encoded() -> Data { + var payload = Data() + let type: UInt8 + switch self { + case .hello(let version, let size): + type = MessageType.hello + if version != 0 { payload.append(UInt8(version)) } + payload.appendBE16(UInt16(size.width)) + payload.appendBE16(UInt16(size.height)) + case .video(let size, let accessUnit): + type = MessageType.video + payload.appendBE16(UInt16(size.width)) + payload.appendBE16(UInt16(size.height)) + payload.append(accessUnit) + case .progress(let percent): + type = MessageType.progress + payload.append(UInt8(percent)) + case .navigated: + type = MessageType.navigated + case .pageURL(let url): + type = MessageType.pageURL + payload.append(Data(url.utf8)) + case .unknown(let unknownType): + type = unknownType + } + return framed(type, payload) + } +} + +func framed(_ type: UInt8, _ payload: Data) -> Data { + var message = Data([type]) + message.appendBE32(UInt32(payload.count)) + message.append(payload) + return message +} + +extension Data { + mutating func appendBE16(_ value: UInt16) { + append(UInt8(value >> 8)) + append(UInt8(truncatingIfNeeded: value)) + } + + mutating func appendBE32(_ value: UInt32) { + appendBE16(UInt16(value >> 16)) + appendBE16(UInt16(truncatingIfNeeded: value)) + } +} + +/// Strict incremental parser; the VM is untrusted, so any bad message is a final violation. +/// Call `next()` until nil after each `append`: that keeps the buffer to one message plus one read. +public struct AgentMessageDecoder: Sendable { + public struct ProtocolViolation: Error, Equatable, Sendable, CustomStringConvertible { + public let description: String + + init(_ description: String) { + self.description = description + } + } + + public private(set) var helloSize: PixelSize? + private var buffer: [UInt8] = [] + private var start = 0 + private var violation: ProtocolViolation? + + public init() {} + + public mutating func append(_ bytes: Data) { + guard violation == nil else { return } + if start > 0 { + buffer.removeFirst(start) + start = 0 + } + buffer.append(contentsOf: bytes) + } + + public var bufferedByteCount: Int { buffer.count - start } + + /// Includes bytes already returned; for tests of the bound. + var storedByteCount: Int { buffer.count } + + public mutating func next() throws(ProtocolViolation) -> AgentMessage? { + if let violation { throw violation } + do { + return try decodeNext() + } catch { + violation = error + buffer = [] + start = 0 + throw error + } + } + + private mutating func decodeNext() throws(ProtocolViolation) -> AgentMessage? { + guard bufferedByteCount >= WireProtocol.headerSize else { return nil } + let type = buffer[start] + let length = Int(be32(at: start + 1)) + try checkHeader(type: type, length: length) + guard bufferedByteCount - WireProtocol.headerSize >= length else { return nil } + + let p = start + WireProtocol.headerSize + start = p + length + switch type { + case MessageType.hello: + let version = length == 5 ? Int(buffer[p]) : 0 + let sizeAt = p + length - 4 + let size = PixelSize(width: Int(be16(at: sizeAt)), height: Int(be16(at: sizeAt + 2))) + helloSize = size + return .hello(version: version, size: size) + case MessageType.video: + let size = PixelSize(width: Int(be16(at: p)), height: Int(be16(at: p + 2))) + guard size == helloSize else { + throw ProtocolViolation("VIDEO is \(size) but HELLO said \(helloSize.map(\.description) ?? "nothing")") + } + return .video(size: size, accessUnit: Data(buffer[(p + 4)..<(p + length)])) + case MessageType.progress: + let percent = Int(buffer[p]) + guard percent <= 100 else { throw ProtocolViolation("PROGRESS \(percent) is over 100") } + return .progress(percent) + case MessageType.navigated: + return .navigated + case MessageType.pageURL: + guard let url = String(validating: buffer[p..<(p + length)], as: UTF8.self) else { + throw ProtocolViolation("PAGE_URL isn't UTF-8") + } + return .pageURL(url) + default: + return .unknown(type: type) + } + } + + private func checkHeader(type: UInt8, length: Int) throws(ProtocolViolation) { + func name() -> String { String(format: "type 0x%02x", type) } + if helloSize == nil, type != MessageType.hello { + throw ProtocolViolation("\(name()) before HELLO") + } + let valid: Bool + switch type { + case MessageType.hello: + guard helloSize == nil else { throw ProtocolViolation("a second HELLO") } + valid = length == 5 || length == 4 + case MessageType.video: + valid = (4...WireProtocol.maxVideoPayload).contains(length) + case MessageType.progress: + valid = length == 1 + case MessageType.navigated: + valid = length == 0 + case MessageType.pageURL: + valid = (1...WireProtocol.maxPageURL).contains(length) + default: + valid = length <= WireProtocol.maxOtherPayload + } + guard valid else { throw ProtocolViolation("\(name()) with a \(length)-byte payload") } + } + + private func be16(at i: Int) -> UInt16 { + UInt16(buffer[i]) << 8 | UInt16(buffer[i + 1]) + } + + private func be32(at i: Int) -> UInt32 { + UInt32(be16(at: i)) << 16 | UInt32(be16(at: i + 2)) + } +} diff --git a/Sources/KnotchyCore/Time/DeadlineTimers.swift b/Sources/KnotchyCore/Time/DeadlineTimers.swift new file mode 100644 index 0000000..7aa83bb --- /dev/null +++ b/Sources/KnotchyCore/Time/DeadlineTimers.swift @@ -0,0 +1,67 @@ +/// Tests call `fireDue()` after advancing a `TestClock`; firing is idempotent for a given `now`. +@MainActor +final class DeadlineTimers { + var onFire: @MainActor (Key) -> Void = { _ in } + + private struct Entry { + var deadline: ContinuousClock.Instant + var order: Int + } + + private let clock: any KnotchyClock + private var entries: [Key: Entry] = [:] + private var nextOrder = 0 + private var driver: Task? + + init(clock: any KnotchyClock) { + self.clock = clock + } + + isolated deinit { + driver?.cancel() + } + + func set(_ key: Key, after duration: Duration) { + set(key, at: clock.now + duration) + } + + func set(_ key: Key, at deadline: ContinuousClock.Instant) { + nextOrder += 1 + entries[key] = Entry(deadline: deadline, order: nextOrder) + reschedule() + } + + func cancel(_ key: Key) { + guard entries.removeValue(forKey: key) != nil else { return } + reschedule() + } + + func deadline(of key: Key) -> ContinuousClock.Instant? { + entries[key]?.deadline + } + + /// Keys `onFire` re-arms at or before `now` fire in this same pass; doing that every time loops forever. + func fireDue() { + let now = clock.now + while let (key, _) = entries + .filter({ $0.value.deadline <= now }) + .min(by: { ($0.value.deadline, $0.value.order) < ($1.value.deadline, $1.value.order) }) { + entries[key] = nil + onFire(key) + } + reschedule() + } + + private func reschedule() { + driver?.cancel() + guard let next = entries.values.map(\.deadline).min() else { + driver = nil + return + } + driver = Task { [weak self, clock] in + do { try await clock.sleep(until: next) } catch { return } + guard !Task.isCancelled else { return } // Replaced while waking up. + self?.fireDue() + } + } +} diff --git a/Sources/KnotchyCore/Time/KnotchyClock.swift b/Sources/KnotchyCore/Time/KnotchyClock.swift new file mode 100644 index 0000000..4474842 --- /dev/null +++ b/Sources/KnotchyCore/Time/KnotchyClock.swift @@ -0,0 +1,15 @@ +/// `ContinuousClock` keeps counting while the Mac sleeps, so an expired idle countdown fires on wake. +public protocol KnotchyClock: Sendable { + var now: ContinuousClock.Instant { get } + func sleep(until deadline: ContinuousClock.Instant) async throws +} + +public struct SystemClock: KnotchyClock { + public init() {} + + public var now: ContinuousClock.Instant { .now } + + public func sleep(until deadline: ContinuousClock.Instant) async throws { + try await Task.sleep(until: deadline, clock: .continuous) + } +} diff --git a/Sources/knotchyctl/main.swift b/Sources/knotchyctl/main.swift new file mode 100644 index 0000000..3fa129b --- /dev/null +++ b/Sources/knotchyctl/main.swift @@ -0,0 +1,121 @@ +import CoreImage +import Foundation +import ImageIO +import KnotchyCore +import Synchronization +import UniformTypeIdentifiers + +// knotchyctl snapshot [--after ] [--click ,] +// [--proxy ://[user:pass@]host:port] [--wireguard ] [--allow-local-proxy] + +let arguments = Array(CommandLine.arguments.dropFirst()) + +func fail(_ message: String) -> Never { + FileHandle.standardError.write(Data("knotchyctl: \(message)\n".utf8)) + exit(1) +} + +func vmSources() -> URL? { + let repo = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent() + .deletingLastPathComponent().appendingPathComponent("vm") + return FileManager.default.fileExists(atPath: repo.appendingPathComponent("Containerfile").path) ? repo : nil +} + +func writePNG(_ buffer: CVPixelBuffer, to path: String) throws { + let image = CIImage(cvPixelBuffer: buffer) + guard let cgImage = CIContext().createCGImage(image, from: image.extent), + let destination = CGImageDestinationCreateWithURL(URL(fileURLWithPath: path) as CFURL, + UTType.png.identifier as CFString, 1, nil) + else { throw CocoaError(.fileWriteUnknown) } + CGImageDestinationAddImage(destination, cgImage, nil) + guard CGImageDestinationFinalize(destination) else { throw CocoaError(.fileWriteUnknown) } +} + +// Thrown rather than fail()ed after the VM starts, so the VM still gets destroyed. +struct SnapshotError: Error, CustomStringConvertible { + let description: String + init(_ description: String) { self.description = description } +} + +final class LatestFrame: Sendable { + private let frame = Mutex(nil) + func set(_ new: DecodedFrame) { frame.withLock { $0 = new } } + func get() -> DecodedFrame? { frame.withLock { $0 } } +} + +func snapshot(url: URL, out: String, after: Double, click: (x: UInt16, y: UInt16)?, route: NetworkRoute) async throws { + let backend = ContainerCLIBackend(vmSources: vmSources()) + backend.setNetworkRoute(route) + if arguments.contains("--allow-local-proxy") { backend.allowLocalProxy() } + // An outdated image would silently ignore the proxy or VPN route. + guard case .ready = await backend.preflight() else { fail("the VM image isn't ready; build it with scripts/build-vm.sh") } + let id = "knotchyctl-" + String(format: "%08x", UInt32.random(in: .min ... .max)) + let started = ContinuousClock.now + let vm = try await backend.start(id: id, url: url, renderSize: SessionController.renderSize, + blockCIDRs: HostNetworks.current()) + do { + let connection = try await StreamClient(renderSize: SessionController.renderSize).connect(socketPath: vm.socketPath) + let latest = LatestFrame() + connection.setFrameHandler { latest.set($0) } + var ready = false + for await event in connection.events { + if event == .contentReady { ready = true; break } + if case .ended(let reason) = event { throw CocoaError(.featureUnsupported, userInfo: ["reason": "\(reason)"]) } + } + guard ready else { throw SnapshotError("the stream ended before content was ready") } + print("content ready after \(ContinuousClock.now - started)") + let pages = Task { + for await event in connection.events { + if case .pageURL(let page) = event { print("page \(page.absoluteString)") } + } + } + defer { pages.cancel() } + if let click { + try await Task.sleep(for: .seconds(1)) + connection.send(.pointerMove(x: click.x, y: click.y, mods: [])) + connection.send(.pointerButton(.left, pressed: true, x: click.x, y: click.y, mods: .leftButton)) + connection.send(.pointerButton(.left, pressed: false, x: click.x, y: click.y, mods: [])) + } + try await Task.sleep(for: .seconds(after)) + guard let frame = latest.get() else { throw SnapshotError("no frame decoded") } + try writePNG(frame.pixelBuffer, to: out) + print("wrote \(out)") + connection.close() + } catch { + try? await backend.destroy(id: id) + throw error + } + try await backend.destroy(id: id) +} + +guard arguments.first == "snapshot", arguments.count >= 3, let url = URL(string: arguments[1]) else { + fail("usage: knotchyctl snapshot [--after ] [--click ,] [--proxy ] [--wireguard ]") +} +var route = NetworkRoute.direct +if let index = arguments.firstIndex(of: "--proxy") { + guard let proxy = URLComponents(string: arguments[safe: index + 1] ?? ""), let host = proxy.host, let port = proxy.port, + let kind = ProxySettings.Kind(rawValue: proxy.scheme ?? "") + else { fail("--proxy needs http://[user:pass@]host:port or socks5://…") } + route = .proxy(ProxySettings(kind: kind, host: host, port: port, + username: proxy.user ?? "", password: proxy.password ?? "")) +} +if let index = arguments.firstIndex(of: "--wireguard") { + guard let text = try? String(contentsOfFile: arguments[safe: index + 1] ?? "", encoding: .utf8) else { + fail("--wireguard needs a readable .conf file") + } + do { route = .wireGuard(try WireGuardConfig.parse(text)) } catch { fail("the WireGuard config: \(error)") } +} +let after = arguments.firstIndex(of: "--after").flatMap { Double(arguments[safe: $0 + 1] ?? "") } ?? 3 +let click: (x: UInt16, y: UInt16)? = arguments.firstIndex(of: "--click").flatMap { index in + let parts = (arguments[safe: index + 1] ?? "").split(separator: ",").compactMap { UInt16($0) } + return parts.count == 2 ? (parts[0], parts[1]) : nil +} +do { + try await snapshot(url: url, out: arguments[2], after: after, click: click, route: route) +} catch { + fail("\(error)") +} + +extension Array { + subscript(safe index: Int) -> Element? { indices.contains(index) ? self[index] : nil } +} diff --git a/Tests/KnotchyCoreTests/BackendChainTests.swift b/Tests/KnotchyCoreTests/BackendChainTests.swift new file mode 100644 index 0000000..914f717 --- /dev/null +++ b/Tests/KnotchyCoreTests/BackendChainTests.swift @@ -0,0 +1,66 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +struct BackendChainTests { + @Test func runsJobsOneAtATimeInOrder() async { + let chain = BackendChain() + var log: [String] = [] + for name in ["a", "b", "c"] { + chain.enqueue { + log.append("\(name) begins") + await Task.yield() + log.append("\(name) ends") + } + } + await chain.waitUntilIdle() + #expect(log == ["a begins", "a ends", "b begins", "b ends", "c begins", "c ends"]) + } + + @Test func waitsForJobsEnqueuedByJobs() async { + let chain = BackendChain() + var log: [String] = [] + chain.enqueue { + log.append("outer") + chain.enqueue { + await Task.yield() // Still running when the outer job's Task finishes. + log.append("inner") + } + } + await chain.waitUntilIdle() + #expect(log == ["outer", "inner"]) + } + + @Test func closingSkipsJobsThatHaveNotStarted() async { + let chain = BackendChain() + var log: [String] = [] + chain.enqueue { + log.append("running") + chain.close() + } + chain.enqueue { log.append("queued") } + await chain.waitUntilIdle() + chain.enqueue { log.append("after close") } + await chain.waitUntilIdle() + #expect(log == ["running"]) + } + + @Test func liveIDsAreASnapshot() { + let ids = LiveIDs() + ids.insert("knotchy-00000001") + ids.insert("knotchy-00000002") + ids.remove("knotchy-00000001") + #expect(ids.snapshot == ["knotchy-00000002"]) + } + + @Test nonisolated func liveIDsCanBeUsedFromManyThreadsAtOnce() { + let ids = LiveIDs() + DispatchQueue.concurrentPerform(iterations: 1000) { i in + ids.insert("knotchy-\(i)") + _ = ids.snapshot + if i.isMultiple(of: 2) { ids.remove("knotchy-\(i)") } + } + #expect(ids.snapshot.count == 500) + } +} diff --git a/Tests/KnotchyCoreTests/CLIRunnerTests.swift b/Tests/KnotchyCoreTests/CLIRunnerTests.swift new file mode 100644 index 0000000..aa08525 --- /dev/null +++ b/Tests/KnotchyCoreTests/CLIRunnerTests.swift @@ -0,0 +1,19 @@ +import Foundation +import Testing +@testable import KnotchyCore + +struct CLIRunnerTests { + @Test func findsARuntimeInstalledAfterLaunch() throws { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent("cli-\(UUID().uuidString)") + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: directory) } + let tool = directory.appendingPathComponent("container") + let runner = CLIRunner(searchPaths: [tool.path]) + #expect(runner.executable == nil) + + try "#!/bin/sh\necho container CLI version 1.4.1\n".write(to: tool, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: tool.path) + #expect(runner.executable == tool.path) + #expect(try runner.runBlocking(["--version"], timeout: 5).text.contains("1.4.1")) + } +} diff --git a/Tests/KnotchyCoreTests/ClockTests.swift b/Tests/KnotchyCoreTests/ClockTests.swift new file mode 100644 index 0000000..1562ed2 --- /dev/null +++ b/Tests/KnotchyCoreTests/ClockTests.swift @@ -0,0 +1,156 @@ +import Testing +@testable import KnotchyCore + +@Suite(.timeLimit(.minutes(1))) +struct TestClockTests { + @Test func sleepReturnsOnlyOnceTimeIsAdvancedPastTheDeadline() async throws { + let clock = TestClock() + let deadline = clock.now + .seconds(10) + let sleeper = Task { try await clock.sleep(until: deadline) } + await waitUntil("the task sleeps") { clock.sleeperCount == 1 } + + clock.advance(by: .seconds(9)) + #expect(clock.sleeperCount == 1) + + clock.advance(by: .seconds(1)) + try await sleeper.value + #expect(clock.sleeperCount == 0) + } + + @Test func sleepingUntilNowOrThePastReturnsImmediately() async throws { + let clock = TestClock() + try await clock.sleep(until: clock.now) + try await clock.sleep(until: clock.now - .seconds(1)) + } + + @MainActor + @Test func aSleepInAnAlreadyCancelledTaskThrows() async { + @MainActor final class Outcome { + var error: (any Error)? + var finished = false + } + let clock = TestClock() + let outcome = Outcome() + Task { @MainActor in + withUnsafeCurrentTask { $0?.cancel() } + do { try await clock.sleep(until: clock.now + .seconds(10)) } catch { outcome.error = error } + outcome.finished = true + } + // Bounded: a sleep that wrongly registers would never return. + await waitUntil("the sleep ends") { outcome.finished } + #expect(outcome.error is CancellationError) + #expect(clock.sleeperCount == 0) + } + + @Test func cancellingASleeperThrowsAndForgetsIt() async { + let clock = TestClock() + let sleeper = Task { try await clock.sleep(until: clock.now + .seconds(10)) } + await waitUntil("the task sleeps") { clock.sleeperCount == 1 } + + sleeper.cancel() + await #expect(throws: CancellationError.self) { try await sleeper.value } + #expect(clock.sleeperCount == 0) + } +} + +@MainActor +struct DeadlineTimersTests { + @Test func firesDueDeadlinesInOrder() { + let clock = TestClock() + let timers = DeadlineTimers(clock: clock) + var fired: [String] = [] + timers.onFire = { fired.append($0) } + + // Seven keys share a deadline, so a lost tie-break shows up nearly every run. + let ties = (1...6).map { "tie\($0)" } + timers.set("late", after: .seconds(2)) + timers.set("early", after: .seconds(1)) + for key in ties { timers.set(key, after: .seconds(2)) } + clock.advance(by: .milliseconds(999)) + timers.fireDue() + #expect(fired == []) + + clock.advance(by: .seconds(2)) + timers.fireDue() + #expect(fired == ["early", "late"] + ties) + + timers.fireDue() + #expect(fired == ["early", "late"] + ties) + } + + @Test func settingAKeyAgainReplacesItsDeadline() { + let clock = TestClock() + let timers = DeadlineTimers(clock: clock) + var fired: [String] = [] + timers.onFire = { fired.append($0) } + + timers.set("a", after: .seconds(1)) + timers.set("a", after: .seconds(5)) + #expect(timers.deadline(of: "a") == clock.now + .seconds(5)) + clock.advance(by: .seconds(4)) + timers.fireDue() + #expect(fired == []) + } + + @Test func cancelledDeadlinesNeverFire() { + let clock = TestClock() + let timers = DeadlineTimers(clock: clock) + var fired: [String] = [] + timers.onFire = { fired.append($0) } + + timers.set("a", after: .seconds(1)) + timers.cancel("a") + clock.advance(by: .seconds(2)) + timers.fireDue() + #expect(fired == []) + #expect(timers.deadline(of: "a") == nil) + } + + @Test func onFireMaySetAndCancelTimers() { + let clock = TestClock() + let timers = DeadlineTimers(clock: clock) + var fired: [String] = [] + timers.onFire = { [unowned timers] key in + fired.append(key) + if key == "a" { + timers.cancel("b") + timers.set("c", after: .zero) + timers.set("d", after: .seconds(1)) + } + } + timers.set("a", after: .seconds(1)) + timers.set("b", after: .seconds(1)) + clock.advance(by: .seconds(1)) + timers.fireDue() + #expect(fired == ["a", "c"]) + #expect(timers.deadline(of: "d") == clock.now + .seconds(1)) + } + + @Test func theDriverFiresEveryTimerInDeadlineOrder() async { + let clock = TestClock() + let timers = DeadlineTimers(clock: clock) + var fired: [String] = [] + timers.onFire = { fired.append($0) } + + timers.set("b", after: .seconds(2)) + timers.set("a", after: .seconds(1)) + await waitUntil("the driver sleeps") { clock.sleeperCount == 1 } + clock.advance(by: .seconds(1)) + await waitUntil("the driver fires a") { fired == ["a"] } + await waitUntil("the driver sleeps again") { clock.sleeperCount == 1 } + clock.advance(by: .seconds(1)) + await waitUntil("the driver fires b") { fired == ["a", "b"] } + } + + @Test func theDriverFiresWithoutHelp() async { + let clock = TestClock() + let timers = DeadlineTimers(clock: clock) + var fired: [String] = [] + timers.onFire = { fired.append($0) } + + timers.set("a", after: .seconds(1)) + await waitUntil("the driver sleeps") { clock.sleeperCount == 1 } + clock.advance(by: .seconds(1)) + await waitUntil("the driver fires") { fired == ["a"] } + } +} diff --git a/Tests/KnotchyCoreTests/ContainerCLIBackendRuntimeTests.swift b/Tests/KnotchyCoreTests/ContainerCLIBackendRuntimeTests.swift new file mode 100644 index 0000000..36d5b69 --- /dev/null +++ b/Tests/KnotchyCoreTests/ContainerCLIBackendRuntimeTests.swift @@ -0,0 +1,37 @@ +import Foundation +import Testing +@testable import KnotchyCore + +struct ContainerCLIBackendRuntimeTests { + /// A fake `container` that answers only `--version`. + private func fakeCLI(printing line: String) throws -> (CLIRunner, URL) { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent("cli-\(UUID().uuidString)") + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + let tool = directory.appendingPathComponent("container") + let script = "#!/bin/sh\nif [ \"$1\" = --version ]; then echo '\(line)'; exit 0; fi\nexit 1\n" + try script.write(to: tool, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: tool.path) + return (CLIRunner(searchPaths: [tool.path]), directory) + } + + @Test func anOldRuntimeIsFlaggedBeforeAnythingElse() async throws { + let (cli, directory) = try fakeCLI(printing: "container CLI version 1.3.0 (build: release)") + defer { try? FileManager.default.removeItem(at: directory) } + let backend = ContainerCLIBackend(vmSources: nil, cli: cli) + #expect(await backend.runtimeStatus() == .outdated("1.3.0")) + #expect(await backend.preflight() == .issue(.cliOutdated(found: "1.3.0"))) + } + + @Test func aMissingRuntimeIsReported() async { + let backend = ContainerCLIBackend(vmSources: nil, cli: CLIRunner(searchPaths: ["/nonexistent/container"])) + #expect(await backend.runtimeStatus() == .missing) + #expect(await backend.preflight() == .issue(.cliMissing)) + } + + @Test func anOldRuntimeHasNoAutomaticFix() async throws { + let backend = ContainerCLIBackend(vmSources: nil, cli: CLIRunner(searchPaths: [])) + await #expect(throws: VMBackendError.self) { + try await backend.fix(.cliOutdated(found: "1.3.0")) { _ in } + } + } +} diff --git a/Tests/KnotchyCoreTests/ContainerRuntimeTests.swift b/Tests/KnotchyCoreTests/ContainerRuntimeTests.swift new file mode 100644 index 0000000..437153e --- /dev/null +++ b/Tests/KnotchyCoreTests/ContainerRuntimeTests.swift @@ -0,0 +1,34 @@ +import Testing +@testable import KnotchyCore + +struct ContainerRuntimeTests { + @Test func readsTheVersionFromTheCLIOutput() { + #expect(ContainerRuntime.version(in: "container CLI version 1.4.1 (build: release, commit: 9a8917c)\n") == "1.4.1") + #expect(ContainerRuntime.version(in: "warning: x\ncontainer CLI version 2.0 (build: debug)") == "2.0") + #expect(ContainerRuntime.version(in: "container version 1.4.1") == nil) + } + + @Test func comparesVersionsNumerically() { + #expect(ContainerRuntime.isOlder("1.3.9", than: "1.4.0")) + #expect(ContainerRuntime.isOlder("1.4", than: "1.4.1")) + #expect(!ContainerRuntime.isOlder("1.10.0", than: "1.4.0")) + #expect(!ContainerRuntime.isOlder("1.4.0", than: "1.4")) + } + + @Test func judgesAnInstalledRuntime() { + #expect(ContainerRuntime.status(versionOutput: nil) == .missing) + #expect(ContainerRuntime.status(versionOutput: "container CLI version 1.3.1 (build: release)") == .outdated("1.3.1")) + #expect(ContainerRuntime.status(versionOutput: "container CLI version 1.4.1 (build: release)") == .current("1.4.1")) + #expect(ContainerRuntime.status(versionOutput: "container CLI version 9.0.0") == .current("9.0.0")) + } + + @Test func unreadableOutputNeverTriggersAnUpdate() { + #expect(ContainerRuntime.status(versionOutput: "container 3 (a new format)") == .current(nil)) + #expect(ContainerRuntime.status(versionOutput: "") == .current(nil)) + } + + @Test func thePinnedReleaseIsNewEnough() { + #expect(!ContainerRuntime.isOlder(ContainerRuntime.pinned.version, than: ContainerRuntime.minimumVersion)) + #expect(ContainerRuntime.pinned.sha256.count == 64) + } +} diff --git a/Tests/KnotchyCoreTests/ContentReadinessTests.swift b/Tests/KnotchyCoreTests/ContentReadinessTests.swift new file mode 100644 index 0000000..334abcf --- /dev/null +++ b/Tests/KnotchyCoreTests/ContentReadinessTests.swift @@ -0,0 +1,150 @@ +import Testing +@testable import KnotchyCore + +struct ContentReadinessTests { + private let t0 = ContinuousClock.now + + @Test func ignoresBootstrapFramesAndProgress() { + // A bootstrap 100 after NAVIGATED never gets here: the agent's progress gate drops it. + var r = ContentReadiness() + r.hello(at: t0) + r.frame(isUniform: false) + r.progress(100) + #expect(!r.isReady) + } + + @Test func readyOnTheFirstNonUniformFrameAfterNavigated() { + var r = ContentReadiness() + r.hello(at: t0) + r.navigated(at: t0) + r.frame(isUniform: true) + #expect(!r.isReady) + r.frame(isUniform: false) + #expect(r.isReady) + } + + @Test func readyOnProgress100AfterNavigated() { + var r = ContentReadiness() + r.hello(at: t0) + r.navigated(at: t0) + r.progress(40) + #expect(!r.isReady) + r.progress(100) + #expect(r.isReady) + } + + @Test func readyFifteenSecondsAfterNavigated() { + var r = ContentReadiness() + r.hello(at: t0) + r.navigated(at: t0 + .seconds(1)) + #expect(r.nextDeadline == t0 + .seconds(16)) + r.tick(now: t0 + .seconds(16) - .milliseconds(1)) + #expect(!r.isReady) + r.tick(now: t0 + .seconds(16)) + #expect(r.isReady) + #expect(r.nextDeadline == nil) + } + + @Test func readyTwentyFiveSecondsAfterHelloIfNavigatedNeverComes() { + var r = ContentReadiness() + #expect(r.nextDeadline == nil) + r.hello(at: t0) + #expect(r.nextDeadline == t0 + .seconds(25)) + r.tick(now: t0 + .seconds(24)) + #expect(!r.isReady) + r.tick(now: t0 + .seconds(25)) + #expect(r.isReady) + } + + @Test func theHelloBackstopStillAppliesAfterALateNavigated() { + var r = ContentReadiness() + r.hello(at: t0) + r.navigated(at: t0 + .seconds(20)) + #expect(r.nextDeadline == t0 + .seconds(25)) + r.tick(now: t0 + .seconds(25)) + #expect(r.isReady) + } + + @Test func aLateViewerIsCaughtUpByTheReplay() { + var loaded = ContentReadiness() + loaded.hello(at: t0) + loaded.navigated(at: t0) + loaded.progress(100) + #expect(loaded.isReady) + + var loading = ContentReadiness() + loading.hello(at: t0) + loading.navigated(at: t0) + loading.progress(40) + #expect(!loading.isReady) + loading.frame(isUniform: false) + #expect(loading.isReady) + } + + @Test func keepsTheFirstHelloAndNavigated() { + // A repeated message can't push the timeouts back. + var r = ContentReadiness() + r.hello(at: t0) + r.hello(at: t0 + .seconds(10)) + #expect(r.nextDeadline == t0 + .seconds(25)) + r.navigated(at: t0 + .seconds(5)) + r.navigated(at: t0 + .seconds(9)) + #expect(r.nextDeadline == t0 + .seconds(20)) + } + + @Test func staysReadyOnceReady() { + var r = ContentReadiness() + r.hello(at: t0) + r.navigated(at: t0) + r.frame(isUniform: false) + r.frame(isUniform: true) + r.progress(10) + #expect(r.isReady) + } +} + +struct FrameSamplerTests { + private func frame(width: Int, height: Int, bytesPerRow: Int) -> [UInt8] { + var bytes = [UInt8](repeating: 0, count: height * bytesPerRow) + for y in 0.. [UInt8] { + (0.. Data { + nals.reduce(into: Data()) { data, nal in + data.appendBE32(UInt32(nal.count)) + data.append(contentsOf: nal) + } + } + + @Test func splitsOnThreeAndFourByteStartCodes() { + let stream = Data(four + aud + three + sps + four + pps + three + idr) + #expect(AnnexB.nalUnits(in: stream) == [aud, sps, pps, idr].map { Data($0) }) + } + + @Test func ignoresLeadingBytesAndTrailingZeros() { + let stream = Data([0xFF, 0x00] + three + slice + [0, 0] + four + slice + [0]) + #expect(AnnexB.nalUnits(in: stream) == [Data(slice), Data(slice)]) + } + + @Test func handlesStreamsWithoutStartCodes() { + #expect(AnnexB.nalUnits(in: Data()) == []) + #expect(AnnexB.nalUnits(in: Data([0x65, 0x88])) == []) + #expect(AnnexB.nalUnits(in: Data(four)) == []) + #expect(AnnexB.nalUnits(in: Data(three + idr + three)) == [Data(idr)]) + } + + @Test func turnsAnIDRAccessUnitIntoAnAVCCSampleWithNewParameterSets() { + var parser = H264Parser() + let unit = parser.parse(Data(four + aud + four + sps + four + pps + four + sei + four + idr + four + idr)) + #expect(unit.sample == avcc(idr, idr)) + #expect(unit.isIDR) + #expect(unit.newParameterSets == ParameterSets(sps: Data(sps), pps: Data(pps))) + #expect(parser.parameterSets == unit.newParameterSets) + } + + @Test func repeatedParameterSetsAreNotAChange() { + var parser = H264Parser() + _ = parser.parse(Data(four + sps + four + pps + four + idr)) + let again = parser.parse(Data(four + aud + four + sps + four + pps + four + idr)) + #expect(again.newParameterSets == nil) + let delta = parser.parse(Data(four + aud + four + slice)) + #expect(delta.sample == avcc(slice)) + #expect(!delta.isIDR) + #expect(delta.newParameterSets == nil) + // Repeated with every IDR: not a change, so the decoder isn't rebuilt. + let nextIDR = parser.parse(Data(four + aud + four + sps + four + pps + four + idr)) + #expect(nextIDR.newParameterSets == nil) + } + + @Test func aChangedSPSOrPPSIsANewFormat() { + var parser = H264Parser() + _ = parser.parse(Data(four + sps + four + pps + four + idr)) + let changedSPS = parser.parse(Data(four + sps2 + four + pps + four + idr)) + #expect(changedSPS.newParameterSets == ParameterSets(sps: Data(sps2), pps: Data(pps))) + let changedPPS = parser.parse(Data(four + sps2 + four + pps2 + four + idr)) + #expect(changedPPS.newParameterSets == ParameterSets(sps: Data(sps2), pps: Data(pps2))) + } + + @Test func aHalfPairIsNeverAFormat() { + var parser = H264Parser() + let spsOnly = parser.parse(Data(four + sps + four + idr)) + #expect(spsOnly.newParameterSets == nil) + #expect(parser.parameterSets == nil) + + _ = parser.parse(Data(four + sps + four + pps + four + idr)) + let lone = parser.parse(Data(four + sps2 + four + slice)) + #expect(lone.newParameterSets == nil) + #expect(parser.parameterSets == ParameterSets(sps: Data(sps), pps: Data(pps))) + } + + @Test func onlyValidSliceUnitsAreDecoded() { + var parser = H264Parser() + // Type 21, a 3D extension slice. + let extensionSlice = parser.parse(Data(four + [0x75, 0x88])) + #expect(extensionSlice.sample.isEmpty) + #expect(!extensionSlice.isIDR) + // Forbidden bit set. + let forbidden = parser.parse(Data(four + [0xE5, 0x88] + four + slice)) + #expect(forbidden.sample == avcc(slice)) + #expect(!forbidden.isIDR) + #expect(parser.parse(Data(four + idr + four + slice)).isIDR) + let long = [UInt8(0x41)] + [UInt8](repeating: 0x9A, count: 299) + #expect(parser.parse(Data(four + long)).sample.prefix(4) == Data([0, 0, 0x01, 0x2C])) + } + + @Test func slicesBeforeAnyParameterSetsHaveNoFormat() { + var parser = H264Parser() + let unit = parser.parse(Data(four + slice)) + #expect(unit.sample == avcc(slice)) + #expect(unit.newParameterSets == nil) + #expect(parser.parameterSets == nil) + } +} diff --git a/Tests/KnotchyCoreTests/HostChecksTests.swift b/Tests/KnotchyCoreTests/HostChecksTests.swift new file mode 100644 index 0000000..f3ab10c --- /dev/null +++ b/Tests/KnotchyCoreTests/HostChecksTests.swift @@ -0,0 +1,35 @@ +import Testing +@testable import KnotchyCore + +struct HostChecksTests { + @Test func unsupportedMacsAlwaysSeeTheWindow() { + #expect(SetupLaunchRule.shouldOpen(hostSupported: false, setupCompleted: true, runtime: .current("1.4.1"))) + } + + @Test func theFirstLaunchAlwaysSeesTheWindow() { + #expect(SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: false, runtime: .current("1.4.1"))) + } + + @Test func anUnsupportedMacOrAFirstLaunchOpensItWhateverTheRuntime() { + #expect(SetupLaunchRule.shouldOpen(hostSupported: false, setupCompleted: false, runtime: .missing)) + #expect(SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: false, runtime: .missing)) + #expect(SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: false, runtime: .outdated("1.3.0"))) + } + + @Test func laterOnlyAMissingOrOldRuntimeOpensIt() { + #expect(SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: true, runtime: .missing)) + #expect(SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: true, runtime: .outdated("1.3.0"))) + #expect(!SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: true, runtime: .current("1.4.1"))) + #expect(!SetupLaunchRule.shouldOpen(hostSupported: true, setupCompleted: true, runtime: .current(nil))) + } + + @MainActor @Test func thisMacCanRunVirtualMachines() { + #expect(SystemHost.unsupported() == nil) + } + + @Test func factsIncludeTheSystemAndFreeSpace() async { + let facts = await SystemHost().facts() + #expect(facts.osVersion.hasPrefix("macOS ")) + #expect((facts.bytesFree ?? 0) > 0) + } +} diff --git a/Tests/KnotchyCoreTests/LinkIntakeTests.swift b/Tests/KnotchyCoreTests/LinkIntakeTests.swift new file mode 100644 index 0000000..3bf53d7 --- /dev/null +++ b/Tests/KnotchyCoreTests/LinkIntakeTests.swift @@ -0,0 +1,154 @@ +import Foundation +import Testing +@testable import KnotchyCore + +struct LinkIntakeTests { + private func extract(_ text: String) -> String? { + LinkIntake.extractURL(from: text)?.absoluteString + } + + @Test func findsAURLInsideProse() { + #expect(extract("Your parcel is waiting at https://example.com/track?id=42 today") == "https://example.com/track?id=42") + } + + @Test func acceptsABareURL() { + #expect(extract("http://example.com") == "http://example.com") + } + + @Test func stripsTrailingPunctuation() { + #expect(extract("Go to https://example.com/page.") == "https://example.com/page") + #expect(extract("**https://example.com/page**") == "https://example.com/page") + #expect(extract("https://example.com/search?q=*") == "https://example.com/search?q=*") + #expect(extract("Really? https://example.com/page!?") == "https://example.com/page") + #expect(extract("'https://example.com/a'") == "https://example.com/a") + #expect(extract("\"https://example.com/a\",") == "https://example.com/a") + #expect(extract("see https://example.com/a;") == "https://example.com/a") + } + + @Test func keepsBalancedBracketsAndStripsUnbalancedOnes() { + #expect(extract("https://en.wikipedia.org/wiki/Foo_(bar)") == "https://en.wikipedia.org/wiki/Foo_(bar)") + #expect(extract("(see https://en.wikipedia.org/wiki/Foo_(bar)).") == "https://en.wikipedia.org/wiki/Foo_(bar)") + #expect(extract("(see https://example.com/a)") == "https://example.com/a") + #expect(extract("(https://example.com/a)and more") == "https://example.com/a") + #expect(extract("(https://en.wikipedia.org/wiki/Foo_(bar))") == "https://en.wikipedia.org/wiki/Foo_(bar)") + #expect(extract("[link](https://example.com/a)") == "https://example.com/a") + #expect(extract("https://example.com/a]") == "https://example.com/a") + #expect(extract("{https://example.com/a}") == "https://example.com/a") + } + + @Test func handlesMarkdownLinks() { + #expect(extract("[https://example.com/a](https://example.com/a)") == "https://example.com/a") + #expect(extract("[https://example.com/a](https://example.org/b)") == "https://example.com/a") + #expect(extract("[httpbin](https://httpbin.org)") == "https://httpbin.org") + #expect(extract("See [XMLHttpRequest](https://developer.mozilla.org/docs/XMLHttpRequest).") + == "https://developer.mozilla.org/docs/XMLHttpRequest") + #expect(extract("**[Example](https://example.com)**") == "https://example.com") + #expect(extract("*[Example](https://example.com/a)*") == "https://example.com/a") + #expect(extract("Get it: **[httpbin](https://httpbin.org)**.") == "https://httpbin.org") + #expect(extract("[x](https://example.com)and more") == "https://example.com") + #expect(extract("[w](https://en.wikipedia.org/wiki/Foo_(bar))**") == "https://en.wikipedia.org/wiki/Foo_(bar)") + } + + @Test func smartQuotesAndCJKPunctuationEndALink() { + #expect(extract("\u{201C}https://example.com\u{201D}") == "https://example.com") + #expect(extract("\u{201C}https://example.com/page\u{201D}") == "https://example.com/page") + #expect(extract("\u{00AB}https://example.com/page\u{00BB}") == "https://example.com/page") + #expect(extract("请访问https://example.com。谢谢") == "https://example.com") + #expect(extract("请访问https://example.com.谢谢") == "https://example.com") + #expect(extract("https://example.com。テスト") == "https://example.com") + #expect(extract("见(https://example.com/a)") == "https://example.com/a") + #expect(extract("「https://example.com/a」") == "https://example.com/a") + } + + @Test func unwrapsAngleBrackets() { + #expect(extract("Open now") == "https://example.com/a") + } + + @Test func usesTheFirstWebURL() { + #expect(extract("ftp://files.example.com then https://b.example.com and https://c.example.com") == "https://b.example.com") + } + + @Test func textWithoutAWebLinkHasNoLink() { + #expect(extract("javascript:alert(1)") == nil) + #expect(extract("file:///etc/passwd") == nil) + #expect(extract("mailto:someone@example.com") == nil) + #expect(extract("ftp://files.example.com") == nil) + #expect(extract("httpx://example.com") == nil) + } + + @Test func rejectsEmptyAndHostlessInput() { + #expect(extract("") == nil) + #expect(extract(" \n") == nil) + #expect(extract("no links here, just httpbin and https://") == nil) + } + + @Test func acceptsUppercaseSchemes() { + #expect(extract("HTTPS://EXAMPLE.COM/") == "HTTPS://EXAMPLE.COM/") + } + + @Test func acceptsLinksUpToTheLengthLimit() { + let base = "https://example.com/" + let longest = base + String(repeating: "a", count: LinkIntake.maxURLBytes - base.utf8.count) + #expect(extract(longest) == longest) + #expect(extract("Go to " + longest + ".") == longest) // Measured after trimming. + #expect(extract(longest + "a") == nil) + // Measured as encoded: each `{` becomes `%7B`. + #expect(extract(base + String(repeating: "{", count: 6000)) == nil) + } + + @Test func skipsAnOverlongTokenWholeIncludingLinksNestedInIt() { + let overlong = "https://example.com/" + String(repeating: "a", count: LinkIntake.maxURLBytes) + #expect(extract(overlong + "https://nested.example.com/") == nil) + #expect(extract(overlong + " https://next.example.com/") == "https://next.example.com/") + } + + @Test(arguments: [ + String(repeating: "http", count: 256 * 1024), + String(repeating: "http ", count: 100_000), + String(repeating: "http" + String(repeating: ")", count: 16_000) + " ", count: 16), + String(repeating: "http](", count: 200_000), + ]) + func hostileTextIsScannedInLinearTime(_ hostile: String) { + // Quadratic versions took 10 s to minutes on these; linear takes well under a second. + let start = ContinuousClock.now + #expect(extract(hostile) == nil) + #expect(ContinuousClock.now - start < .seconds(5)) + } + + @Test func onlyLinksStartingNearTheStartOfHugeTextCount() { + let filler = String(repeating: "x", count: LinkIntake.maxScannedCharacters) + #expect(extract(filler + " https://example.com/") == nil) + #expect(extract("https://example.com/ " + filler) == "https://example.com/") + } + + @Test func aLinkThatStartsBeforeTheScanLimitIsReadWhole() { + let link = "https://example.com/very/long/path?token=abcdef" + let filler = String(repeating: "x", count: LinkIntake.maxScannedCharacters - 30) + #expect(extract(filler + " " + link) == link) + } + + @Test func findsTheLinkBehindSelectedHyperlinkText() { + func extract(html: String) -> String? { LinkIntake.extractURL(fromHTML: html)?.absoluteString } + #expect(extract(html: #"Amflow's latest e-bikes"#) + == "https://www.theverge.com/transportation/909060/amflows-latest-e-bikes") + #expect(extract(html: #"Verify"#) + == "https://www.google.com/url?q=https://example.com/&sa=D") + #expect(extract(html: "x") == "https://example.com/a") + #expect(extract(html: #"mail web"#) == "https://example.org/") + #expect(extract(html: "

see https://example.com/page.

") == "https://example.com/page") + #expect(extract(html: "

no links

") == nil) + } + + @Test func validatesURLObjects() throws { + let web = try #require(URL(string: "https://example.com/x")) + #expect(LinkIntake.validate(web) == web) + #expect(LinkIntake.validate(try #require(URL(string: "HTTP://example.com/"))) != nil) + #expect(LinkIntake.validate(URL(filePath: "/etc/passwd")) == nil) + #expect(LinkIntake.validate(try #require(URL(string: "ftp://files.example.com/"))) == nil) + #expect(LinkIntake.validate(try #require(URL(string: "mailto:a@example.com"))) == nil) + #expect(LinkIntake.validate(try #require(URL(string: "https:///no-host"))) == nil) + #expect(LinkIntake.validate(try #require(URL(string: "http://:80/"))) == nil) + let long = "https://example.com/" + String(repeating: "a", count: LinkIntake.maxURLBytes) + #expect(LinkIntake.validate(try #require(URL(string: long))) == nil) + } +} diff --git a/Tests/KnotchyCoreTests/NetworkRouteTests.swift b/Tests/KnotchyCoreTests/NetworkRouteTests.swift new file mode 100644 index 0000000..09be1fb --- /dev/null +++ b/Tests/KnotchyCoreTests/NetworkRouteTests.swift @@ -0,0 +1,145 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@Suite struct NetworkRouteTests { + static let key = Data(repeating: 7, count: 32).base64EncodedString() + static let otherKey = Data(repeating: 9, count: 32).base64EncodedString() + + static let mullvadStyle = """ + [Interface] + # Device: Quick Fox + PrivateKey = \(key) + Address = 10.64.12.34/32,fc00:bbbb:bbbb:bb01::1:c21/128 + DNS = 10.64.0.1 + + [Peer] + PublicKey = \(otherKey) + AllowedIPs = 0.0.0.0/0,::0/0 + Endpoint = se-got-wg-001.relays.mullvad.net:51820 + """ + + @Test func parsesAProviderConfig() throws { + let config = try WireGuardConfig.parse(Self.mullvadStyle) + #expect(config.privateKey == Self.key) + #expect(config.addresses == ["10.64.12.34/32"]) + #expect(config.dns == ["10.64.0.1"]) + #expect(config.peerPublicKey == Self.otherKey) + #expect(config.endpointHost == "se-got-wg-001.relays.mullvad.net") + #expect(config.endpointPort == 51820) + #expect(config.mtu == nil) + #expect(config.persistentKeepalive == nil) + } + + @Test func keysAreCaseInsensitiveAndRepeatsJoin() throws { + let text = """ + [interface] + privatekey=\(Self.key) + Address = 10.2.0.2 + Address = 10.2.0.3/32 + DNS = 10.2.0.1, 1.1.1.1, example.search.domain + MTU = 1380 + [PEER] + publickey = \(Self.otherKey) + PresharedKey = \(Self.key) + endpoint = 203.0.113.9:443 + PersistentKeepalive = 15 + """ + let config = try WireGuardConfig.parse(text) + #expect(config.addresses == ["10.2.0.2/32", "10.2.0.3/32"]) + #expect(config.dns == ["10.2.0.1", "1.1.1.1"]) + #expect(config.mtu == 1380) + #expect(config.presharedKey == Self.key) + #expect(config.persistentKeepalive == 15) + } + + @Test func rejectsWhatTheVMCannotUse() { + func parse(_ text: String) -> WireGuardConfig.ParseError? { + do { _ = try WireGuardConfig.parse(text); return nil } catch { return error } + } + let peer = "[Peer]\nPublicKey = \(Self.otherKey)\nEndpoint = 1.2.3.4:51820\n" + #expect(parse("[Interface]\nAddress = 10.0.0.2/32\n" + peer) == .missing("PrivateKey")) + #expect(parse("[Interface]\nPrivateKey = nope\nAddress = 10.0.0.2/32\n" + peer) == .invalid("PrivateKey")) + #expect(parse("[Interface]\nPrivateKey = \(Self.key)\nAddress = fd00::2/128\n" + peer) == .missing("an IPv4 Address")) + #expect(parse("[Interface]\nPrivateKey = \(Self.key)\nAddress = 10.0.0.2/32\n") == .missing("[Peer]")) + #expect(parse("[Interface]\nPrivateKey = \(Self.key)\nAddress = 10.0.0.2/32\n" + peer + peer) == .multiplePeers) + let v6Endpoint = "[Peer]\nPublicKey = \(Self.otherKey)\nEndpoint = [2001:db8::1]:51820\n" + #expect(parse("[Interface]\nPrivateKey = \(Self.key)\nAddress = 10.0.0.2/32\n" + v6Endpoint) != nil) + let shellEndpoint = "[Peer]\nPublicKey = \(Self.otherKey)\nEndpoint = $(reboot):51820\n" + #expect(parse("[Interface]\nPrivateKey = \(Self.key)\nAddress = 10.0.0.2/32\n" + shellEndpoint) == .invalid("Endpoint")) + #expect(parse("[Interface]\nPrivateKey = \(Self.key)\nAddress = 10.0.0.2/32\nPostUp = rm -rf /\n" + peer) == nil) + } + + @Test func setconfForcesAFullTunnelAndAKeepalive() throws { + let config = try WireGuardConfig.parse(Self.mullvadStyle) + let setconf = config.setconf(endpointAddress: "185.213.154.66") + #expect(setconf == """ + [Interface] + PrivateKey = \(Self.key) + + [Peer] + PublicKey = \(Self.otherKey) + Endpoint = 185.213.154.66:51820 + AllowedIPs = 0.0.0.0/0 + PersistentKeepalive = 25 + + """) + } + + @Test func proxyURLsEncodeCredentialsAndUseTheAddress() { + let proxy = ProxySettings(kind: .socks5, host: "proxy.example", port: 1080, username: "me@home", password: "p:w/d@!") + #expect(proxy.url(address: "203.0.113.7") == "socks5://me%40home:p%3Aw%2Fd%40%21@203.0.113.7:1080") + #expect(ProxySettings(kind: .http, host: "h", port: 8080).url(address: "10.0.0.5") == "http://10.0.0.5:8080") + #expect(ProxySettings(kind: .http, host: "h", port: 8080, username: "u").url(address: "10.0.0.5") == "http://u@10.0.0.5:8080") + } + + @Test func launchKeepsSecretsOutOfTheEnvironment() throws { + var asked: [String] = [] + let resolve = { (host: String) -> String in asked.append(host); return "198.51.100.4" } + #expect(try NetworkRoute.direct.launch(resolve: resolve) == RouteLaunch(environment: ["KNOTCHY_ROUTE=direct"], secrets: [])) + + let proxy = NetworkRoute.proxy(ProxySettings(kind: .http, host: "proxy.corp", port: 3128, username: "u", password: "p")) + #expect(try proxy.launch(resolve: resolve) == RouteLaunch( + environment: ["KNOTCHY_ROUTE=proxy", "KNOTCHY_PROXY_ADDR=198.51.100.4:3128"], + secrets: ["KNOTCHY_PROXY_URL=http://u:p@198.51.100.4:3128"] + )) + + let wireGuard = try NetworkRoute.wireGuard(WireGuardConfig.parse(Self.mullvadStyle)).launch(resolve: resolve) + #expect(asked == ["proxy.corp", "se-got-wg-001.relays.mullvad.net"]) + #expect(wireGuard.environment == [ + "KNOTCHY_ROUTE=wireguard", "KNOTCHY_WG_ENDPOINT=198.51.100.4:51820", "KNOTCHY_WG_ADDRESS=10.64.12.34/32", + "KNOTCHY_WG_DNS=10.64.0.1", "KNOTCHY_WG_MTU=1420", + ]) + #expect(wireGuard.environment.allSatisfy { !$0.contains(Self.key) }) + #expect(wireGuard.secrets.count == 1 && wireGuard.secrets[0].hasPrefix("KNOTCHY_WG_SETCONF=")) + #expect((wireGuard.environment + wireGuard.secrets).allSatisfy { !$0.contains("\n") }) + + #expect(throws: VMBackendError.failed("no")) { try NetworkRoute.unusable(reason: "no").launch(resolve: resolve) } + } + + @Test func routeLabels() throws { + #expect(NetworkRoute.direct.label == nil) + #expect(NetworkRoute.proxy(ProxySettings(kind: .socks5, host: "h", port: 1)).label == "SOCKS5 proxy") + var config = try WireGuardConfig.parse(Self.mullvadStyle) + #expect(NetworkRoute.wireGuard(config).label == "VPN") + config.name = "sg-sin-wg-001" + #expect(NetworkRoute.wireGuard(config).label == "sg-sin-wg-001") + } + + @Test func proxiesOnTheMacOrItsNetworkAreRefused() throws { + for local in ["10.1.2.3", "127.0.0.1", "169.254.169.254", "172.20.0.1", "192.168.64.1", "100.100.1.1", "0.0.0.0"] { + #expect(ProxySettings.isLocal(local, hostNetworks: []), "\(local)") + } + #expect(ProxySettings.isLocal("203.0.113.9", hostNetworks: ["203.0.113.0/24"])) + #expect(!ProxySettings.isLocal("203.0.113.9", hostNetworks: ["198.51.100.0/24"])) + #expect(!ProxySettings.isLocal("8.8.8.8", hostNetworks: [])) + #expect(!ProxySettings.isLocal("172.32.0.1", hostNetworks: [])) + + let proxy = NetworkRoute.proxy(ProxySettings(kind: .http, host: "proxy.lan", port: 3128)) + #expect(throws: VMBackendError.self) { try proxy.launch(resolve: { _ in "192.168.1.10" }) } + #expect(try proxy.launch(resolve: { _ in "192.168.1.10" }, allowLocalProxy: true).environment.count == 2) + // A local WireGuard server is fine: only wireguard-go can reach it. + let wireGuard = NetworkRoute.wireGuard(try WireGuardConfig.parse(Self.mullvadStyle)) + #expect(try wireGuard.launch(resolve: { _ in "192.168.1.1" }).environment.contains("KNOTCHY_WG_ENDPOINT=192.168.1.1:51820")) + } +} diff --git a/Tests/KnotchyCoreTests/NotchGeometryTests.swift b/Tests/KnotchyCoreTests/NotchGeometryTests.swift new file mode 100644 index 0000000..63f279d --- /dev/null +++ b/Tests/KnotchyCoreTests/NotchGeometryTests.swift @@ -0,0 +1,165 @@ +import CoreGraphics +import Testing +@testable import KnotchyCore + +struct NotchGeometryTests { + private let macBookPro = ScreenInfo( + frame: CGRect(x: 0, y: 0, width: 1512, height: 982), + visibleFrame: CGRect(x: 0, y: 80, width: 1512, height: 870), + safeAreaTop: 32, + auxLeft: CGRect(x: 0, y: 950, width: 662, height: 32), + auxRight: CGRect(x: 850, y: 950, width: 662, height: 32), + menuBarThickness: 24 + ) + + private let external = ScreenInfo( + frame: CGRect(x: 1512, y: -200, width: 1920, height: 1080), + visibleFrame: CGRect(x: 1512, y: -200, width: 1920, height: 1055), + safeAreaTop: 0, auxLeft: .zero, auxRight: .zero, menuBarThickness: 24 + ) + + @Test func peekUnderAPhysicalNotch() { + let layout = NotchGeometry.layout(for: macBookPro, mode: .peek) + #expect(layout.notch == CGRect(x: 662, y: 950, width: 188, height: 32)) + #expect(layout.pill == CGRect(x: 590, y: 950, width: 332, height: 32)) + #expect(layout.panel == CGRect(x: 430, y: 510, width: 652, height: 472)) + #expect(layout.toolbar == CGRect(x: 436, y: 916, width: 640, height: 34)) + #expect(layout.content == CGRect(x: 436, y: 516, width: 640, height: 400)) + #expect(layout.contentScale == 0.625) + } + + @Test func expandedUnderAPhysicalNotch() { + let layout = NotchGeometry.layout(for: macBookPro, mode: .expanded) + #expect(layout.panel == CGRect(x: 238, y: 270, width: 1036, height: 712)) + #expect(layout.content == CGRect(x: 244, y: 276, width: 1024, height: 640)) + #expect(layout.contentScale == 1) + } + + @Test func theNotchHeightIsTheSafeArea() { + var screen = macBookPro + screen.safeAreaTop = 37 + screen.auxLeft = CGRect(x: 0, y: 945, width: 662, height: 37) + screen.auxRight = CGRect(x: 850, y: 945, width: 662, height: 37) + let layout = NotchGeometry.layout(for: screen, mode: .peek) + #expect(layout.notch == CGRect(x: 662, y: 945, width: 188, height: 37)) + #expect(layout.panel.height == CGFloat(37 + 34 + 400 + 6)) + } + + @Test func aNotchNeedsASafeAreaAndBothMenuBarAreas() { + let virtualNotch = CGRect(x: 656, y: 950, width: 200, height: 32) + var noRightArea = macBookPro + noRightArea.auxRight = .zero + #expect(!noRightArea.hasNotch) + #expect(NotchGeometry.layout(for: noRightArea, mode: .peek).notch == virtualNotch) + + var noSafeArea = macBookPro + noSafeArea.safeAreaTop = 0 + #expect(!noSafeArea.hasNotch) + #expect(NotchGeometry.layout(for: noSafeArea, mode: .peek).notch == virtualNotch) + } + + @Test func asymmetricEarsAndNotchWidthClamping() { + var screen = macBookPro + screen.auxLeft = CGRect(x: 0, y: 950, width: 600, height: 32) + screen.auxRight = CGRect(x: 850, y: 950, width: 662, height: 32) + #expect(NotchGeometry.layout(for: screen, mode: .peek).notch == CGRect(x: 600, y: 950, width: 250, height: 32)) + + screen.auxLeft = CGRect(x: 0, y: 950, width: 500, height: 32) + screen.auxRight = CGRect(x: 1000, y: 950, width: 512, height: 32) + #expect(NotchGeometry.layout(for: screen, mode: .peek).notch.width == 300) + + screen.auxLeft = CGRect(x: 0, y: 950, width: 700, height: 32) + screen.auxRight = CGRect(x: 760, y: 950, width: 752, height: 32) + #expect(NotchGeometry.layout(for: screen, mode: .peek).notch == CGRect(x: 670, y: 950, width: 120, height: 32)) + } + + @Test func virtualNotchOnAScreenWithoutOne() { + let layout = NotchGeometry.layout(for: external, mode: .peek) + #expect(layout.notch == CGRect(x: 2372, y: 855, width: 200, height: 25)) + #expect(layout.panel == CGRect(x: 2146, y: 415, width: 652, height: 465)) + #expect(layout.content == CGRect(x: 2152, y: 421, width: 640, height: 400)) + } + + @Test func aScreenLeftOfTheMainOne() { + var screen = external + screen.frame.origin = CGPoint(x: -1920, y: 0) + screen.visibleFrame.origin = CGPoint(x: -1920, y: 0) + let layout = NotchGeometry.layout(for: screen, mode: .peek) + #expect(layout.notch == CGRect(x: -1060, y: 1055, width: 200, height: 25)) + #expect(layout.panel == CGRect(x: -1286, y: 615, width: 652, height: 465)) + } + + @Test func autoHiddenMenuBarKeepsAVisibleNotch() { + var screen = external + screen.visibleFrame = screen.frame + screen.menuBarThickness = 0 + #expect(NotchGeometry.layout(for: screen, mode: .peek).notch.height == 24) + + screen.menuBarThickness = 30 + #expect(NotchGeometry.layout(for: screen, mode: .peek).notch.height == 30) + } + + @Test func expandedScalesDownOnAShortScreenKeeping16by10() { + let short = ScreenInfo( + frame: CGRect(x: 0, y: 0, width: 1280, height: 720), + visibleFrame: CGRect(x: 0, y: 60, width: 1280, height: 636), + safeAreaTop: 0, auxLeft: .zero, auxRight: .zero, menuBarThickness: 24 + ) + let layout = NotchGeometry.layout(for: short, mode: .expanded) + // Usable height: 720 - 60 (Dock) - 24 (notch) - 34 - 6 - 8 = 588, rounded down to 585. + #expect(layout.content.size == CGSize(width: 936, height: 585)) + #expect(layout.content.width / layout.content.height == 1.6) + #expect(layout.panel.minY >= short.visibleFrame.minY) + #expect(layout.contentScale == CGFloat(936) / 1024) + #expect(NotchGeometry.layout(for: short, mode: .peek).content.size == CGSize(width: 640, height: 400)) + } + + @Test func expandedScalesDownOnANarrowScreen() { + let portrait = ScreenInfo( + frame: CGRect(x: 0, y: 0, width: 900, height: 1600), + visibleFrame: CGRect(x: 0, y: 0, width: 900, height: 1575), + safeAreaTop: 0, auxLeft: .zero, auxRight: .zero, menuBarThickness: 25 + ) + let layout = NotchGeometry.layout(for: portrait, mode: .expanded) + // Usable width: 900 - 16 - 12 = 872, which is exactly 16:10 at 545 high. + #expect(layout.panel == CGRect(x: 8, y: 990, width: 884, height: 610)) + #expect(layout.content == CGRect(x: 14, y: 996, width: 872, height: 545)) + } + + @Test func aVirtualNotchIsOnWholePoints() { + var odd = external + odd.frame.size.width = 1365 + odd.visibleFrame.size.width = 1365 + let layout = NotchGeometry.layout(for: odd, mode: .peek) + #expect(layout.notch.minX == layout.notch.minX.rounded()) + #expect(layout.panel.minX == layout.panel.minX.rounded()) + } + + @Test func mapsSurfacePointsToRenderPixels() { + let render = PixelSize(width: 1024, height: 640) + let peek = CGSize(width: 640, height: 400) + func point(_ p: CGPoint, _ surface: CGSize) -> [UInt16]? { + NotchGeometry.videoPoint(p, surfaceSize: surface, renderSize: render).map { [$0.x, $0.y] } + } + #expect(point(CGPoint(x: 320, y: 200), peek) == [512, 320]) + #expect(point(.zero, peek) == [0, 0]) + #expect(point(CGPoint(x: 0.5, y: 0.5), peek) == [0, 0]) + #expect(point(CGPoint(x: 640, y: 400), peek) == [1023, 639]) + #expect(point(CGPoint(x: -5, y: 900), peek) == [0, 639]) + // Letterboxing: a wider surface gets 30 pt bars at the sides, a taller one 50 pt above and below. + #expect(point(CGPoint(x: 30, y: 0), CGSize(width: 700, height: 400)) == [0, 0]) + #expect(point(CGPoint(x: 0, y: 450), CGSize(width: 640, height: 500)) == [0, 639]) + #expect(point(CGPoint(x: 0, y: 50), CGSize(width: 640, height: 500)) == [0, 0]) + } + + @Test func nothingToMapOntoGivesNoPoint() { + let render = PixelSize(width: 1024, height: 640) + let peek = CGSize(width: 640, height: 400) + #expect(NotchGeometry.videoPoint(.zero, surfaceSize: .zero, renderSize: render) == nil) + #expect(NotchGeometry.videoPoint(CGPoint(x: CGFloat.nan, y: 5), surfaceSize: peek, renderSize: render) == nil) + #expect(NotchGeometry.videoPoint(.zero, surfaceSize: CGSize(width: CGFloat.infinity, height: CGFloat.infinity), renderSize: render) == nil) + #expect(NotchGeometry.videoPoint(.zero, surfaceSize: CGSize(width: CGFloat.leastNonzeroMagnitude, height: 1), renderSize: render) == nil) + #expect(NotchGeometry.videoPoint(.zero, surfaceSize: peek, renderSize: PixelSize(width: 70_000, height: 640)) == nil) + #expect(NotchGeometry.videoPoint(.zero, surfaceSize: peek, renderSize: PixelSize(width: 0, height: 640)) == nil) + } +} diff --git a/Tests/KnotchyCoreTests/RuntimeInstallerTests.swift b/Tests/KnotchyCoreTests/RuntimeInstallerTests.swift new file mode 100644 index 0000000..c89c120 --- /dev/null +++ b/Tests/KnotchyCoreTests/RuntimeInstallerTests.swift @@ -0,0 +1,83 @@ +import CryptoKit +import Foundation +import Testing +@testable import KnotchyCore + +// Serialized: each download clears leftover download folders, which other tests would still be using. +@Suite(.serialized) +struct RuntimeInstallerTests { + private func release(for data: Data, url: URL) -> ContainerRuntime.Release { + let hash = SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined() + return ContainerRuntime.Release(version: "1.4.1", url: url, sha256: hash, size: Int64(data.count)) + } + + private func source(_ data: Data) throws -> URL { + let file = FileManager.default.temporaryDirectory.appendingPathComponent("source-\(UUID().uuidString).pkg") + try data.write(to: file) + return file + } + + private static func downloadDirectories() -> Set { + let names = (try? FileManager.default.contentsOfDirectory(atPath: NSTemporaryDirectory())) ?? [] + return Set(names.filter { $0.hasPrefix("knotchy-runtime-") }) + } + + @Test func aMatchingDownloadIsKeptUntilDiscarded() async throws { + let data = Data("package".utf8) + let url = try source(data) + defer { try? FileManager.default.removeItem(at: url) } + let installer = SystemRuntimeInstaller() + let package = try await installer.download(release(for: data, url: url)) { _ in } + #expect(try Data(contentsOf: package.file) == data) + #expect(package.file.lastPathComponent == url.lastPathComponent) + installer.discard(package) + #expect(!FileManager.default.fileExists(atPath: package.directory.path)) + } + + @Test func aMismatchedDownloadIsDeleted() async throws { + let url = try source(Data("tampered".utf8)) + defer { try? FileManager.default.removeItem(at: url) } + let expected = release(for: Data("package".utf8), url: url) + await #expect(throws: DownloadError.mismatch) { + _ = try await SystemRuntimeInstaller().download(expected) { _ in } + } + #expect(Self.downloadDirectories().isEmpty) + } + + @Test func aNewDownloadClearsLeftovers() async throws { + let leftover = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("knotchy-runtime-LEFTOVER") + try FileManager.default.createDirectory(at: leftover, withIntermediateDirectories: true) + let data = Data("package".utf8) + let url = try source(data) + defer { try? FileManager.default.removeItem(at: url) } + let installer = SystemRuntimeInstaller() + let package = try await installer.download(release(for: data, url: url)) { _ in } + #expect(!FileManager.default.fileExists(atPath: leftover.path)) + installer.discard(package) + } + + @Test func aFailedDownloadSaysSo() async { + let missing = URL(fileURLWithPath: "/nonexistent/container.pkg") + await #expect(throws: DownloadError.self) { + _ = try await SystemRuntimeInstaller().download(release(for: Data(), url: missing)) { _ in } + } + } + + // Only this path of the real `openInInstaller` may run in tests: any other would open Installer. + @MainActor + @Test func aChangedPackageIsNotOpened() async throws { + let data = Data("package".utf8) + let url = try source(data) + defer { try? FileManager.default.removeItem(at: url) } + let installer = SystemRuntimeInstaller() + let expected = release(for: data, url: url) + let package = try await installer.download(expected) { _ in } + try Data("pAckage".utf8).write(to: package.file) // Same size, so the hash has to catch it. + guard case .changed = await installer.openInInstaller(package, release: expected) else { + Issue.record("Expected the changed package not to be opened") + installer.discard(package) + return + } + #expect(!FileManager.default.fileExists(atPath: package.directory.path)) + } +} diff --git a/Tests/KnotchyCoreTests/SessionLifecycleTests.swift b/Tests/KnotchyCoreTests/SessionLifecycleTests.swift new file mode 100644 index 0000000..95eba2b --- /dev/null +++ b/Tests/KnotchyCoreTests/SessionLifecycleTests.swift @@ -0,0 +1,372 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionLifecycleTests { + private let first = "knotchy-00000001" + private let second = "knotchy-00000002" + + @Test func launchSweepsOrphansFirst() async { + let h = Harness() + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: [])]) + #expect(h.state == .idle) + } + + @Test func aLinkBootsConnectsAndLoadsThenPeeks() async { + let h = Harness() + h.backend.hold(.start) + h.connector.hold() + h.controller.open(Harness.url) + #expect(h.state == .starting(.booting, folded: false)) + #expect(h.controller.openedURL == Harness.url) + #expect(h.controller.liveIDs.snapshot == [first]) + + await waitUntil("start runs") { h.backend.parkedCount(.start) == 1 } + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .start(first)]) + #expect(h.backend.startArguments == [.init(id: first, url: Harness.url, + renderSize: PixelSize(width: 1024, height: 640), + blockCIDRs: ["192.168.1.0/24"])]) + + h.backend.release(.start) + await waitUntil("connecting") { h.state == .starting(.connecting, folded: false) } + await waitUntil("connect runs") { h.connector.parkedCount == 1 } + #expect(h.connector.socketPaths == ["/fake/\(first).sock"]) + + h.connector.release() + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + let stream = h.connector.connections[0] + #expect(h.controller.connection === stream) + + stream.emit(.hello(version: 1, size: SessionController.renderSize)) + stream.emit(.contentReady) + await waitUntil("live") { h.state == .live(.peek) } + } + + @Test func thePageURLFollowsTheVMButNeverReplacesTheOpenedLink() async { + let h = Harness() + let stream = await h.goLive() + #expect(h.controller.pageURL == nil) + let next = URL(string: "https://example.org/next")! + stream.emit(.pageURL(next)) + await waitUntil("the page URL shows") { h.controller.pageURL == next } + #expect(h.controller.openedURL == Harness.url) + + h.controller.open(URL(string: "https://example.net/")!) + #expect(h.controller.pageURL == nil) + } + + @Test func destroyIsConfirmedBeforeDestroyedShowsThenGoesIdle() async { + let h = Harness() + let stream = await h.goLive() + h.backend.hold(.destroy) + h.controller.destroy() + #expect(h.state == .destroying) + #expect(stream.isClosed) + #expect(h.controller.connection == nil) + + await waitUntil("destroy runs") { h.backend.parkedCount(.destroy) == 1 } + #expect(h.state == .destroying) + #expect(h.controller.liveIDs.snapshot == [first]) + + h.backend.release(.destroy) + await waitUntil("destroyed") { h.state == .destroyed } + #expect(h.controller.liveIDs.snapshot.isEmpty) + h.advance(by: .milliseconds(1499)) + #expect(h.state == .destroyed) + h.advance(by: .milliseconds(1)) + #expect(h.state == .idle) + #expect(h.controller.openedURL == nil) + } + + @Test(arguments: [LiveMode.peek, .expanded, .collapsed]) + func destroyWorksFromEveryLiveMode(_ mode: LiveMode) async { + let h = Harness() + await h.goLive() + h.enter(mode) + #expect(h.state == .live(mode)) + h.controller.destroy() + #expect(h.state == .destroying) + #expect(h.controller.countdown == nil) + await waitUntil("destroyed") { h.state == .destroyed } + } + + @Test func destroyDuringPreflightNeverCallsTheBackendsDestroy() async { + let h = Harness() + h.backend.hold(.preflight) + h.controller.open(Harness.url) + await waitUntil("preflight runs") { h.backend.parkedCount(.preflight) == 1 } + h.controller.destroy() + h.backend.release(.preflight) + await waitUntil("destroyed") { h.state == .destroyed } + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight]) + #expect(h.controller.liveIDs.snapshot.isEmpty) + } + + @Test func destroyWhileConnectingClosesTheLateStream() async { + let h = Harness() + h.connector.hold() + h.controller.open(Harness.url) + await waitUntil("connect runs") { h.connector.parkedCount == 1 } + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.connector.release() + await waitUntil("the late stream is closed") { h.connector.connections.first?.isClosed == true } + #expect(h.state == .destroyed) + #expect(h.controller.connection == nil) + h.advance(by: .milliseconds(1500)) + #expect(h.state == .idle) + } + + @Test func aConnectThatFailsAfterDestroyIsIgnored() async { + let h = Harness() + h.connector.hold() + h.connector.failNextConnect(with: .timedOut) + h.backend.hold(.destroy) + h.controller.open(Harness.url) + await waitUntil("connect runs") { h.connector.parkedCount == 1 } + h.controller.destroy() + await waitUntil("destroy runs") { h.backend.parkedCount(.destroy) == 1 } + h.connector.release() + await h.controller.waitForSessionTasks() + #expect(h.state == .destroying) // Not the failed connect's error card. + h.backend.release(.destroy) + await waitUntil("destroyed") { h.state == .destroyed } + } + + @Test func destroyWhileBootingDestroysTheVMOnceStartReturns() async { + let h = Harness() + h.backend.hold(.start) + h.controller.open(Harness.url) + await waitUntil("start runs") { h.backend.parkedCount(.start) == 1 } + h.controller.destroy() + #expect(h.state == .destroying) + h.backend.release(.start) + await waitUntil("destroyed") { h.state == .destroyed } + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .start(first), .destroy(first)]) + #expect(h.connector.socketPaths.isEmpty) + } + + @Test func aStuckDestroyShowsTheAmberPillAndCanBeRetried() async { + let h = Harness() + await h.goLive() + h.backend.failDestroys(with: VMBackendError.destroyStuck(id: first)) + h.controller.destroy() + await waitUntil("stuck") { h.state == .failed(.destroyStuck) } + #expect(h.controller.liveIDs.snapshot == [first]) + + h.backend.failDestroys(with: nil) + h.controller.retry() + #expect(h.state == .destroying) + await waitUntil("destroyed") { h.state == .destroyed } + #expect(h.backend.calls.filter { $0 == .destroy(first) }.count == 2) + #expect(h.controller.liveIDs.snapshot.isEmpty) + } + + @Test func aRetryNeverRetriesItself() async { + let h = Harness() + await h.goLive() + h.backend.failDestroys(with: VMBackendError.destroyStuck(id: first)) + h.controller.destroy() + await waitUntil("stuck") { h.state == .failed(.destroyStuck) } + + h.controller.retry() // Fails again: its own job must not also force-destroy the VM. + await h.controller.waitForBackendIdle() + #expect(h.state == .failed(.destroyStuck)) + + h.backend.failDestroys(with: nil) + h.controller.retry() + await waitUntil("destroyed") { h.state == .destroyed } + await h.controller.waitForBackendIdle() + #expect(!h.backend.calls.contains(.forceDestroy(first))) + } + + @Test func theStuckPillGoesIdleAfter30sAndTheVMStaysListed() async { + let h = Harness() + await h.goLive() + h.backend.failDestroys(with: VMBackendError.destroyStuck(id: first)) + h.controller.destroy() + await waitUntil("stuck") { h.state == .failed(.destroyStuck) } + h.advance(by: .seconds(29)) + #expect(h.state == .failed(.destroyStuck)) + h.advance(by: .seconds(1)) + #expect(h.state == .idle) + #expect(h.controller.liveIDs.snapshot == [first]) + } + + @Test func eachLaterJobRetriesAStuckVMOnce() async { + let h = Harness() + await h.goLive() + h.backend.failDestroys(with: VMBackendError.destroyStuck(id: first)) + h.controller.destroy() + await waitUntil("stuck") { h.state == .failed(.destroyStuck) } + await h.controller.waitForBackendIdle() + #expect(!h.backend.calls.contains(.forceDestroy(first))) + + h.backend.failDestroys(with: nil) + h.backend.failForceDestroys(with: VMBackendError.destroyStuck(id: first)) + await h.goLive(Harness.otherURL) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls.filter { $0 == .forceDestroy(first) }.count == 1) + #expect(h.controller.liveIDs.snapshot == [first, second]) + + h.backend.failForceDestroys(with: nil) + h.controller.destroy() // Destroying `second` is a later job: `first` gets another retry. + await waitUntil("destroyed") { h.state == .destroyed } + await h.controller.waitForBackendIdle() + #expect(h.backend.calls.filter { $0 == .forceDestroy(first) }.count == 2) + #expect(h.controller.liveIDs.snapshot.isEmpty) + } + + @Test func stuckVMsTakeTurnsForRetries() async { + let h = Harness() + h.backend.failDestroys(with: VMBackendError.destroyStuck(id: "any")) + h.backend.failForceDestroys(with: VMBackendError.destroyStuck(id: "any")) + await h.goLive() + h.controller.destroy() + await waitUntil("the first VM is stuck") { h.state == .failed(.destroyStuck) } + await h.goLive(Harness.otherURL) + h.controller.destroy() + await waitUntil("the second VM is stuck") { h.state == .failed(.destroyStuck) } + await h.goLive() + await h.controller.waitForBackendIdle() + let retries = h.backend.calls.filter { if case .forceDestroy = $0 { true } else { false } } + #expect(retries == [.forceDestroy(first), .forceDestroy(first), .forceDestroy(second)]) + } + + @Test func aStartFailureShowsTheErrorCardAndQueuesDestroy() async { + let h = Harness() + h.backend.failStarts(with: VMBackendError.failed("container run timed out")) + h.controller.open(Harness.url) + await waitUntil("failed") { h.state == .failed(.start("container run timed out")) } + await waitUntil("the VM is destroyed") { h.backend.calls.contains(.destroy(first)) } + await waitUntil("gone") { h.controller.liveIDs.snapshot.isEmpty } + + h.backend.failStarts(with: nil) + h.controller.retry() + #expect(h.state == .starting(.booting, folded: false)) + #expect(h.controller.openedURL == Harness.url) + await waitUntil("the retry starts") { h.backend.calls.contains(.start(second)) } + } + + @Test func dismissingTheErrorCardGoesIdle() async { + let h = Harness() + h.backend.failStarts(with: VMBackendError.failed("boom")) + h.controller.open(Harness.url) + await waitUntil("failed") { h.state == .failed(.start("boom")) } + h.controller.dismiss() + #expect(h.state == .idle) + #expect(h.controller.openedURL == nil) + } + + @Test func aConnectFailureFailsTheStart() async { + let h = Harness() + h.connector.failNextConnect(with: .timedOut) + h.controller.open(Harness.url) + await waitUntil("failed") { h.state == .failed(.start("The VM didn't answer in time.")) } + await waitUntil("the VM is destroyed") { h.backend.calls.contains(.destroy(first)) } + } + + @Test func aVersionMismatchAsksForARebuildAndDestroysTheVM() async { + let h = Harness() + h.connector.failNextConnect(with: .versionMismatch(found: 0, need: 1)) + h.backend.hold(.destroy) + h.controller.open(Harness.url) + await waitUntil("setup required") { + h.state == .setupRequired(.imageOutdated(found: 0, need: 1), error: nil) + } + await waitUntil("destroy runs") { h.backend.parkedCount(.destroy) == 1 } + #expect(h.controller.liveIDs.snapshot == [first]) // Kept until the destroy is confirmed. + h.backend.release(.destroy) + await waitUntil("gone") { h.controller.liveIDs.snapshot.isEmpty } + } + + @Test func theStreamEndingWhileStartingFailsTheStart() async { + let h = Harness() + let stream = await h.openAndConnect() + stream.emit(.ended(.noFirstFrame)) + await waitUntil("failed") { h.state == .failed(.start("The VM didn't send a first frame in time.")) } + await waitUntil("the VM is destroyed") { h.backend.calls.contains(.destroy(first)) } + } + + @Test func theStreamEndingWhileLiveShowsSessionEndedFor3s() async { + let h = Harness() + let stream = await h.goLive() + stream.emit(.ended(.closed)) + await waitUntil("ended") { h.state == .failed(.ended(.closed)) } + await waitUntil("the VM is destroyed") { h.backend.calls.contains(.destroy(first)) } + h.advance(by: .milliseconds(2999)) + #expect(h.state == .failed(.ended(.closed))) + h.advance(by: .milliseconds(1)) + #expect(h.state == .idle) + } + + @Test(arguments: [LiveMode.peek, .expanded, .collapsed]) + func theStreamEndingInEveryLiveModeShowsSessionEnded(_ mode: LiveMode) async { + let h = Harness() + let stream = await h.goLive() + h.enter(mode) + stream.emit(.ended(.decodeErrors)) + await waitUntil("ended") { h.state == .failed(.ended(.decodeErrors)) } + #expect(h.controller.countdown == nil) + #expect(h.controller.connection == nil) + } + + @Test func aStreamThatFinishesWithoutEndedStillEnds() async { + let h = Harness() + let stream = await h.goLive() + stream.finishEvents() + await waitUntil("ended") { h.state == .failed(.ended(.closed)) } + await waitUntil("the VM is destroyed") { h.backend.calls.contains(.destroy(first)) } + } + + @Test func theAppQuitsItselfAfter60sIdle() { + let h = Harness() + h.advance(by: .seconds(59)) + #expect(h.recorder.quitRequests == 0) + h.advance(by: .seconds(1)) + #expect(h.recorder.quitRequests == 1) + } + + @Test func theIdleQuitTimerRestartsWheneverIdleStarts() async { + let h = Harness() + h.advance(by: .seconds(30)) + await h.goLive() + h.advance(by: .seconds(60)) + #expect(h.recorder.quitRequests == 0) + + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.advance(by: .milliseconds(1500)) + #expect(h.state == .idle) + h.advance(by: .seconds(59)) + #expect(h.recorder.quitRequests == 0) + h.advance(by: .seconds(1)) + #expect(h.recorder.quitRequests == 1) + } + + @Test func noLinkOpensOnceTheChainIsClosedForQuitting() async { + let h = Harness() + h.controller.closeBackendChain() + h.controller.open(Harness.url) + #expect(h.state == .idle) + } + + @Test func theQuitSnapshotIncludesAStartInFlight() async { + let h = Harness() + h.backend.hold(.start) + h.controller.open(Harness.url) + await waitUntil("start runs") { h.backend.parkedCount(.start) == 1 } + h.controller.closeBackendChain() + #expect(h.controller.liveIDs.snapshot == [first]) + + // Nothing may destroy the VM behind the quit path's back. + h.backend.release(.start) + await h.controller.waitForBackendIdle() + #expect(!h.backend.calls.contains(.destroy(first))) + #expect(h.controller.liveIDs.snapshot == [first]) + } +} diff --git a/Tests/KnotchyCoreTests/SessionNewLinkTests.swift b/Tests/KnotchyCoreTests/SessionNewLinkTests.swift new file mode 100644 index 0000000..39b955e --- /dev/null +++ b/Tests/KnotchyCoreTests/SessionNewLinkTests.swift @@ -0,0 +1,204 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionNewLinkTests { + private let first = "knotchy-00000001" + private let second = "knotchy-00000002" + + @Test func fromLiveTheOldVMIsDestroyedAndTheNewOneStartsAtOnce() async { + let h = Harness() + let old = await h.goLive() + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + #expect(h.controller.openedURL == Harness.otherURL) + #expect(old.isClosed) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .start(first), .destroy(first), .preflight, .start(second)]) + } + + @Test func aStartThatHasNotBegunNeverRuns() async { + let h = Harness() + h.controller.open(Harness.url) + h.controller.open(Harness.otherURL) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .start(second)]) + #expect(h.controller.liveIDs.snapshot == [second]) + } + + @Test func aSupersededStartIsDestroyedRightAfterItReturns() async { + let h = Harness() + h.backend.hold(.start) + h.controller.open(Harness.url) + await waitUntil("start runs") { h.backend.parkedCount(.start) == 1 } + h.controller.open(Harness.otherURL) + #expect(h.controller.liveIDs.snapshot == [first, second]) + + h.backend.release(.start) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .start(first), .destroy(first), .preflight, .start(second)]) + await waitUntil("the new session connects") { h.connector.socketPaths == ["/fake/\(second).sock"] } + #expect(h.controller.liveIDs.snapshot == [second]) + } + + @Test func aConnectThatReturnsAfterANewLinkIsClosed() async { + let h = Harness() + h.connector.hold() + h.controller.open(Harness.url) + await waitUntil("connect runs") { h.connector.parkedCount == 1 } + h.backend.hold(.start) + h.controller.open(Harness.otherURL) + await waitUntil("the new start runs") { h.backend.parkedCount(.start) == 1 } + + h.connector.release() + await waitUntil("the late stream is closed") { h.connector.connections.first?.isClosed == true } + #expect(h.state == .starting(.booting, folded: false)) + #expect(h.controller.connection == nil) + + h.backend.release(.start) + await waitUntil("the new session connects") { h.state == .starting(.loading, folded: false) } + #expect((h.controller.connection as? FakeStreamConnection)?.socketPath == "/fake/\(second).sock") + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .start(first), .destroy(first), .preflight, .start(second)]) + } + + @Test func aSupersededStartThatFailsLeavesTheNewSessionAlone() async { + let h = Harness() + h.backend.hold(.start) + h.backend.hold(.destroy) + h.controller.open(Harness.url) + await waitUntil("start runs") { h.backend.parkedCount(.start) == 1 } + h.controller.open(Harness.otherURL) + h.backend.failStarts(with: VMBackendError.failed("boom")) + h.backend.release(.start) + await waitUntil("the old VM's destroy runs") { h.backend.parkedCount(.destroy) == 1 } + #expect(h.state == .starting(.booting, folded: false)) // Not the old start's error card. + h.backend.failStarts(with: nil) + h.backend.release(.destroy) + await waitUntil("the new session connects") { h.state == .starting(.loading, folded: false) } + } + + @Test func anOldSessionsStuckDestroyDoesNotDriveTheCurrentPill() async { + let h = Harness() + await h.goLive() + h.backend.hold(.destroy) + h.backend.failDestroys(with: VMBackendError.destroyStuck(id: first)) + h.controller.destroy() + await waitUntil("destroy runs") { h.backend.parkedCount(.destroy) == 1 } + h.controller.open(Harness.otherURL) + h.controller.destroy() // The new session never started a VM, so this one succeeds. + h.backend.release(.destroy) + await h.controller.waitForBackendIdle() + #expect(h.state == .destroyed) + } + + @Test func liveIDsCoverASupersededSessionUntilItsDestroyIsConfirmed() async { + let h = Harness() + await h.goLive() + h.backend.hold(.destroy) + h.controller.open(Harness.otherURL) + await waitUntil("destroy runs") { h.backend.parkedCount(.destroy) == 1 } + #expect(h.controller.liveIDs.snapshot == [first, second]) + h.backend.release(.destroy) + await h.controller.waitForBackendIdle() + #expect(h.controller.liveIDs.snapshot == [second]) + } + + @Test func fromDestroyingTheNewSessionStartsAfterTheDestroy() async { + let h = Harness() + await h.goLive() + h.backend.hold(.destroy) + h.controller.destroy() + await waitUntil("destroy runs") { h.backend.parkedCount(.destroy) == 1 } + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + + h.backend.release(.destroy) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls.suffix(3) == [.destroy(first), .preflight, .start(second)]) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + } + + @Test func fromTheCollapsedPillTheNewLinkOpensThePanel() async { + let h = Harness() + await h.goLive() + h.controller.clickedOutside() + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + #expect(h.controller.countdown == nil) + } + + @Test func fromDestroyedTheCheckmarkIsCutShort() async { + let h = Harness() + await h.goLive() + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + h.advance(by: .seconds(2)) + #expect(h.state != .idle) + } + + @Test func fromSessionEndedThePillIsCutShort() async { + let h = Harness() + let stream = await h.goLive() + stream.emit(.ended(.closed)) + await waitUntil("ended") { h.state == .failed(.ended(.closed)) } + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + h.advance(by: .seconds(3)) + #expect(h.state != .idle) + } + + @Test func fromTheErrorCardTheNewLinkStarts() async { + let h = Harness() + h.backend.failStarts(with: VMBackendError.failed("boom")) + h.controller.open(Harness.url) + await waitUntil("failed") { h.state == .failed(.start("boom")) } + h.backend.failStarts(with: nil) + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + await waitUntil("the new start runs") { h.backend.calls.contains(.start(second)) } + } + + @Test func fromAStuckDestroyTheStuckVMStaysOnPendingCleanup() async { + let h = Harness() + await h.goLive() + h.backend.failDestroys(with: VMBackendError.destroyStuck(id: first)) + h.backend.failForceDestroys(with: VMBackendError.destroyStuck(id: first)) + h.controller.destroy() + await waitUntil("stuck") { h.state == .failed(.destroyStuck) } + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + await h.controller.waitForBackendIdle() + #expect(h.controller.liveIDs.snapshot == [first, second]) + } + + @Test func fromSetupRequiredPreflightRunsAgain() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + h.controller.open(Harness.otherURL) + #expect(h.state == .starting(.booting, folded: false)) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .preflight, .start(second)]) + } + + @Test func backendCallsNeverOverlap() async { + let h = Harness() + h.backend.hold(.start) + h.controller.open(Harness.url) + await waitUntil("start runs") { h.backend.parkedCount(.start) == 1 } + h.controller.open(Harness.otherURL) + h.controller.open(Harness.url) + h.controller.destroy() + h.controller.open(Harness.otherURL) + h.backend.release(.start) + await h.controller.waitForBackendIdle() + #expect(h.backend.maxConcurrentCalls == 1) + #expect(h.controller.liveIDs.snapshot == ["knotchy-00000004"]) + } +} diff --git a/Tests/KnotchyCoreTests/SessionPanelTests.swift b/Tests/KnotchyCoreTests/SessionPanelTests.swift new file mode 100644 index 0000000..ac58cc4 --- /dev/null +++ b/Tests/KnotchyCoreTests/SessionPanelTests.swift @@ -0,0 +1,340 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionPanelTests { + @Test func aFreshPanelStaysOpenUntilEnteredOrFor30s() async { + let h = Harness() + await h.goLive() + h.advance(by: .milliseconds(29_999)) + #expect(h.state == .live(.peek)) + h.advance(by: .milliseconds(1)) + #expect(h.state == .live(.collapsed)) + } + + @Test func theFirstClickOutsideFoldsAnUnengagedPanel() async { + let h = Harness() + await h.goLive() + h.controller.clickedOutside() + #expect(h.state == .live(.collapsed)) + } + + @Test func anEnteredPeekFolds400msAfterThePointerLeaves() async { + let h = Harness() + await h.goLive() + h.controller.pointerEntered() + h.advance(by: .seconds(60)) + #expect(h.state == .live(.peek)) // Entered: the 30 s fold no longer applies. + + h.controller.pointerExited() + h.advance(by: .milliseconds(399)) + #expect(h.state == .live(.peek)) + h.advance(by: .milliseconds(1)) + #expect(h.state == .live(.collapsed)) + } + + @Test func comingBackWithinTheGraceKeepsItOpen() async { + let h = Harness() + await h.goLive() + h.controller.pointerEntered() + h.controller.pointerExited() + h.advance(by: .milliseconds(200)) + h.controller.pointerEntered() + h.advance(by: .seconds(1)) + #expect(h.state == .live(.peek)) + } + + @Test func afterAClickInsideOnlyAClickOutsideFolds() async { + let h = Harness() + await h.goLive() + h.controller.pointerEntered() + h.controller.panelClicked() + h.controller.pointerExited() + h.advance(by: .seconds(60)) + #expect(h.state == .live(.peek)) + h.controller.clickedOutside() + #expect(h.state == .live(.collapsed)) + } + + @Test func expandedFoldsOnlyOnAClickOutside() async { + let h = Harness() + await h.goLive() + h.controller.pointerEntered() + h.controller.expand() + #expect(h.state == .live(.expanded)) + h.controller.pointerExited() + h.advance(by: .seconds(60)) + #expect(h.state == .live(.expanded)) + + h.controller.shrink() + #expect(h.state == .live(.peek)) + h.controller.pointerEntered() + h.controller.pointerExited() + h.advance(by: .seconds(1)) + #expect(h.state == .live(.peek)) // Still key after Expanded: pointer-away doesn't fold. + + h.controller.expand() + h.controller.clickedOutside() + #expect(h.state == .live(.collapsed)) + } + + @Test func expandAndShrinkOnlyApplyToTheirModes() async { + let h = Harness() + h.controller.expand() + #expect(h.state == .idle) + await h.goLive() + h.controller.shrink() + #expect(h.state == .live(.peek)) + h.controller.clickedOutside() + h.controller.expand() + #expect(h.state == .live(.collapsed)) + } + + @Test func hoveringThePillReopensPeek() async { + let h = Harness() + await h.goLive() + h.controller.clickedOutside() + h.controller.pointerEntered() + #expect(h.state == .live(.peek)) + h.controller.pointerExited() + h.advance(by: .milliseconds(400)) + #expect(h.state == .live(.collapsed)) + } + + @Test func theIdleCountdownRunsOnlyWhileCollapsedAndResetsOnReopen() async { + let h = Harness() + await h.goLive() + #expect(h.controller.countdown == nil) + h.controller.clickedOutside() + #expect(h.controller.countdown == .seconds(300)) + + h.advance(by: .seconds(240)) + #expect(h.controller.countdown == .seconds(60)) + h.controller.pointerEntered() + #expect(h.controller.countdown == nil) + h.advance(by: .seconds(120)) + #expect(h.state == .live(.peek)) + + h.controller.clickedOutside() + #expect(h.controller.countdown == .seconds(300)) + h.advance(by: .seconds(300) - .milliseconds(1)) + #expect(h.state == .live(.collapsed)) + h.advance(by: .milliseconds(1)) + #expect(h.state == .destroying) + await waitUntil("destroyed") { h.state == .destroyed } + } + + @Test func decodingPausesWhileCollapsed() async { + let h = Harness() + let stream = await h.goLive() + h.controller.clickedOutside() + #expect(stream.decodeChanges == [false]) + h.controller.pointerEntered() + #expect(stream.decodeChanges == [false, true]) + } + + @Test func foldingWhileStartingShowsThePillAndStaysFoldedWhenReady() async { + let h = Harness() + h.connector.hold() + h.controller.open(Harness.url) + h.controller.clickedOutside() + #expect(h.state == .starting(.booting, folded: true)) + + await waitUntil("connecting") { h.state == .starting(.connecting, folded: true) } + h.connector.release() + await waitUntil("loading") { h.state == .starting(.loading, folded: true) } + + let stream = h.connector.connections[0] + stream.emit(.contentReady) + await waitUntil("collapsed") { h.state == .live(.collapsed) } + #expect(h.controller.countdown == .seconds(300)) + #expect(stream.decodeChanges == [false]) + } + + @Test func hoveringTheStartingPillUnfoldsIt() async { + let h = Harness() + h.backend.hold(.start) + h.controller.open(Harness.url) + h.controller.clickedOutside() + h.controller.pointerEntered() + #expect(h.state == .starting(.booting, folded: false)) + h.controller.pointerExited() + h.advance(by: .milliseconds(400)) + #expect(h.state == .starting(.booting, folded: true)) + h.backend.release(.start) + } + + @Test func aGraceFoldStartedWhileStartingCarriesIntoPeek() async { + let h = Harness() + let stream = await h.openAndConnect() + h.controller.pointerEntered() + h.controller.pointerExited() + h.advance(by: .milliseconds(200)) + stream.emit(.contentReady) + await waitUntil("live") { h.state == .live(.peek) } + h.advance(by: .milliseconds(200)) + #expect(h.state == .live(.collapsed)) + } + + @Test func retryKeepsThePanelEnteredWhenThePointerIsOnIt() async { + let h = Harness() + h.backend.failStarts(with: VMBackendError.failed("boom")) + h.controller.open(Harness.url) + await waitUntil("failed") { h.state == .failed(.start("boom")) } + h.backend.failStarts(with: nil) + h.controller.pointerEntered() + h.controller.retry() + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + h.connector.connections[0].emit(.contentReady) + await waitUntil("live") { h.state == .live(.peek) } + + h.advance(by: .seconds(30)) + #expect(h.state == .live(.peek)) + h.controller.pointerExited() + h.advance(by: .milliseconds(400)) + #expect(h.state == .live(.collapsed)) + } + + @Test func pointerInputDoesNothingWithoutAnOpenPanel() async { + let h = Harness() + h.controller.clickedOutside() + h.controller.pointerEntered() + h.controller.pointerExited() + h.controller.panelClicked() + #expect(h.state == .idle) + } +} + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionProgressTests { + @Test func showsTheLatestProgressWhileStarting() async { + let h = Harness() + let stream = await h.openAndConnect() + stream.emit(.progress(10)) + await waitUntil("10 %") { h.controller.progress == 10 } + stream.emit(.progress(60)) + await waitUntil("60 %") { h.controller.progress == 60 } + h.advance(by: .seconds(10)) + #expect(h.controller.progress == 60) // No hiding before content is ready. + } + + @Test func hidesAtProgress100() async { + let h = Harness() + let stream = await h.openAndConnect() + stream.emit(.progress(30)) + await waitUntil("30 %") { h.controller.progress == 30 } + stream.emit(.progress(100)) + await waitUntil("hidden") { h.controller.progress == nil } + } + + @Test func onceReadyItHides2sAfterTheLastProgress() async { + let h = Harness() + let stream = await h.openAndConnect() + stream.emit(.progress(30)) + stream.emit(.contentReady) + await waitUntil("live") { h.state == .live(.peek) } + #expect(h.controller.progress == 30) + h.advance(by: .milliseconds(1999)) + #expect(h.controller.progress == 30) + h.advance(by: .milliseconds(1)) + #expect(h.controller.progress == nil) + } + + @Test func aLoadThatNeverReports100StillHidesWhenReady() async { + let h = Harness() + let stream = await h.openAndConnect() + stream.emit(.progress(30)) + await waitUntil("30 %") { h.controller.progress == 30 } + h.advance(by: .seconds(5)) + stream.emit(.contentReady) + await waitUntil("live") { h.state == .live(.peek) } + #expect(h.controller.progress == nil) + } + + @Test func endingASessionHidesTheBar() async { + let h = Harness() + let stream = await h.openAndConnect() + stream.emit(.progress(40)) + await waitUntil("40 %") { h.controller.progress == 40 } + stream.emit(.ended(.closed)) + await waitUntil("failed") { h.state == .failed(.start("The VM closed the connection.")) } + #expect(h.controller.progress == nil) + } + + @Test func progressAfterReadyShowsAgainThenHides() async { + let h = Harness() + let stream = await h.goLive() + stream.emit(.progress(20)) + await waitUntil("20 %") { h.controller.progress == 20 } + h.advance(by: .seconds(2)) + #expect(h.controller.progress == nil) + } +} + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionToolbarTests { + @Test func navigateSendsTheURLAndUpdatesTheAddress() async throws { + let h = Harness() + let stream = await h.goLive() + let url = try #require(URL(string: "https://example.net/page")) + h.controller.navigate(url) + #expect(stream.sent == [.navigate("https://example.net/page")]) + #expect(h.controller.openedURL == url) + } + + @Test func backForwardAndReloadRunScripts() async { + let h = Harness() + let stream = await h.goLive() + h.controller.goBack() + h.controller.goForward() + h.controller.reload() + #expect(stream.sent == [.script("history.back()"), .script("history.forward()"), .script("location.reload()")]) + } + + @Test func toolbarActionsNeedALiveSession() async { + let h = Harness() + let stream = await h.openAndConnect() + h.controller.reload() + h.controller.navigate(Harness.otherURL) + #expect(stream.sent.isEmpty) + #expect(h.controller.openedURL == Harness.url) + } + + @Test func aNonWebLinkShowsANoticeFor2s() async throws { + let h = Harness() + h.controller.open(try #require(URL(string: "ftp://files.example.com"))) + #expect(h.controller.notice == .noWebLink) + #expect(h.state == .idle) + h.advance(by: .milliseconds(1999)) + #expect(h.controller.notice == .noWebLink) + h.advance(by: .milliseconds(1)) + #expect(h.controller.notice == nil) + } + + @Test func navigateRejectsNonWebLinks() async throws { + let h = Harness() + let stream = await h.goLive() + h.controller.navigate(try #require(URL(string: "file:///etc/passwd"))) + #expect(stream.sent.isEmpty) + #expect(h.controller.notice == .noWebLink) + #expect(h.controller.openedURL == Harness.url) + } + + @Test func textWithoutALinkShowsANotice() { + let h = Harness() + h.controller.open(text: "call me maybe") + #expect(h.controller.notice == .noWebLink) + #expect(h.state == .idle) + } + + @Test func textWithALinkOpensIt() { + let h = Harness() + h.controller.open(text: "Your parcel: https://example.com/ (tracking)") + #expect(h.state == .starting(.booting, folded: false)) + #expect(h.controller.openedURL == Harness.url) + } +} diff --git a/Tests/KnotchyCoreTests/SessionRuntimeInstallTests.swift b/Tests/KnotchyCoreTests/SessionRuntimeInstallTests.swift new file mode 100644 index 0000000..35c9431 --- /dev/null +++ b/Tests/KnotchyCoreTests/SessionRuntimeInstallTests.swift @@ -0,0 +1,82 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionRuntimeInstallTests { + @Test func aFreshInstallHoldsLinksThenStartsTheServiceAndBuilds() async { + let h = Harness() + let hold = await h.controller.beginRuntimeInstall(replacing: nil) + #expect((try? hold.get()) != nil) + #expect(h.state == .settingUp(.cliMissing)) + #expect(h.controller.setupFolded) + h.controller.open(Harness.url) + #expect(h.state == .settingUp(.cliMissing)) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: [])]) // The link waits: no preflight, no start. + + h.backend.queuePreflight(.issue(.imageMissing)) + #expect(await h.controller.finishRuntimeInstall(.installed) == .ready) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(Array(h.backend.calls.prefix(5)) + == [.sweep(keeping: []), .fix(.serviceStopped), .preflight, .fix(.imageMissing), .preflight]) + } + + @Test func anUpdateStopsTheServiceFirst() async { + let h = Harness() + let hold = await h.controller.beginRuntimeInstall(replacing: "1.3.0") + #expect((try? hold.get()) != nil) + #expect(h.state == .settingUp(.cliOutdated(found: "1.3.0"))) + #expect(h.backend.calls == [.sweep(keeping: []), .stopService]) + } + + @Test func aServiceThatWontStopEndsTheHold() async { + let h = Harness() + h.backend.failStopService(with: VMBackendError.failed("still running")) + guard case .failure(let refusal) = await h.controller.beginRuntimeInstall(replacing: "1.3.0") else { + Issue.record("The hold should have been refused") + return + } + #expect(refusal == .stopFailed("still running")) + #expect(h.state == .idle) + } + + @Test func theHoldWaitsForTheBrowserToClose() async { + let h = Harness() + await h.goLive() + guard case .failure(let refusal) = await h.controller.beginRuntimeInstall(replacing: "1.3.0") else { + Issue.record("The hold should have been refused") + return + } + #expect(refusal == .sessionOpen) + #expect(h.state == .live(.peek)) + #expect(!h.backend.calls.contains(.stopService)) + } + + @Test func theHoldIsAcceptedAfterAFailedStart() async { + let h = Harness() + h.backend.failStarts(with: VMBackendError.failed("boot failed")) + h.controller.open(Harness.url) + await waitUntil("the start fails") { if case .failed(.start) = h.state { true } else { false } } + let hold = await h.controller.beginRuntimeInstall(replacing: nil) + #expect((try? hold.get()) != nil) + #expect(h.state == .settingUp(.cliMissing)) + } + + @Test func aCancelledInstallGoesBackToIdle() async { + let h = Harness() + _ = await h.controller.beginRuntimeInstall(replacing: nil) + #expect(await h.controller.finishRuntimeInstall(.cancelled) == .needs(.cliMissing)) + #expect(h.state == .idle) + } + + @Test func aFailedInstallShowsItsCardToAWaitingLink() async { + let h = Harness() + _ = await h.controller.beginRuntimeInstall(replacing: nil) + h.controller.open(Harness.url) + #expect(await h.controller.finishRuntimeInstall(.failed("Couldn't install the runtime.")) + == .failed(.cliMissing, "Couldn't install the runtime.")) + #expect(h.state == .setupRequired(.cliMissing, error: "Couldn't install the runtime.")) + } +} diff --git a/Tests/KnotchyCoreTests/SessionSetupRunTests.swift b/Tests/KnotchyCoreTests/SessionSetupRunTests.swift new file mode 100644 index 0000000..f7b1782 --- /dev/null +++ b/Tests/KnotchyCoreTests/SessionSetupRunTests.swift @@ -0,0 +1,121 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionSetupRunTests { + @Test func aWindowRunFixesEachIssueInTurnAndEndsIdle() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + #expect(await h.controller.runSetup(.serviceStopped, continuing: true) == .ready) + #expect(h.state == .idle) + #expect(h.backend.calls == [.sweep(keeping: []), .fix(.serviceStopped), .preflight, .fix(.imageMissing), .preflight]) + } + + @Test func aWindowRunStartsFolded() async { + let h = Harness() + h.backend.hold(.fix) + let run = Task { await h.controller.runSetup(.imageMissing, continuing: false) } + await waitUntil("building") { h.backend.parkedCount(.fix) == 1 } + #expect(h.state == .settingUp(.imageMissing)) + #expect(h.controller.setupFolded) + h.backend.release(.fix) + #expect(await run.value == .ready) + } + + @Test func aWindowRunNeverFixesTheSameIssueTwice() async { + let h = Harness() + h.backend.queuePreflight(.issue(.serviceStopped)) + #expect(await h.controller.runSetup(.serviceStopped, continuing: true) + == .failed(.serviceStopped, SessionController.stillBrokenMessage)) + #expect(h.state == .idle) + #expect(h.backend.calls.filter { $0 == .fix(.serviceStopped) }.count == 1) + } + + @Test func anotherImageIssueAfterABuildIsTheSameFix() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageOutdated(found: 2, need: 3))) + #expect(await h.controller.runSetup(.imageMissing, continuing: true) + == .failed(.imageOutdated(found: 2, need: 3), SessionController.stillBrokenMessage)) + #expect(h.backend.calls.filter { if case .fix = $0 { true } else { false } }.count == 1) + } + + @Test func aFailedWindowRunLeavesNoCardWithoutALink() async { + let h = Harness() + h.backend.scriptFix(output: [], error: VMBackendError.failed("container build failed")) + #expect(await h.controller.runSetup(.imageMissing, continuing: true) == .failed(.imageMissing, "container build failed")) + #expect(h.state == .idle) + } + + @Test func aLinkDuringAWindowRunWaitsForTheWholeChain() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.backend.hold(.fix) + let run = Task { await h.controller.runSetup(.serviceStopped, continuing: true) } + await waitUntil("starting the service") { h.backend.parkedCount(.fix) == 1 } + h.controller.open(Harness.url) + #expect(h.state == .settingUp(.serviceStopped)) + #expect(!h.controller.setupFolded) // A waiting link shows its card. + h.backend.release(.fix) + #expect(await run.value == .ready) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(Array(h.backend.calls.prefix(5)) + == [.sweep(keeping: []), .fix(.serviceStopped), .preflight, .fix(.imageMissing), .preflight]) + } + + @Test func aBusyControllerRefusesTheWindow() async { + let h = Harness() + await h.goLive() + #expect(await h.controller.runSetup(.imageMissing, continuing: true) == nil) + #expect(await h.controller.checkSetup() == nil) + } + + @Test func checkingStartsAStoppedServiceButNeverBuilds() async { + let h = Harness() + h.backend.queuePreflight(.issue(.serviceStopped), .issue(.imageMissing)) + #expect(await h.controller.checkSetup() == .needs(.imageMissing)) + #expect(h.state == .idle) + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .fix(.serviceStopped), .preflight]) + } + + @Test func checkingOpensALinkACardWasHolding() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + #expect(await h.controller.checkSetup() == .ready) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(h.controller.openedURL == Harness.url) + } + + @Test func checkingAReadyMacFixesNothing() async { + let h = Harness() + #expect(await h.controller.checkSetup() == .ready) + #expect(!h.backend.calls.contains { if case .fix = $0 { true } else { false } }) + } + + @Test func setupOutputKeepsTheLast200Lines() async { + let h = Harness() + h.backend.scriptFix(output: (1...250).map { "line \($0)" }) + _ = await h.controller.runSetup(.imageMissing, continuing: false) + #expect(h.controller.setupOutput.count == 200) + #expect(h.controller.setupOutput.first == "line 51") + #expect(h.controller.setupOutput.last == "line 250") + } + + @Test func waitingForSetupReturnsOnlyWhenItEnds() async { + let h = Harness() + h.backend.hold(.fix) + let run = Task { await h.controller.runSetup(.imageMissing, continuing: false) } + await waitUntil("building") { h.backend.parkedCount(.fix) == 1 } + let waiter = Task { + await h.controller.waitWhileSettingUp() + return h.state + } + await Task.yield() + h.backend.release(.fix) + #expect(await waiter.value == .idle) + _ = await run.value + } +} diff --git a/Tests/KnotchyCoreTests/SessionSetupTests.swift b/Tests/KnotchyCoreTests/SessionSetupTests.swift new file mode 100644 index 0000000..0bc8caa --- /dev/null +++ b/Tests/KnotchyCoreTests/SessionSetupTests.swift @@ -0,0 +1,301 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SessionSetupTests { + @Test func aPreflightIssueNeedsSetupWithoutStartingAVM() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight]) + #expect(h.controller.liveIDs.snapshot.isEmpty) + } + + @Test func theSetupActionShowsItsOutputThenTheLinkOpens() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.backend.scriptFix(output: ["Step 1/9", "Step 9/9"]) + h.backend.hold(.fix) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + + h.controller.performSetupAction() + #expect(h.state == .settingUp(.imageMissing)) + await waitUntil("the output shows") { h.controller.setupLog == "Step 9/9" } + + h.backend.release(.fix) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(h.controller.openedURL == Harness.url) + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .fix(.imageMissing), .preflight, .start("knotchy-00000002")]) + } + + @Test func aSetupActionFoldsAwayAndItsLinkStartsFolded() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.backend.hold(.fix) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + h.controller.clickedOutside() + #expect(!h.controller.setupFolded) // Only a running action folds. + + h.controller.performSetupAction() + h.controller.clickedOutside() + #expect(h.controller.setupFolded) + h.controller.pointerEntered() + #expect(!h.controller.setupFolded) + h.controller.foldSetup() + #expect(h.controller.setupFolded) + + h.backend.release(.fix) + await waitUntil("loading, folded") { h.state == .starting(.loading, folded: true) } + #expect(!h.controller.setupFolded) + } + + @Test func aFoldedSetupOpenedByHoveringFoldsWhenThePointerLeaves() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.backend.hold(.fix) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + h.controller.performSetupAction() + h.controller.foldSetup() + + h.controller.pointerEntered() + #expect(!h.controller.setupFolded) + h.controller.pointerExited() + h.advance(by: SessionController.foldGrace) + #expect(h.controller.setupFolded) + + h.controller.showPanel() + h.controller.pointerEntered() + h.controller.pointerExited() + h.advance(by: SessionController.foldGrace) + #expect(!h.controller.setupFolded) + } + + @Test func aFailedSetupShowsTheErrorOnTheCard() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing)) + h.backend.scriptFix(output: [], error: VMBackendError.failed("container build failed")) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + h.controller.performSetupAction() + await waitUntil("the error shows") { + h.state == .setupRequired(.imageMissing, error: "container build failed") + } + } + + @Test func aSecondIssueAfterSetupShowsItsOwnCard() async { + let h = Harness() + h.backend.queuePreflight(.issue(.imageMissing), .issue(.imageOutdated(found: 2, need: 3))) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + h.controller.performSetupAction() + await waitUntil("the next issue") { h.state == .setupRequired(.imageOutdated(found: 2, need: 3), error: nil) } + } + + @Test func aMissingOrOldRuntimeOpensSetup() async { + for issue in [SetupIssue.cliMissing, .cliOutdated(found: "1.3.0")] { + let h = Harness() + h.backend.queuePreflight(.issue(issue)) + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(issue, error: nil) } + h.controller.performSetupAction() + #expect(h.recorder.setupRequests == 1) + #expect(h.state == .setupRequired(issue, error: nil)) + await h.controller.waitForBackendIdle() + #expect(!h.backend.calls.contains(.fix(issue))) + } + } + + @Test func lowDiskNeedsSetupAndHasNoAction() async { + let h = Harness() + h.disk.available = 1_000_000_000 + h.controller.open(Harness.url) + await waitUntil("setup required") { h.state == .setupRequired(.lowDisk(bytesFree: 1_000_000_000), error: nil) } + h.controller.performSetupAction() + #expect(h.state == .setupRequired(.lowDisk(bytesFree: 1_000_000_000), error: nil)) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls == [.sweep(keeping: []), .preflight]) + } + + @Test func dismissDropsThePendingLink() async { + // Only visible when setup runs again afterwards: here, a requested rebuild. + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: 20)), + .issue(.imageMissing)) + await h.goLive() + h.controller.requestImageUpdate() + h.controller.open(Harness.otherURL) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + + h.controller.dismiss() + #expect(h.state == .settingUp(.imageStale(builtDaysAgo: 20))) + await waitUntil("idle after the rebuild") { h.state == .idle } + #expect(!h.backend.calls.contains(.start("knotchy-00000003"))) + } + + @Test func aNewLinkDuringAnAutomaticStartOpensAfterIt() async { + let h = Harness() + h.backend.queuePreflight(.issue(.serviceStopped)) + h.backend.hold(.fix) + h.controller.open(Harness.url) + await waitUntil("starting the service") { h.backend.parkedCount(.fix) == 1 } + #expect(h.state == .settingUp(.serviceStopped)) + #expect(!h.controller.setupFolded) + h.controller.open(Harness.otherURL) + #expect(h.state == .settingUp(.serviceStopped)) + h.backend.release(.fix) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(h.controller.openedURL == Harness.otherURL) + } + + @Test func aStaleImageWarnsButNeverBlocks() async { + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: 20))) + await h.goLive() + #expect(h.controller.staleImage == StaleImage(builtDaysAgo: 20)) + } + + @Test func updateNowRebuildsOnceTheSessionEnds() async { + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: 20))) + await h.goLive() + h.controller.requestImageUpdate() + #expect(h.controller.staleImage == nil) + + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.advance(by: .milliseconds(1500)) + #expect(h.state == .settingUp(.imageStale(builtDaysAgo: 20))) + await waitUntil("idle after the rebuild") { h.state == .idle } + #expect(h.backend.calls.suffix(2) == [.fix(.imageStale(builtDaysAgo: 20)), .preflight]) + } + + @Test func aRebuildThatEndsInAnotherIssueShowsItsCard() async { + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: 20)), + .issue(.serviceStopped)) + await h.goLive() + h.controller.requestImageUpdate() + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.advance(by: .milliseconds(1500)) + await waitUntil("the next issue") { h.state == .setupRequired(.serviceStopped, error: nil) } + } + + @Test func aFailedRebuildStillOpensAPendingLink() async { + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: 20))) + h.backend.scriptFix(output: [], error: VMBackendError.failed("container build failed")) + h.backend.hold(.fix) + await h.goLive() + h.controller.requestImageUpdate() + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.advance(by: .milliseconds(1500)) + await waitUntil("rebuilding") { h.backend.parkedCount(.fix) == 1 } + + h.controller.open(Harness.otherURL) + #expect(h.state == .settingUp(.imageStale(builtDaysAgo: 20))) + h.backend.release(.fix) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(h.controller.openedURL == Harness.otherURL) + } + + @Test func aLinkOpenedAfterARebuildStartsUnengaged() async { + // The pointer was on the panel before it hid; nothing says it is there now. + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: 20))) + h.backend.hold(.fix) + await h.goLive() + h.controller.pointerEntered() + h.controller.requestImageUpdate() + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.advance(by: .milliseconds(1500)) + await waitUntil("rebuilding") { h.backend.parkedCount(.fix) == 1 } + h.controller.open(Harness.otherURL) + h.backend.release(.fix) + + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + h.connector.connections.last!.emit(.contentReady) + await waitUntil("live") { h.state == .live(.peek) } + h.advance(by: .seconds(30)) + #expect(h.state == .live(.collapsed)) + } + + @Test func aFreshImageBuildSatisfiesARequestedRebuild() async { + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: 20)), + .issue(.imageMissing)) + await h.goLive() + h.controller.requestImageUpdate() + h.controller.open(Harness.otherURL) + await waitUntil("setup required") { h.state == .setupRequired(.imageMissing, error: nil) } + h.controller.performSetupAction() + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.advance(by: .milliseconds(1500)) + #expect(h.state == .idle) + await h.controller.waitForBackendIdle() + #expect(h.backend.calls.filter { if case .fix = $0 { true } else { false } } == [.fix(.imageMissing)]) + } + + @Test func laterHidesTheWarningForThisSession() async { + let h = Harness() + h.backend.queuePreflight(.ready(appRoot: "/fake/appRoot", staleImage: StaleImage(builtDaysAgo: nil))) + await h.goLive() + h.controller.dismissStaleImageWarning() + #expect(h.controller.staleImage == nil) + + h.controller.destroy() + await waitUntil("destroyed") { h.state == .destroyed } + h.advance(by: .milliseconds(1500)) + #expect(h.state == .idle) + } + + @Test func aStoppedServiceStartsByItselfAndTheLinkOpens() async { + let h = Harness() + h.backend.queuePreflight(.issue(.serviceStopped)) + h.controller.open(Harness.url) + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(h.backend.calls == [.sweep(keeping: []), .preflight, .fix(.serviceStopped), .preflight, + .start("knotchy-00000002")]) + } + + @Test func aServiceThatWontStayUpShowsItsCardOnce() async { + let h = Harness() + h.backend.queuePreflight(.issue(.serviceStopped), .issue(.serviceStopped)) + h.controller.open(Harness.url) + await waitUntil("setup required") { + h.state == .setupRequired(.serviceStopped, error: SessionController.stillBrokenMessage) + } + #expect(h.backend.calls.filter { $0 == .fix(.serviceStopped) }.count == 1) + + h.backend.queuePreflight(.issue(.serviceStopped)) + h.controller.open(Harness.url) // A new request gets its own automatic start. + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(h.backend.calls.filter { $0 == .fix(.serviceStopped) }.count == 2) + } + + @Test func retryStartsTheServiceAgain() async { + let h = Harness() + h.backend.queuePreflight(.issue(.serviceStopped)) + h.backend.failStarts(with: VMBackendError.failed("boot failed")) + h.controller.open(Harness.url) + await waitUntil("the start fails") { if case .failed(.start) = h.state { true } else { false } } + + h.backend.failStarts(with: nil) + h.backend.queuePreflight(.issue(.serviceStopped)) + h.controller.retry() + await waitUntil("loading") { h.state == .starting(.loading, folded: false) } + #expect(h.backend.calls.filter { $0 == .fix(.serviceStopped) }.count == 2) + } +} diff --git a/Tests/KnotchyCoreTests/SetupFlowTests.swift b/Tests/KnotchyCoreTests/SetupFlowTests.swift new file mode 100644 index 0000000..06cc12e --- /dev/null +++ b/Tests/KnotchyCoreTests/SetupFlowTests.swift @@ -0,0 +1,242 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +@Suite(.timeLimit(.minutes(1))) +struct SetupFlowTests { + let host = FakeHost() + let installer = FakeInstaller() + let driver = FakeSetupDriver() + + func makeFlow() -> SetupFlow { + SetupFlow(host: host, installer: installer, driver: driver, retryDelay: .zero) + } + + @Test func aReadyMacShowsEveryRowDone() async { + let flow = makeFlow() + await flow.refresh() + #expect(flow.mac == .done("macOS 27.0 · 212 GB free")) + #expect(flow.runtime == .done("container 1.4.1 installed")) + #expect(flow.vm == .done("Ready")) + #expect(flow.allRequiredDone) + #expect(!flow.canSetUp) + } + + @Test func aMissingRuntimeOffersAnInstallWithoutCheckingTheVM() async { + driver.runtimeStatuses = [.missing] + let flow = makeFlow() + await flow.refresh() + #expect(flow.runtimeAction == .install) + #expect(flow.runtime == .pending("Apple's container 1.4.1 · 118 MB · installs with Apple's Installer")) + #expect(flow.vm == .pending("About 3 minutes · downloads about 225 MB")) + #expect(flow.canSetUp) + #expect(driver.calls == ["runtimeStatus"]) + } + + @Test func anOldRuntimeOffersAnUpdate() async { + driver.runtimeStatuses = [.outdated("1.3.0")] + let flow = makeFlow() + await flow.refresh() + #expect(flow.runtimeAction == .update) + #expect(flow.runtime == .pending("container 1.3.0 is too old · updates to 1.4.1 with Apple's Installer")) + } + + @Test func aMacThatCantRunVirtualMachinesShowsOnlyThat() async { + host.current.unsupported = .noVirtualization + let flow = makeFlow() + await flow.refresh() + #expect(flow.unsupported == .noVirtualization) + #expect(driver.calls.isEmpty) + #expect(!flow.canSetUp) + } + + @Test func lowDiskBlocksOnlyWhileSomethingNeedsSpace() async { + host.current.bytesFree = 2_500_000_000 + driver.runtimeStatuses = [.missing] + let flow = makeFlow() + await flow.refresh() + #expect(flow.mac == .failed("Setting up needs about 3 GB free. 2.5 GB is free.", details: [])) + #expect(!flow.canSetUp) + + driver.runtimeStatuses = [.current("1.4.1")] + await flow.refresh() + #expect(flow.mac == .done("macOS 27.0 · 2.5 GB free")) + #expect(flow.allRequiredDone) + } + + @Test func installingWithInstallerFinishesTheChain() async throws { + driver.runtimeStatuses = [.missing, .current("1.4.1")] + installer.session.hasQuit = false + let flow = makeFlow() + await flow.setUp() + #expect(driver.calls == ["runtimeStatus", "begin(nil)", "runtimeStatus", "finish(installed)"]) + #expect(flow.mac == .done("macOS 27.0 · 212 GB free")) + #expect(flow.runtime == .done("container 1.4.1 installed")) + #expect(flow.vm == .done("Ready")) + #expect(flow.allRequiredDone) + + // Installer still reads the package while its summary is open. + try await Task.sleep(for: .milliseconds(50)) + #expect(installer.calls == ["download", "open"]) + installer.session.hasQuit = true + await waitUntil("the package is discarded") { installer.calls.contains("discard") } + #expect(installer.calls == ["download", "open", "discard"]) + } + + @Test func anUpdatePassesTheOldVersionOn() async { + driver.runtimeStatuses = [.outdated("1.3.0"), .current("1.4.1")] + let flow = makeFlow() + await flow.setUp() + #expect(driver.calls.contains("begin(1.3.0)")) + #expect(flow.allRequiredDone) + } + + @Test func aMismatchedDownloadIsNeverInstalled() async { + driver.runtimeStatuses = [.missing] + installer.failDownload(with: .mismatch) + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .failed("The download didn't match the expected package, so it wasn't installed.", details: [])) + #expect(installer.calls == ["download"]) + #expect(!driver.calls.contains { $0.hasPrefix("begin") }) + } + + @Test func anOfflineDownloadSaysSo() async { + driver.runtimeStatuses = [.missing] + installer.failDownload(with: .failed("The Internet connection appears to be offline.")) + let flow = makeFlow() + await flow.setUp() + guard case .failed(let message, _) = flow.runtime else { + Issue.record("Expected a failure") + return + } + #expect(message == "Couldn't download the runtime. Check your connection and try again.") + } + + @Test func quittingInstallerWithoutInstallingIsCancelled() async { + driver.runtimeStatuses = [.missing] + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .pending("Installing was cancelled.")) + #expect(flow.runtimeAction == .install) + #expect(flow.canSetUp) + #expect(driver.calls == ["runtimeStatus", "begin(nil)", "runtimeStatus", "runtimeStatus", "finish(cancelled)"]) + await waitUntil("the package is discarded") { installer.calls.contains("discard") } + #expect(installer.calls == ["download", "open", "discard"]) + } + + @Test func aChangedDownloadIsNotOpened() async { + driver.runtimeStatuses = [.missing] + installer.open(with: .changed) + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .failed("The download changed after it was checked, so it wasn't opened.", details: [])) + #expect(installer.calls == ["download", "open"]) + #expect(driver.calls.last?.hasPrefix("finish(failed") == true) + } + + @Test func installerThatWontOpenSaysSo() async { + driver.runtimeStatuses = [.missing] + installer.open(with: .failed("x")) + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .failed("Couldn't open Installer.", details: ["x"])) + #expect(installer.calls == ["download", "open", "discard"]) + #expect(flow.canSetUp) + #expect(driver.calls.last?.hasPrefix("finish(failed") == true) + } + + @Test func anUpdateWaitsForTheBrowserToClose() async { + driver.runtimeStatuses = [.outdated("1.3.0")] + driver.holdResult = .failure(.sessionOpen) + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .failed("Close the Knotchy browser first.", details: [])) + #expect(installer.calls == ["download", "discard"]) + } + + @Test func quittingWhileInstallerIsOpenLeavesItToFinish() async { + driver.runtimeStatuses = [.missing] + installer.session.hasQuit = false + let flow = makeFlow() + flow.start() + await waitUntil("Installer is open") { flow.runtime == .running("Finish the install in Installer…", fraction: nil) } + flow.cancel() + await waitUntil("the flow stops") { !flow.isRunning } + #expect(!driver.calls.contains { $0.hasPrefix("finish(") }) + #expect(!installer.calls.contains("discard")) + installer.session.hasQuit = true // Ends the cleanup task. + } + + @Test func aClosingBrowserIsWaitedFor() async { + driver.runtimeStatuses = [.outdated("1.3.0")] + for state in [SessionState.destroying, .failed(.destroyStuck)] { + driver.state = state + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .failed("Wait for the Knotchy browser to finish closing, then try again.", details: [])) + } + #expect(installer.calls.isEmpty) + } + + @Test func aFailedServiceStartStopsSetUp() async { + driver.checkResults = [.failed(.serviceStopped, "Couldn't start the service: x")] + driver.setupOutput = ["x"] + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .failed("Couldn't start the service: x", details: ["x"])) + #expect(driver.calls.filter { $0 == "checkSetup" }.count == 1) + #expect(!driver.calls.contains { $0.hasPrefix("runSetup") }) + } + + @Test func aBuildFailureShowsItsOutput() async { + driver.checkResults = [.needs(.imageMissing), .needs(.imageMissing)] + driver.runResults = [.failed(.imageMissing, "The image build failed: out of space")] + driver.setupOutput = ["Step 7/9", "error: out of space"] + let flow = makeFlow() + await flow.setUp() + #expect(flow.vm == .failed("The image build failed: out of space", details: ["Step 7/9", "error: out of space"])) + #expect(driver.calls.contains("runSetup(imageMissing, continuing: true)")) + } + + @Test func aSetupTheNotchStartedIsFollowed() async { + driver.state = .settingUp(.imageMissing) + driver.checkResults = [nil, nil, .ready] + let flow = makeFlow() + await flow.setUp() + #expect(driver.calls.contains("wait")) + #expect(flow.vm == .done("Ready")) + } + + @Test func theRowsShowTheControllersProgress() { + let flow = makeFlow() + driver.state = .settingUp(.imageMissing) + driver.setupLog = "Step 3/9" + #expect(flow.vm == .running("Step 3/9", fraction: nil)) + driver.state = .settingUp(.serviceStopped) + #expect(flow.runtime == .running("Starting the runtime…", fraction: nil)) + } + + @Test func anOpenBrowserCountsAsAReadyVM() async { + driver.state = .live(.peek) + driver.checkResults = [nil] + let flow = makeFlow() + await flow.refresh() + #expect(flow.vm == .done("Ready")) + #expect(flow.allRequiredDone) + } + + @Test func anOpenBrowserIsReportedBeforeDownloadingAnUpdate() async { + driver.state = .live(.peek) + driver.runtimeStatuses = [.outdated("1.3.0")] + let flow = makeFlow() + await flow.setUp() + #expect(flow.runtime == .failed("Close the Knotchy browser first.", details: [])) + #expect(installer.calls.isEmpty) + } + + @Test func downloadProgressReadsInMegabytes() { + #expect(SetupFlow.downloadText(58_886_932, of: ContainerRuntime.pinned.size) == "Downloading… 59 of 118 MB") + } +} diff --git a/Tests/KnotchyCoreTests/Support/Fakes.swift b/Tests/KnotchyCoreTests/Support/Fakes.swift new file mode 100644 index 0000000..1d293c1 --- /dev/null +++ b/Tests/KnotchyCoreTests/Support/Fakes.swift @@ -0,0 +1,336 @@ +import Foundation +import Synchronization +import Testing +@testable import KnotchyCore + +/// A forgotten `release` fails the test after this instead of hanging the run. +private let heldCallLimit: Duration = .seconds(10) + +final class FakeVMBackend: VMBackend { + enum Call: Equatable, Sendable { + case sweep(keeping: Set) + case preflight + case start(String) + case destroy(String) + case forceDestroy(String) + case fix(SetupIssue) + case destroyBlocking([String]) + case stopService + + var kind: Kind { + switch self { + case .sweep: .sweep + case .preflight: .preflight + case .start: .start + case .destroy: .destroy + case .forceDestroy: .forceDestroy + case .fix: .fix + case .destroyBlocking: .destroyBlocking + case .stopService: .stopService + } + } + } + + enum Kind: Hashable, Sendable { + case sweep, preflight, start, destroy, forceDestroy, fix, destroyBlocking, stopService + } + + struct StartArguments: Equatable, Sendable { + var id: String + var url: URL + var renderSize: PixelSize + var blockCIDRs: [String] + } + + private struct State { + var calls: [Call] = [] + var startArguments: [StartArguments] = [] + var active = 0 + var maxActive = 0 + var held: Set = [] + var nextParkID = 0 + var parked: [Kind: [(id: Int, continuation: CheckedContinuation)]] = [:] + var preflightResults: [PreflightResult] = [] + var startError: (any Error)? + var destroyError: (any Error)? + var forceDestroyError: (any Error)? + var fixOutput: [String] = [] + var fixError: (any Error)? + var runtimeStatus: RuntimeStatus = .current("1.4.1") + var stopError: (any Error)? + } + + private let state = Mutex(State()) + + var calls: [Call] { state.withLock { $0.calls } } + var startArguments: [StartArguments] { state.withLock { $0.startArguments } } + var maxConcurrentCalls: Int { state.withLock { $0.maxActive } } + + /// Used in order; after them, preflight is ready. + func queuePreflight(_ results: PreflightResult...) { + state.withLock { $0.preflightResults += results } + } + + func failStarts(with error: (any Error)?) { state.withLock { $0.startError = error } } + func failDestroys(with error: (any Error)?) { state.withLock { $0.destroyError = error } } + func failForceDestroys(with error: (any Error)?) { state.withLock { $0.forceDestroyError = error } } + + func scriptFix(output: [String], error: (any Error)? = nil) { + state.withLock { + $0.fixOutput = output + $0.fixError = error + } + } + + func hold(_ kind: Kind) { + precondition(kind != .destroyBlocking, "destroyBlocking is synchronous and can't be held") + _ = state.withLock { $0.held.insert(kind) } + } + + func release(_ kind: Kind) { + let parked = state.withLock { s in + s.held.remove(kind) + return s.parked.removeValue(forKey: kind) ?? [] + } + for call in parked { call.continuation.resume() } + } + + func parkedCount(_ kind: Kind) -> Int { + state.withLock { $0.parked[kind]?.count ?? 0 } + } + + func sweepOrphans(keeping: Set) async { + await begin(.sweep(keeping: keeping)) + end() + } + + func preflight() async -> PreflightResult { + await begin(.preflight) + defer { end() } + return state.withLock { s in + s.preflightResults.isEmpty ? .ready(appRoot: "/fake/appRoot", staleImage: nil) : s.preflightResults.removeFirst() + } + } + + func start(id: String, url: URL, renderSize: PixelSize, blockCIDRs: [String]) async throws -> VMSession { + state.withLock { + $0.startArguments.append(StartArguments(id: id, url: url, renderSize: renderSize, blockCIDRs: blockCIDRs)) + } + await begin(.start(id)) + defer { end() } + if let error = state.withLock({ $0.startError }) { throw error } + return VMSession(id: id, socketPath: "/fake/\(id).sock") + } + + func destroy(id: String) async throws { + await begin(.destroy(id)) + defer { end() } + if let error = state.withLock({ $0.destroyError }) { throw error } + } + + func forceDestroy(id: String) async throws { + await begin(.forceDestroy(id)) + defer { end() } + if let error = state.withLock({ $0.forceDestroyError }) { throw error } + } + + func fix(_ issue: SetupIssue, output: @escaping @Sendable (String) -> Void) async throws { + record(.fix(issue)) + state.withLock { $0.fixOutput }.forEach(output) + await parkIfHeld(.fix) + defer { end() } + if let error = state.withLock({ $0.fixError }) { throw error } + } + + func destroyBlocking(ids: [String], deadline: Duration) { + state.withLock { $0.calls.append(.destroyBlocking(ids)) } + } + + func setRuntimeStatus(_ status: RuntimeStatus) { state.withLock { $0.runtimeStatus = status } } + func failStopService(with error: (any Error)?) { state.withLock { $0.stopError = error } } + + func runtimeStatus() async -> RuntimeStatus { + state.withLock { $0.runtimeStatus } + } + + func stopService() async throws { + await begin(.stopService) + defer { end() } + if let error = state.withLock({ $0.stopError }) { throw error } + } + + private func begin(_ call: Call) async { + record(call) + await parkIfHeld(call.kind) + } + + private func record(_ call: Call) { + state.withLock { s in + s.calls.append(call) + s.active += 1 + s.maxActive = max(s.maxActive, s.active) + } + } + + private func end() { + state.withLock { $0.active -= 1 } + } + + private func parkIfHeld(_ kind: Kind) async { + await withCheckedContinuation { (continuation: CheckedContinuation) in + let id: Int? = state.withLock { s in + guard s.held.contains(kind) else { return nil } + s.nextParkID += 1 + s.parked[kind, default: []].append((s.nextParkID, continuation)) + return s.nextParkID + } + guard let id else { return continuation.resume() } + Task { + try? await Task.sleep(for: heldCallLimit) + let forgotten = self.state.withLock { s -> CheckedContinuation? in + guard let index = s.parked[kind]?.firstIndex(where: { $0.id == id }) else { return nil } + return s.parked[kind]?.remove(at: index).continuation + } + if let forgotten { + Issue.record("A held \(kind) call was never released") + forgotten.resume() + } + } + } + } +} + +/// Queued errors go to connects in call order; `connections` is in completion order. +final class FakeStreamConnector: StreamConnector { + private struct State { + var socketPaths: [String] = [] + var connections: [FakeStreamConnection] = [] + var errors: [StreamError] = [] + var held = false + var nextParkID = 0 + var parked: [(id: Int, continuation: CheckedContinuation)] = [] + } + + private let state = Mutex(State()) + + var socketPaths: [String] { state.withLock { $0.socketPaths } } + var connections: [FakeStreamConnection] { state.withLock { $0.connections } } + var parkedCount: Int { state.withLock { $0.parked.count } } + + func failNextConnect(with error: StreamError) { + state.withLock { $0.errors.append(error) } + } + + func hold() { + state.withLock { $0.held = true } + } + + func release() { + let parked = state.withLock { s in + s.held = false + defer { s.parked = [] } + return s.parked + } + for call in parked { call.continuation.resume() } + } + + func connect(socketPath: String) async throws -> any StreamConnection { + let scriptedError = state.withLock { s -> StreamError? in + s.socketPaths.append(socketPath) + return s.errors.isEmpty ? nil : s.errors.removeFirst() + } + await withCheckedContinuation { (continuation: CheckedContinuation) in + let id: Int? = state.withLock { s in + guard s.held else { return nil } + s.nextParkID += 1 + s.parked.append((s.nextParkID, continuation)) + return s.nextParkID + } + guard let id else { return continuation.resume() } + Task { + try? await Task.sleep(for: heldCallLimit) + let forgotten = self.state.withLock { s -> CheckedContinuation? in + guard let index = s.parked.firstIndex(where: { $0.id == id }) else { return nil } + return s.parked.remove(at: index).continuation + } + if let forgotten { + Issue.record("A held connect was never released") + forgotten.resume() + } + } + } + if let scriptedError { throw scriptedError } + let connection = FakeStreamConnection(socketPath: socketPath) + state.withLock { $0.connections.append(connection) } + return connection + } +} + +final class FakeStreamConnection: StreamConnection { + let socketPath: String + let events: AsyncStream + private let continuation: AsyncStream.Continuation + + private struct State { + var decodeEnabled = true + var decodeChanges: [Bool] = [] + var sent: [InputEvent] = [] + var closed = false + } + + private let state = Mutex(State()) + + init(socketPath: String) { + self.socketPath = socketPath + let (stream, continuation) = AsyncStream.makeStream(of: StreamEvent.self) + events = stream + self.continuation = continuation + } + + var decodeEnabled: Bool { + get { state.withLock { $0.decodeEnabled } } + set { + state.withLock { + $0.decodeEnabled = newValue + $0.decodeChanges.append(newValue) + } + } + } + + var decodeChanges: [Bool] { state.withLock { $0.decodeChanges } } + var sent: [InputEvent] { state.withLock { $0.sent } } + var isClosed: Bool { state.withLock { $0.closed } } + + func setFrameHandler(_ handler: @escaping @Sendable (DecodedFrame) -> Void) {} + + func send(_ input: InputEvent) { + state.withLock { $0.sent.append(input) } + } + + func close() { + state.withLock { $0.closed = true } + continuation.finish() + } + + func emit(_ event: StreamEvent) { + continuation.yield(event) + } + + /// Without an `.ended` event, as a buggy client might. + func finishEvents() { + continuation.finish() + } +} + +final class FakeDiskSpace: DiskSpaceChecking { + private let free = Mutex(100_000_000_000) + + var available: Int64? { + get { free.withLock { $0 } } + set { free.withLock { $0 = newValue } } + } + + func bytesFree(atPath path: String) -> Int64? { + free.withLock { $0 } + } +} diff --git a/Tests/KnotchyCoreTests/Support/Harness.swift b/Tests/KnotchyCoreTests/Support/Harness.swift new file mode 100644 index 0000000..31214f9 --- /dev/null +++ b/Tests/KnotchyCoreTests/Support/Harness.swift @@ -0,0 +1,87 @@ +import Foundation +import Testing +@testable import KnotchyCore + +@MainActor +final class Recorder { + var quitRequests = 0 + var setupRequests = 0 +} + +@MainActor +final class Harness { + static let url = URL(string: "https://example.com/")! + static let otherURL = URL(string: "https://example.org/")! + + let backend = FakeVMBackend() + let connector = FakeStreamConnector() + let disk = FakeDiskSpace() + let clock = TestClock() + let recorder: Recorder + let controller: SessionController + + init(hostNetworks: [String] = ["192.168.1.0/24"]) { + let recorder = Recorder() + self.recorder = recorder + var sessions = 0 + controller = SessionController( + backend: backend, + connector: connector, + diskSpace: disk, + clock: clock, + hostNetworks: { hostNetworks }, + requestQuit: { recorder.quitRequests += 1 }, + makeSessionID: { + sessions += 1 + return String(format: "knotchy-%08x", sessions) + } + ) + controller.showSetup = { recorder.setupRequests += 1 } + } + + /// Lets go of held calls, so the fakes' 10 s check can't fire later against another test. + deinit { + for kind in [FakeVMBackend.Kind.sweep, .preflight, .start, .destroy, .forceDestroy, .fix, .stopService] { + backend.release(kind) + } + connector.release() + for stream in connector.connections { stream.close() } + } + + var state: SessionState { controller.state } + + func advance(by duration: Duration) { + clock.advance(by: duration) + controller.fireDueTimers() + } + + @discardableResult + func openAndConnect(_ url: URL = Harness.url) async -> FakeStreamConnection { + let before = connector.connections.count + controller.open(url) + await waitUntil("the stream connects") { + self.connector.connections.count > before && self.state == .starting(.loading, folded: false) + } + return connector.connections.last! + } + + func enter(_ mode: LiveMode) { + switch mode { + case .peek: + break + case .expanded: + controller.pointerEntered() + controller.expand() + case .collapsed: + controller.clickedOutside() + } + } + + @discardableResult + func goLive(_ url: URL = Harness.url) async -> FakeStreamConnection { + let stream = await openAndConnect(url) + stream.emit(.contentReady) + await waitUntil("the session is live") { self.state == .live(.peek) } + return stream + } +} diff --git a/Tests/KnotchyCoreTests/Support/SetupFakes.swift b/Tests/KnotchyCoreTests/Support/SetupFakes.swift new file mode 100644 index 0000000..394a745 --- /dev/null +++ b/Tests/KnotchyCoreTests/Support/SetupFakes.swift @@ -0,0 +1,117 @@ +import Foundation +import Synchronization +@testable import KnotchyCore + +final class FakeHost: HostChecking { + private let value = Mutex(HostFacts(unsupported: nil, osVersion: "macOS 27.0", bytesFree: 212_000_000_000)) + + var current: HostFacts { + get { value.withLock { $0 } } + set { value.withLock { $0 = newValue } } + } + + func facts() async -> HostFacts { current } +} + +@MainActor +final class FakeInstallerSession: InstallerSession { + var hasQuit: Bool + + nonisolated init(hasQuit: Bool = true) { + self.hasQuit = hasQuit + } +} + +final class FakeInstaller: RuntimeInstalling { + enum Opening: Sendable { + case opened + case changed + case failed(String) + } + + private struct State { + var downloadError: DownloadError? + var opening: Opening = .opened + var calls: [String] = [] + } + + private let state = Mutex(State()) + /// What `.opened` hands out; it has already quit unless a test says otherwise. + let session = FakeInstallerSession() + + var calls: [String] { state.withLock { $0.calls } } + func failDownload(with error: DownloadError) { state.withLock { $0.downloadError = error } } + func open(with opening: Opening) { state.withLock { $0.opening = opening } } + + func download(_ release: ContainerRuntime.Release, + progress: @escaping @Sendable (Int64) -> Void) async throws -> DownloadedPackage { + let error = state.withLock { s -> DownloadError? in + s.calls.append("download") + return s.downloadError + } + if let error { throw error } + return DownloadedPackage(directory: URL(fileURLWithPath: "/fake/knotchy-runtime"), name: release.url.lastPathComponent) + } + + @MainActor + func openInInstaller(_ package: DownloadedPackage, release: ContainerRuntime.Release) async -> InstallerOpening { + let opening = state.withLock { s -> Opening in + s.calls.append("open") + return s.opening + } + switch opening { + case .opened: return .opened(session) + case .changed: return .changed + case .failed(let message): return .failed(message) + } + } + + func discard(_ package: DownloadedPackage) { + state.withLock { $0.calls.append("discard") } + } +} + +@MainActor +final class FakeSetupDriver: SetupDriving { + var state: SessionState = .idle + var setupLog: String? + var setupOutput: [String] = [] + /// Used in order; the last one repeats. + var runtimeStatuses: [RuntimeStatus] = [.current("1.4.1")] + /// Used in order; then `.ready`. + var checkResults: [SetupRunOutcome?] = [] + var runResults: [SetupRunOutcome?] = [] + var holdResult: Result = .success(()) + var finishResult: SetupRunOutcome = .ready + private(set) var calls: [String] = [] + + func runtimeStatus() async -> RuntimeStatus { + calls.append("runtimeStatus") + return runtimeStatuses.count > 1 ? runtimeStatuses.removeFirst() : runtimeStatuses[0] + } + + func checkSetup() async -> SetupRunOutcome? { + calls.append("checkSetup") + return checkResults.isEmpty ? .ready : checkResults.removeFirst() + } + + func runSetup(_ issue: SetupIssue, continuing: Bool) async -> SetupRunOutcome? { + calls.append("runSetup(\(issue), continuing: \(continuing))") + return runResults.isEmpty ? .ready : runResults.removeFirst() + } + + func beginRuntimeInstall(replacing found: String?) async -> Result { + calls.append("begin(\(found ?? "nil"))") + return holdResult + } + + func finishRuntimeInstall(_ result: RuntimeInstallResult) async -> SetupRunOutcome { + calls.append("finish(\(result))") + return finishResult + } + + func waitWhileSettingUp() async { + calls.append("wait") + state = .idle + } +} diff --git a/Tests/KnotchyCoreTests/Support/TestClock.swift b/Tests/KnotchyCoreTests/Support/TestClock.swift new file mode 100644 index 0000000..884723a --- /dev/null +++ b/Tests/KnotchyCoreTests/Support/TestClock.swift @@ -0,0 +1,66 @@ +import Synchronization +@testable import KnotchyCore + +final class TestClock: KnotchyClock { + private struct Sleeper { + let id: Int + let deadline: ContinuousClock.Instant + let continuation: CheckedContinuation + } + + private struct State { + var now: ContinuousClock.Instant + var nextID = 0 + var sleepers: [Sleeper] = [] + } + + private let state: Mutex + + init(now: ContinuousClock.Instant = .now) { + state = Mutex(State(now: now)) + } + + var now: ContinuousClock.Instant { + state.withLock { $0.now } + } + + var sleeperCount: Int { + state.withLock { $0.sleepers.count } + } + + func sleep(until deadline: ContinuousClock.Instant) async throws { + let id = state.withLock { s in + s.nextID += 1 + return s.nextID + } + try await withTaskCancellationHandler { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + let outcome: Result? = state.withLock { s in + if Task.isCancelled { return .failure(CancellationError()) } + if deadline <= s.now { return .success(()) } + s.sleepers.append(Sleeper(id: id, deadline: deadline, continuation: continuation)) + return nil + } + if let outcome { continuation.resume(with: outcome) } + } + } onCancel: { + let sleeper = state.withLock { s -> Sleeper? in + guard let index = s.sleepers.firstIndex(where: { $0.id == id }) else { return nil } + return s.sleepers.remove(at: index) + } + sleeper?.continuation.resume(throwing: CancellationError()) + } + } + + /// The woken tasks run concurrently, in no particular order. + func advance(by duration: Duration) { + let due = state.withLock { s -> [Sleeper] in + s.now += duration + let now = s.now + let due = s.sleepers.filter { $0.deadline <= now }.sorted { $0.deadline < $1.deadline } + s.sleepers.removeAll { $0.deadline <= now } + return due + } + for sleeper in due { sleeper.continuation.resume() } + } +} diff --git a/Tests/KnotchyCoreTests/Support/Waiting.swift b/Tests/KnotchyCoreTests/Support/Waiting.swift new file mode 100644 index 0000000..d753101 --- /dev/null +++ b/Tests/KnotchyCoreTests/Support/Waiting.swift @@ -0,0 +1,19 @@ +import Testing + +/// For work on other executors; controller timers run on the `TestClock` and never need it. +@MainActor +func waitUntil( + _ description: String, + timeout: Duration = .seconds(5), + sourceLocation: SourceLocation = #_sourceLocation, + _ condition: @MainActor () -> Bool +) async { + let deadline = ContinuousClock.now + timeout + while !condition() { + if ContinuousClock.now >= deadline { + Issue.record("Timed out waiting until \(description)", sourceLocation: sourceLocation) + return + } + do { try await Task.sleep(for: .milliseconds(1)) } catch { return } + } +} diff --git a/Tests/KnotchyCoreTests/WireProtocolTests.swift b/Tests/KnotchyCoreTests/WireProtocolTests.swift new file mode 100644 index 0000000..eb33ff6 --- /dev/null +++ b/Tests/KnotchyCoreTests/WireProtocolTests.swift @@ -0,0 +1,216 @@ +import Foundation +import Testing +@testable import KnotchyCore + +struct WireProtocolTests { + private let size = PixelSize(width: 1024, height: 640) + + private func decodeAll(_ bytes: Data, into decoder: inout AgentMessageDecoder) throws -> [AgentMessage] { + decoder.append(bytes) + var messages: [AgentMessage] = [] + while let message = try decoder.next() { messages.append(message) } + return messages + } + + private func decodeAll(_ bytes: Data) throws -> [AgentMessage] { + var decoder = AgentMessageDecoder() + return try decodeAll(bytes, into: &decoder) + } + + private func violation(_ bytes: Data) -> AgentMessageDecoder.ProtocolViolation? { + do { + _ = try decodeAll(bytes) + return nil + } catch { + return error as? AgentMessageDecoder.ProtocolViolation + } + } + + private func raw(_ type: UInt8, _ payload: [UInt8]) -> Data { + framed(type, Data(payload)) + } + + @Test func matchesTheImageProtocolVersion() throws { + let root = URL(filePath: #filePath).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() + let containerfile = try String(contentsOf: root.appending(path: "vm/Containerfile"), encoding: .utf8) + let arg = containerfile.split(separator: "\n").first { $0.hasPrefix("ARG KNOTCHY_PROTOCOL=") } + #expect(arg == "ARG KNOTCHY_PROTOCOL=\(WireProtocol.version)") + } + + @Test func pageURLMustBeShortUTF8() throws { + let hello = raw(MessageType.hello, [1, 0x04, 0x00, 0x02, 0x80]) + #expect(try decodeAll(hello + raw(MessageType.pageURL, Array("https://a.test/ü".utf8))) + == [.hello(version: 1, size: size), .pageURL("https://a.test/ü")]) + #expect(violation(hello + raw(MessageType.pageURL, [0x68, 0xC3])) != nil) // cut-off UTF-8 + #expect(violation(hello + raw(MessageType.pageURL, [])) != nil) + let long = [UInt8](repeating: 0x61, count: WireProtocol.maxPageURL + 1) + #expect(violation(hello + raw(MessageType.pageURL, long)) != nil) + } + + @Test func helloAndVideoBytesAreAsSpecified() throws { + #expect(AgentMessage.hello(version: 1, size: size).encoded() == Data([0x01, 0, 0, 0, 5, 1, 0x04, 0x00, 0x02, 0x80])) + let hello = raw(MessageType.hello, [1, 0x04, 0x00, 0x02, 0x80]) + let video = raw(MessageType.video, [0x04, 0x00, 0x02, 0x80, 0xAA]) + #expect(try decodeAll(hello + video) == [.hello(version: 1, size: size), .video(size: size, accessUnit: Data([0xAA]))]) + } + + @Test func roundTripsEveryAgentMessage() throws { + let messages: [AgentMessage] = [ + .hello(version: 1, size: size), + .navigated, + .progress(0), + .progress(100), + .video(size: size, accessUnit: Data([0, 0, 0, 1, 0x09, 0xF0])), + .pageURL("https://example.com/a?b=c#d"), + .unknown(type: 0x7F), + ] + let stream = messages.reduce(into: Data()) { $0.append($1.encoded()) } + #expect(try decodeAll(stream) == messages) + } + + @Test func decodesAcrossArbitraryChunkBoundaries() throws { + let messages: [AgentMessage] = [ + .hello(version: 1, size: size), + .video(size: size, accessUnit: Data(repeating: 0xAB, count: 3000)), + .progress(42), + ] + let stream = messages.reduce(into: Data()) { $0.append($1.encoded()) } + var decoder = AgentMessageDecoder() + var decoded: [AgentMessage] = [] + for chunkStart in stride(from: 0, to: stream.count, by: 7) { + decoded += try decodeAll(stream.subdata(in: chunkStart..S4ZQK2|Ic?jvZQHhO_s+j(&+cCA%cai$di4 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%0A8%-`lVse+ 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�lOQ!?Z|)v7AiH<*``6*pO?ZrRu`RxupnQuhuThDYuW zp0*JhHtTsiyaNWHmigKLX-@xRXd>SQfqj488!g-#AzXR{uuy2cwX8pT0KjPB ziI@RKD-6H1xpTT{587Yr#FR~#rZL}o<3K0+yoAoFH%X$2F2Tpud5&DMk=xfB+Cwva?fY8o#+PG}1oixtB!I!F0Kl|`iiTF438+*6p6?MeoZT`AR|pG!c>$)|4e zRc%+d$V%I8dNdYkR;!PTE;}7GBiVER+VssC{N&=Lk1_nD3MB^s6hzR_gdWFh{`OWv zoJoryCPbFUCJTJoLpl&$F(dTJ3)d=40QbG~bMWp99;8T{=E>^<5KC?=IMG}q1YqT3 zN@3~XK5bagalu-}z4o)}%Vw@j(iUvGtn)^S(h(V8PT_yR$B7xJo*m05N*nN`6FVx1 ztYljVysZ3JIXTbMdHO+?7E-KZM7ZYEp$Sl36<>ID*b`yofHvG{v^WR!oG_UeS0hwhpu%k^u7wtTY8uo2ZOr=lNfHU_;~y*@b*7@EiNqc`@Z24V)~uRT!b5m>;7;n{kZGUnE_aYx zsOR$|h3DBSMMgrsWzIcE?=(5GO4gczgx^*nm6_ke_UPz`pOO6OTf=ys!JuQDg8jKx zZed{dKlB;Q170&@RCjJvcIERdbw1$g4pQ! z7SI{5|N9Pj7_1=7`CTRz-EtSH-&=Tb%OH%>x)F6$j}KIZFJXCsek>E&-^MxrJJqfK@c!JNGy0u_w(UVaztV$`i=XPe_sqW)w&A%2(kEh9~lYc7QCW+qO)FKb2STB~Y0tQ?FUvKC6Z zY*)j9uQwH=>~UMqTRQQm-slU%pZAEuc#@`9t|R;miZ(UW@3}Hf7Fd@wG;aLDC*~^; zeE>;15L@$cv(OdZ&EUKc$9V5QzO!)D=#$8|3(Ys4xhvKNP{UPg%BhAioj&b$&$Y~K z3@h>MtFOS@X7}px9COAm9Ks2$ZcWbD7(!sKTkN}AoH7azE8dG&^*&b|*QB#rGHI}Z zb&%ui_gEI(DD?=EN!|R0F`_?E%A_+u2yd>T|A|%?Exw>R!vB;HWiIT1%Lw@I5ZGg_uY#A2Zl{ zm#bqvLSWUVK}Dm`O9i=73|xr!JKFv$K0AzhQwZoidd39y`PeiJk+PGUl^uh~vt|47 z_t={*Xf;65EAF9gUy!c&{(oi}GunXMvfW)ZFIXWKP;YN*eJm4x|c*1s3d{ z>DAqPbu^J)cP53(VDfiM>x}rb4s$T$8F$p?XDxOtqVrA$%U_m&@el^3UzWeP8OfxwspU*AtypaC;cygc^LZbX_hUh90B8 z(6eV&pk?}7gXUw_a>}Fw{h{9UVOM)csje`hB6IlJ$xPPawYm&qY-6#}) zp!(QEj(K{x0*YEDp`RIzllu*AX@w!gkXgqi(jb@1V0;qX`(3=jtCNr)Bh8@!Y@#H7 zvQ5xvho*nW&5t?bG<(t`>^_9Q~{BwJqTy?$DC!y(+oKEpI{BUE=Q< z{1X54d*S!}&mmq_5%G-jhDzSc!-pNS;8p?scXABt*FBdcpFt_(#j*R675cSbqq3@yziG$@C^_XW}`DJHsLv)p$Ww77t9*yB!C%g}C6) zF!YmXNT-&X&@zXVr*@HVHcHED@bh9RYaZ$EgSTMN$a^rm~{8JZ&XSk;=^H`sXqD&NR&^z$< z;F&p&^H@u#MG$ox6{4(ST~l^!k7G1$WqkEIl`VTp`7Uyte2x`&RNoRiArhpO@{#?R zjof;4epQ^18R31)fBw3-jTO9kA5r)sbg0|kgrFy(aF+=lHK>49-=M4JKlyhsO*u7lN$k1h zn))?vT2MDH-&|){jOb@@Tf<^2Wf1*Uz^c76C{?4rdVY+eeBaoavhFA2>=^vlg>5n< zY&v$6AET{!U|)zIw7Cn39UYL8oTiJndoKs!O#$wqJ70EEo~2Q)>o*Q6gRftP79V_K zg~*!yN%A^V0lLv$bY!1$tet&Rj9#COep0m!O_>>B=X2-YE4k4pXcmh>wEjYFhy8cazv~u!R}6=CHAfs++X9 zUwB=YAHi}u{9|msr^qk3_KVs9`dJ-?&gd#MhASnc1eLUEk~LVDpw(kz0E97AbRBlp zjL)IL2iFLG?^_-=cPGy$q3@j@IBxcRzPAgK=3N8rSJzf2Cw4ppeH&VOqSL%~k3R~d zF1&1%bN%(j!Sj!n9bGfM$_kl@%{TjRPG*{#x*q!$F)WjXy7RC{Pxy4tOtnad$ z&#~)dwigPww~%bZl%FbwK$*nYMK^9l^>mkh`c;J4>{lIVrW)Sf`u_3!-`Qqw#}*$Y zKlH&t`V5=IuwP+>kNipOtGEo?R(b_Kf6z{|Up~AZk8AQI@m-(T77H6PZA?`GT1!Efvx)<#EC+ z90xToK5{$ES9|Z*nEaupa0f_HsP`?+c)TQbVx*_8cKXVL(D1$jW|`Y!0$1eS_~~!e>WRTvvD7V9+RVso<$);N^WX-mI@&%87mPHTTs+jy&Hd08I9g4YycHIuS=V zG?1Y6eJ!yD`C?A7c3sPMtX50hibi0uRlGjHgZm^l8b)kXhF)b&|7VHSH{t&9Y;XqC11h^ zX;o2ak9s@=yc|1}ps!t@W@>jea3;IlOn(&es{x@4`r*$flJKvvih!#G?&@t4@84a~ zFA^oh&e&{b_3Y5V5{siA8RkbXnlKrw&Sm?_v3bskuNB`$iYpTDidp@wT=fVY*Kku&Rf7+!T?@Af!Nj+@&D*Z1+zdjgus zTsV48%RSWP<_tDAuZ$R5U3XRR?>w9rE1?s^7d(sVtjb=^0RrsAjX0Nt$Axv+Z89An zja3*Cpy_L-RgXLf(I`ywrx7XeWy>atcPeC~c03S%(I3Sb(8LLQWV}$QRU3N6ZWcOl z=D2QEODlLSMEckBTBn__zXLy7)g!_6&hu^wll8Fl8$ z`!>y}L^y$Oj!@;97?@Mg@Y*j>wmdkLd(eerfUD%J!o0EGALEc%75LARLMa%fb4Mrk zO8#wFHL^;UgsNjkSXOjDNB?iH&#h`?7q{Z+wNDQsNbD1H0iNi{ zcIpo%*TP3i3f@;Np^p=IC5Ps6eJt7^D@d6PiZHtWHM9v(wl zgC#*f%ffaa(Ycwnm%I))N;sOzc<(q{DA#wYkL-_F)U0U|FVvaobNk}93F|=E*Ue$T zW=~{DBJFACe&VKF4)JmA$tyWiGs}a z-*2Z563bkbPy&|QyDbUWc!qEcBa+;cZ7uKn%#Zt^x_n##JhPK zQ#4K+Fcgj@cojTo+KQH|1>c0jry82+?_}D z_$XMv?ZXWSbGDiU$W6x{l8(9=%)WPG*FcI(@D9#0f&^>}y=*TDD7d){HZy6|kksRw z2={6bv~1Hf>=3g_dC?p$&WTN7o7Nb*!kx2YEy0T2E&-;Hy=u^0{h(~YZ%Dx&tQ z#78?NAb-ACdy~oW$$`#+rzgd9&UY24L5$=YE&hK~%&a7=Pe%isRRzsrHCD&5F$rKGe9Qnc9*(}#>fJ63J2X3mYYJV5t!QbZk ziOX#JU0(CybPfkd7UE>fO<=HkppgT?PG)*>Y8en`r*nqy)EwwFw1>EVrnh3_H zzgd6|g5(?3qGb{ap;EkU-Y!tQp6p;#>-Z~Z)G0x#Z)GKHpSzLH5-~haH{Y)_GKMmX zL#{-%H~fJ8qRy@tEU>FB_vF~}W{y8zo!*71vHZxz#czp(bC2tcuXm46-@j5aSa*=W zfq7g$461ZCqrYh0J^Jvfi0BFFLIMG;h5ii%70Th~+$$_04S+)E-|rD7UC;jR+7r8H zO>~W^mFDKA1IyZwL7fEm+|a&A47AhjQeq^tWvl9pcXoId3cI=D$Y%T1v+>pH(ZXEI z9~!5)-kyzAV28=IG5r$y&iO1xFH;PSc8?y*t$1aXlN;EA&7;IhOO_K(@(6_&X5L*= z@W_C((w!=T@O8(Y)X7|W+8m2fo`4fzUMRJs{oO<6J8rm`!fqn)GSrV+_@bb zKL&krBTgu~EDQqT@vQaw|GY{302FJgySAHTixl9HSJeWQKOcSIy}mp=naI7YX-2&;7F^P& z6rbENd#CB2&gAvxRo=(9{og2Q?C$>!cq|DGltmHWEctBjVgSA{VWG@Bm ziVvSn@HA+YtC{wyZk1C%1it=Z`N0^go5{+=*0w1(?>c5r&u0B_(V;S4wIP}P;g#zQ z1WhmdG3kn*K|6EAAq zY>ZAb{>PoX1T;Ya*MNh}R)m5=qynNKQ-d9^g`m!^JQFA*{}azs$!n)fGJp1vzWW{R z`S-n63rFOtLpOf3{tvFK?>!6n-fave?ItN$Nt zJ$^-9tW@=Sk}nt}b0e@MdHffKG8Wl2^NlX;U!3$(XQ-M0_fxmKR{ zpR#8d7BP8U5}CE!`T@kJhhsKSb7kb*(RZzX@7K9#I(8uk6aDT7E5vr!g^$vA&&*lc zn9sUd_U}h&9jkvS4UAXN7>H6VyiQn21DqG~D{zX&uLEXudSC&gSX;U=@*?(E=BBeu zAW;B1+sls&HicEv1`b{j!hWqsxO=|s-dJLXhN!;y>>OAzxMc)M!306gTu0nQDV3;fFN4E1%Y42o_ z-J%I3C{4~s-E7MJ?@4RaB+>_PI-awGG2B&)+-=ianNsf=EJvC2^)`s(0?hk2(oO+{ zXO@~Z^J=ltDAKN4$D?UerU8sauGyV~zGU{CSLGt}ZXZ1YrT%)HS4F-g>{!JcLs>`h zLjpEMvgjUrOfEyM=ppd}kD71#{s-2tP%PtH{nLu3k_r&lsE>TEbEg4g*V(BDqJ6Z{%1Q6)5ZiU+Ta72^RACh-1Z5&8^8tyU*sEO%o-}orx z6!bzSOCo?f{?PEY8@MK4wV{>z>x7|4wV3+H$rfSs(OsF(snsb|yw=;jMkxQYqC?U$ zfjD&ud{Sxeo|MVBT;lD!ZoBzJcrrJ-VnBZ8jSfyv-@3~8f(JF^JrjjkJEUjL@p9o} z>zC@Ul_IR;7SrjHn#@Y{9{tx$#lk5+mD3nVRS$aNVC5mi(HoSn$)y)eYUF8@ux3ELsnw!1gmkp{bn`4p|$}JQj!*SHT!JH!|xAlxFQ&jtnGP>GmY z@@<-aOcr7JU`-5o;$E36WNPdbleO=O7BB6^d-RBJkyzZ7;M?L!o@k_N==Wbua(4M^ z&M+?LLG%apOHQu}@fUh1(wYCFkzQW$vk7+<-i!MIE={Yju33Z{!DT_6646jFofZB&5u^hfBQ88M#@p} zi4tMC%72DUJA5`_&%?u*R~|m#4kt3j_gy*Mc6NQn2-Gl3TYtN1$P5-^D^Ia|x5i zAiu%QJtCRi$0=x*dXGuehR1Q>dqkFvk6ty7Yo99jAm(x6Te~5_m&w#mYrHv&s)TqH z2D?`Gn2sB+m)fN=_&+>UT(vX3=})rtph%?P;k;({xcCuN^%JR^G8Q(&3n12F8&o$K z$P;4MI8;Sci`6A1KKY`3M+kX06m}8~`PKQN@PTz9JJP|o^RY*U|4n9CLL*c;)KoBD z?V>9#5FAo#(4@ko@4A=Qgq%5>4N}~A&U;RqtINj<2faMW*x~cp;Jg5x?HPdKOF- zZ$3EHb>5Hq=M=rgK_cP zV>w@kR(6%A>Q<43UE^YYoq7g6759(GDdA5~)Fme(*&jEJlf!dSm|RIm;V&dShF*qwq=aYp0}-K zp&@ByTMNKymz?c^%s6;1;U4NrBExGTvzekAnOm`EOP5#PoUgvSf^>Un--ucU{O(G3 zSzKU`FpNeGu>tJFNO{TkqVIk6DgEDCo)RhQse-(l^XD7iwT09tR0XZLm|j$Od!pBA zHs7vp^r1P<7AqlK?3ClJ-;^BGiTGQcd@a)QG|(XJLUH+YMpnQ5hMsopf1>gQA*n1WBx3#|e_1DHf*Mk6spQ?Iv&$wS zLAM@z_N0;_k(+lTi$k5LIpJcVs1p6pK`uYx4zot%@)B>$BBM)3>jqZ8_kPfQKCe_A zGcF$-PxO;lw-VUfrYbN6I;{%K=SeVZIggP4`qfb+*|5 z+M#-FoJojRol#p&{6`)xphCZahmvkVSW^r%g`XHun-StY`UZ3--Co9+2=^3m-KQ3b z*QeOx@U#{+Ql5OhE66pniIrtzlxG#k56#E)>9(q@wy{eBi>hfxq z9~Sx=!xuSkk~+ zvw2RsX~7l$u9fk@AobysZkFq)h$i($1|E1jWBZmBcVbKoHXg+NdMEYRI(^@olRT@& zn|L+sLu9Xx4ZvigclAexsUsYma2nS$mp9z+a38+Jjzg zSNQ&&1&4tR4LC)MM(I5P-P$zXG0m~M0gA3eai6{l-c&JDv6vY;euu$?vTR8fa?|uk z`icfd#VV@LR#tG^`+?oLyw2isYyu?|_LF$cW$)p$#!V)Ff7vMmHKA{um(Ai8s4&*eUJqBR}S_L4NA8CbYDXf zU;E0^y0IP@MR{U(g7-KcQ%ls?y&o>8tknI-OrQxb;pw5X@h|mWTzW^(SbKQuALKjS z-Uav%VZ|Z-(LS4Bh-~Pfxjf-Zd;(dD>=DjLsHy-~^WPkDl`C5!@?)o62t!&*h;Xe}I zY@vnb`(_HI323PvAW^VLc6*r^{hGLT|(dS*c67{Of9?gLRXfP)4;s90N_J_XhlB zOcE?Rf(=cMfo%6-D>XZ3fgstg|awmE-i6`;4NQl}p!ycY^ zs<;@J9Z^}lx3FW>U`dP?Yr++>O8TU9ZnZaRRD`~r@m@l8V%r;O*U?|Ir7AUqo!*Or zc)q71b6_F33HQuH33X*ttdLKlTFV{F6z=q6`h#Qku=p}U>YU?zlE~a**O-a!8e1GkOGesm%B2_vf#Z+U*cK; z@GZaU(%_~q|06njPj)KNvCdsRW8NnjV{(j{_?@?H&eK2?oPIlF$iQw)bdfgL+$0^A<8n(@#n1X*_^0HbiU^!$ns{+c3&0Rvu`{ zrK%ci7zQ`a%NsCmfaCmYXp1dWB}h?vr^U;^myejn{!H}_=P|G(2(i*8&040*Cq8&< zzi6JwE_i3aQ_SL^@c0*=<_ADw-NDj(#B3%6`wy~W@&PtL7V}hFeHQ=N#)<*`1E?#nY2WqFb zwNU+Fc`KScWEu3afV*2s5Xc(MYU#v3oU!nhXX6l~uU(e=ZDMq;mF@3*`@7{FpY0%l6|q@s z(cGQnF>9NS|1)SR?2hgAW_DJ(_B;5}=k6cHAn;zXaC$iUSJ;*U1($LbYE-SMI{VO@ z?jad0L>NEDd7!^U4KR4QT8g}1940;<4jA63q|3R6OwYr9@n8I1ymz#M^FC7WcrPPb z44j8TY`xdHuDCXll<_f-g$6IpA-+Q%#w#uqwx)4a&##K-j~ZOw1+tPZpNy@?S%8O) zR*0w1Wc`gP;c#Hn6*f<%3HA@HVExMNN=$hgkosIja}vGJ5EpzD7c zo~`SxKVLokO6}K?{O?21NBod%t{HpWJ5->s3`(Q)cW3mDA#dm9{KY6q7)}poUnS47 zfZnC)Q1!m-qdfog;;l9q?|*+3bj#0T-qu$;PyE8xNk`=M!0iqdu@Yl)Wj}`o+QM9RU(ZA39 zt6=A5L=k6W)l>J49odtf`Z-}z(6rl2s&mzV+s}@2081_*J*^7_ z(fxT|WtI^}zJA=a38Fv#a-xo*YbSkylxsW~GL6;X(!(~!>ntVqLK1y=HY-QRej z_o}~rmSFNJFivkN1SA{CQ}!U7U2?v5UVLNyaT8Isj!q`!qN&Vf{7Cm#Vr+n@09pLK zg9sY;b(wifrl_+odigE(BIl04k61rQZehdBzYELG%^#byylt-MPXm2woZIO9&B4O7 zTTjzVb8_l%2d!?L7H2`BQdV9WZJ5Huv0+Mv1VEPWh*~MPbLuXN!?Vu2ZI3!($2=2g zq4UEMe}^>@WQ|-MBmBZ3SM=O>pz?PiV}^8BU>qS*V@1k&QQ8gZS!`vazd7Uz)KH%W zC^TB6$65v+=VJ8!{<(3Pfg}a})P1h<|YiL36-Qct4YJ$qc{g+lNh)fHL2~=FM7r}ZI4!J76DoMAU(rbVT z8I9Xdo2%?Zs(kPp(gc%i9tr3IR@D6q=eMmgT`+&m?YHQ_uy;pk$D_&Q_MBtDJ_ba5 zN|5Sn0flmdwxwxL0E}Z{j#^)G0o$x*pwv&moi$le z8~R}}T>DA0K5oRV>2Bdg+9rv$fjxT^6fa}`yCK{)vU_tz2Ilpwz?H<%R?T2l@`>O= zcK81BR~wR2z?Y^|@2RwAs8$-+0_X{r!@^oGh;CERFY;*``Bq=ELUXzQa-h@?-6VJ= zZhY$gNye(g+^Dp@wYfgt`w#pK$X8sek&!6Q*L9G#>tXOT7TJk|OeIqY%>_nkNxzso zt7f=CdI`Jk^uNah*F+9Jmj)($lij-eTw7wz6;M|0g;fJ=%4r*K@ac1N&{$x1L*F?7 zU`HYK1Ds0b1#0mM$+VW`^0h%6wYH7mUGQ@fsl@^C0WQCaTW2rw-L#msT_7l7Z_P=A zvI+A(9XaEozm6K3zb01ZwBX$vE<@LP!xMT>Dutop=r9e`kVZsLHvWD0o{pqC6a!(FJ1^mQlUp5M4pa}y zmLS~T-Uo+}<7U~<&((YuB+mZL> z#+N17=fbez$Lx@Eya~~0Cxn>$X2Rw9&6U@eGO;;(3As;d=eclU$`D+;R@O?>g&Hi6@cn<&vQ}Yc|@$W5~w@mMxF_zzPmEPw>d0SL*%Qv6!WN;fDom<5x zI4GhR@?(}1!Z(p6<(}O6Zk}Z6TUgN}IT(V@FZ3|E{lM;io{n`?*%39*BTuR)X(gj|UIJSm1o|ML@Ey zvRbNkhJ<_p#Gf6KG^rzqO2;G1Pi{TOcHBFmQM}O6!-Ri2ut%F;pJiZnoyc@7A4Rz` zWX%-(B@g0q=zAv39g?b~Z4@5~DWSiWAx79>%2 z{%o{-E$6J0=-z&O>~O0#PX#Yv+cF46{P$&8diRmahJTgR)|q^M_1c#5lnT&%i{}~X z{&$<*+ndlU&UnkRc4%r-(Y=Lx1MyN&p$6*tPAclapci0;U9TDE!^KOOPGt+!toG zt!EE8qqGb`d2Rj;MHQK1uRXjM7SoMlaBdVjXm7pB_4Po>4pGRaE_Bu_8RmftfF3)2 z90pNZhgWYvKO6!QSUJqtphEi!xz#y7*m;9iujZAQ?de{LH>anU^#HjN-_q90p9C)! zLIZh}v|Y1dba9+p6IMXUcYON?YFyr=4d`m0H!O)^3LQc1l2^Q%0VXP0OxZP$vFou zHXZd!ac*C*pFz)&%(NMOGdsi3{rHpLcC#%MAr+w9eed>-DJ8KF^{<#vlhlCrxht02UG%YUOp8cU z5pJp0Bteksc+lYEu|8nhOifvR-k2Wu_L=ESKDH4J!fPF~C3O1%?hZp8eV^-c&a4y@~kq7Yjjq&wnTnjhvQ=BkDDo0p!RUdIO^=_(*2WHA6C*K zJCIHtqm8k+7+P-T<|#y9S6nu!Pn-2ytr{h?(R3CdGlr#6OchRCjGUQ~d8Ib#H%rx} z{9*T5cpg(u@c(0rUp4(#$c3!Qx9#OUUnbW1O=i{@wyAAqM!V`nf!!>wz-dNyudB}~{GSr?wKH^!!`|=o`zOren4HdB6M^$KG-%)EZ zL(Au6OpyV1D@p9K{TKS$^)dJSKMHhwKu>~tnEf?&F-MDMv05j7aK%6Vofa_U9$l@O zGA7?L{QgfUv1!j1^~&%IQ_C;QX?T~18PuZM;v}SB+Y&Q_g;PEpO8pL)6++_|u+O>% zEY>s3>8rw%dnFtb0z3|Y^{>BZ@o)sJlo)&hm#(C8?-!(7m~L0q4X>hTkALeo-HCgI zTLUyZ2kPK2djH_S&Un_}SD)MB#9`?h+Es|C{oMrHrU-abgSXKcOGvetsL#hR9unTv z)^m_~ymZ@NX=cH9(S7xyZ3X><7gFI{F&5kJg#Z^{5WUihjY8Vn=bmy&BA>}&B}*S4 z%my8#hc*ktd{m%VL`})5*-e+?2)BpUi{^N-+1atpbExPpupU9O-&XgzG(fDwhrWgp zbVlnxK!hI){%N$Dc)l_y6X2v2*Q54(HmHF8Y8_5U$dv|rEyMn%+0TLfR8UR(`HMyt zqx3)-(V`oV`v(DRvoEjP?ecM}_v5cw^*QF^dNhc&)I|G*P>Ub**Tdmym=h+^o@Srr z6)ZAe)T(@;5d->dKj25%i`@XBe=cFw9rH+<7I#v-K^p`X^g4_TdS>L;VrcYfQEcCL z(U2GpAu_iU`c9_Nf4dC(lD_(>44;c4?MyF$K$xsq#5@Z}5jq3ZL($tSl;eSHUpY5Y zrZ}fb)qSY)K+=dLs6U=KX6cJ_5(Ub4p{98JVeRMW1>f6NCN$#3KOf0t=C~^2u~&h5 z3i!>Gz;}$7N5({TrGrD#9#se_3-|RYbkA)A?&%J-e}6Pm+&B2i?Jqo#L*QkYbGqw& zI}5tmiKpQlPYXp*8&5L{zN;iZ?oJtQyr9^v4tj>a?YUjtQoOYx^)PqP&PTtNSE!O4 z3}BdGW~NfzXDudF#94|coOf?)8GWWN2~UP|07Dg96E^p7pm+-xPB5Lxva8?4{Mt0d zoWe~jZeHJj$$PlGKRN{BYLn+LgW0r@u?A7SN35rq#>#NO*GP*?SPMiZX;Z!97E^n{ z&fyS7-lBk;<^fYS^=~O|{xj8Bp_?u+1(PK>K8B!wQ*qF?7xVdiv~{xR_HUCq{~7lY z^^>JW!E=MbQoWKK;MdUYbD{)(`PhW!3J34Wea)dQ@l?ZT$Z z9c0Y6LcdM=jz08A;MondWmwnhVHL-_6XNePhblRBZEy2Uq~Z_WHPIsPAr+!9pY)*i zK=Y&~CMVNs6rUwu2nqIP7ix(+ntAWX&U%Ue1zoA zyT)J6%R#cIzwi5YmNe@`gtl)8^6%$gEo_s9Mn{@y=~K1NMFmx>ZS$PAzSJw5ecMS) zVQsXc_IA$M_9<5p+DH)_nR#_SPikq7+>UoQOfvGO(`PzT(Z;XW`GWfoEccin8%-|< z-+HoZJJE8^yvKz%apcdP1EPVR@-5rDvpRTVO^fhNZ3AYVK1alVl3Tmk$)IUV&fKpL zdIPXvST53XWn8m!@y~$Iigg-S*6nLI!zV*ro~0jXIj#>H+W&uL81;N5o;tV6aAGMj zT*ybzq_Ta3@ow)icjqNcK9*R=eR<=(p0v*xiU-C2$nT(7@vIgVd%Qm2M^{al%p!WC zzsAG3<39x}@Ps@$`rr#`J0DwXfr8$QVlFU%JnZu&K2M8edFJLp0(e*Jb5)z4=f0ij z7xP@gsVOidqSF$-I4QLWb*NXVUJ7scFm(*-)|U;P`NxRevwXM6P!Vsm^h+x;rDi{G zY3B?hunbx_311=vEnn0T&X$01KLf=DF5*$uIe>4 z5`y$gb(JBX|Ahc_gv&^gSUNv*C2$w#?YjJ>8By5rpr6vy5$&1X(2}iQZZw{;SCU^$ zxV#Nw3mNoduMwx5BN2Pj=W!|vvdq<}lDlWwX#uoEoMfrD=Z7PSW^&Pc|0kYYGQuut zp+*<$ZYtcnGAFv|PrlMqxr%>_Dma4wZ9&eo+~$*QRNxu7ok5epP%N7v*<{jz+XU<*iJ zusaJG$uG)?IPOpGOYj>XM#fINeDePIf7cX{6vvXzyFe8Spp%pcoe-k z{HXDR_Eo&e?IXqmqLpm6jC5#ZS{$_eS$zdZJfp!^!Fuk+- z^HLHLGaDa=7%<|dq*|ApYZ>@SA|IGY;4zzHM-55Ku7zV(IzL@gVJ=;MJM+~si4`BF z1G%W<9EOJV>n$gS;Wj*c1k6B(yqJ>erUDt zcOcuf&F-BdyUfuWR-PO_JTg3`oYNY z5So==mW#m7xje-9zwXxcXfYUt>DtEig|#X<<4**9L6K$DG2__`WMQz?HL8BMumiaO zqVqbWbANj68+UUU2ltHqfn`1a2={;GC+#B469`)hQWAhWfZ z`xAO5xzggdjaj`tSJEh$kN02P*smT;9-&ZHa@Zi}V%*dqnM;Nf|5g8w*_%7$uCT=T z+g*9zrQ8xv^3}Y4vi3%|i&o;?b9($h+|9dVYs@PX;T634I4=|W|N48K5#b;117t`l z#1%I&(cR|`VH2yXu@`v;?RJ5CYCK($hPUbDOC zKSdWH9sp{3GM4BIz6z1fo4_5DIux~;bL{-*AyMD1U!(8i%D?M}cbw48K3_SqP87;D~%Zz4f#a~&54pfrD9*naxsEHPoDQd>%87${~0j_CQb^7u}@)(7(AeS%)@9j7T znz09}kIYOMW-s(8y=iU^u&^@J2k!cT^8!epywy`@vs3OGfsC=kv^|RxY|ifdxB{Ei zcCm-rfHH|sulR5!wJUtK|K?gR6of6tdZ*)D?zgPx6iYDsIpjh{qIZoAFKIu(Me6!a<;^sbd=WkAvC1_iQj--UK)nOlKgDvknCT)Cr zb{b=A*8J6SBoEt0EU$vPqMs!xW3}eKfNw&L3N_R%{wuB2ij$8|pxhEgrL3_sNxUVi7hmj)Y4U zV-@cmvCqW6rq2>7l^j^ax}sj4w}De5Jy{w~Mt2gB<9}tXuVkzGd(j{NVSQbyNJM?m zGp<2f)x_)r%njSPk&6+*P&cK=F)~wCbZUZjQYjE8D|F(uKX85->}pPSTh~~nT1vadd`m`h^U4yC4ce|W363rH}JD?xFce{$EYdLr{ z&(u+{eSK{IQszDwPP7agiRl$pjeTVuAb0br^{DN~D;Z<>?7%PJnf12Jz>mWKFd{y1~ zPiPN<$*>*7VnCjFea#B|ZNr()H#E5z<5cf)*F)0vp`$;q9IS~`gWL0w~;if;yEOnwL03vAy zs!b;|?%OSJD!e}KC6+o`lpL=c7_H78Y2kHH@Z%o9_A4w}8lfw`HIa2Vyje9+ncRU+ zwhXc%2~>jQx1)Z6uRB`XQl<3&F=e}#{OU7d&(g<-O)J@Z8#(X(EDTHmbfrDR_Tp721&1=lT^= z8KMV{JS+>)55kEg^Zdu=vg5d<+1l>kwMtu)(3&WwjgJE1Oy!8*U^3CRot3c`Pg^Hi zUoOTSDUAe@`aYW)XQ|BZW`Tpi?#(`7!IzI1!lW&q1(Hor(u0eq&fOhz7kHaSG0$KqCAX+Z0F zTwZP}83zv4DkusHJA4FbP#<`w8ml<$H~N)iB+-F>@@PGrI_Z)2f)d}s%MKjzqW4an z-JUSWWzRhQ9h>C+96F+zxiD$XD!J_2){*c?e4XTC%$ncj^3Uk!E@6KcX14TUpe53+ zjHPY28b4nr_PT=xTAMZ$A-3>=d_FiA-7`}^m?dmx4<@@2 zl5#G7FKqOpLEK^ECuNkY<3p&nNX=C(R)3#pvQ+fIg5o5bCCYzY^tybL{=*`2Ep-pp zdUQ#U#ePPo9T@omt3lOvJ!-8y5~@2Q2-rLO`7sn%tsYp<-gYde-=RIXX$)VIl3&x z!uN9HzeGv>t^eeKOH{}e(PioDW(eS_w(J|)7?t%Gi~ys~y|Lqew@)%M_LI6_zg~T9 zPBO{(+PXcCrAfSa3Yp9ci@R8VB5774=qqY~oOqH{RL^W{!3*`D!6m0kZfK zu%sFL)J4#DNpr^ip)#Zebvy~N3D<*OQyf*OV_~*?yNZ!XNw7b0AFYZessV%vvE};^ z(7O$967Wx2p-x&cx@NliIfmJ03&wg44sz=4THTX)?Xi`fi3D~eEqldMTJITTb`D!Q zeS?0Ck=|^`5apYaT}&FGxOUqnca`PjxvJ4ksl8xShVmA?`!C;oJbkRdo(y`( zbw`>h#knDDtLY715A$-8^x{paOVJTwwo`J7RgVwvlOsEQQ`YjJy}o;6GV0lI@KN&L z|7uNMTJw5@JX^Ig;lGSzGw6{MRpa;34O`GB{0W$PIV=b~okzWL*?6TH3%!E1#e&hi zdE;}6wBJUHqM}}Z6uk?XQp(viBSf zNvTpH5D;b}7z1u}<}jR0vC(z0*@&(AwklKjt(WDtcM5F{2?^HOoR&XgBOJ1y&p>_7 zp!j?u`b^gNF7MW>{hE|uw4-kL$v#oTv)#V=opk2l^sC&O2mJp_fO2=pM+oyJ1G%p> zKu_9tLOXLm*Wt^-@)ot}sH(JYGCVdU1x%{>mMop_NI^o^>W};e^@n?0s=&ukv6I7ifA+WcJ&p-=cd7Py4gQxrqA zThx7J)O8^#N6{Ivh+xPi?#ld&T(dFL91u4iq?ByofQVag&~YsIs93Zr+dV z;#cvubJ=0*)gRKFv3>SeS7Fra>Pr(Ts}+4O?AY?b7UBCAU|JR9=)Rq8YJyMwBysoK zJDm8|?AEJyJjxk2k38_84U+FLqdl>y#IARiWeufQm=YlYn`J?m*V2H)^q(Y!R;v`? zmVPzynKh+$83WfX_5k6yuhZ3p(@T0B!!qXLtC;O!TQ%x+=FBK^$dI5Ieh0GXe-yqr z{ZbX_cmAm@c6-0Z2C#Se@eVJna$VDY-RehiGpx6kVB4P5bwsknI8oF%0oNXJZ^!-B zAaCfmd3N^T@FRVa^p!xIf>X`&0QEo#Uh|*aMuqEZZ4Ar6NG;l!Zp4k$E`Ir=RjXZA zH9()QA!COMSd7Tk!6Y&-c{T2`x5msv8i;5MjBY|mCvvNxOVg1#*a<3E*J zDxJJ@PVRk8a7FL|xZ=E?fJneVkcMEiHy6;C`(F;4Y6|n##NKk4>7Ea+?JNsGxVtsG zf34Fn8JFjbPj&jc|9xq@|khnhs>PFa)ae3sEUQ1*2FT0%9yH!_ta+JT}O#0K^gS$ zHAPTUzUj6th`MRR`TRFH%9zs@lsthc6X88YDPmVw61^EGqQD@C;%Qu45{v6#*D4!5 zLdMgbPzc!{NYP$(xa9(THUQ=&q(rRAj~qlt!8j2q>k}lB;|5x|EVIi*LNubL(Pk?P z@1~hVitX@bGHLeJ*xRLy(wS*qzrEs8vwABEA%8uF;oHX?I~B_@H4x3?QL4;)idyIr zEOFn|fDS@fJ$&3IRfLhVmsJXGQ9${?r?N}m#uhp0V zI^WbyY{VY(v^j<;fC&(p)jyg|elN}wK7=}tv9Fwb9>pV9ezp-bT)k(ex|oVI`)huboekoM?&9378Q4(> ziCdXGHozrt@s3U7JYN9aJ08!Zq@q-p!xDR>6$$R2nSEg2Vct#WGu50fGOwAhDXrOF zH=EcbOgDAMNZKD*`?$;x^FqrkjiCX8$AUsi?kPWD(%wg07&<2ecm; z8|}oAyf6N2EWJOj7{D{6Y*kPEGJ@OYBC(is4}nYlRSUirCFcK`$rt^O4?G6_96h*j zCD`srD2bZIkiyoeb|H!X(YxT2Z5ciVia3xnM8%G<2T#}NspVZxkos_~HV<&OCB$>2 z*`tV*trt*=BHn4uxaNBPJb3ttY4V!~z0O;#&tZ0JW%RiugP9wz?RPbQs}t%VT{IbgTnW<2x^<)?RA&@{bZmV>~C^ z6^ZW_dB!8CWmZI#w;Vha7v!$_3|CCv0dN=rt8z{)1y(bUvi%7sI`Gz+lgtp;!66Uf zbE1X?!4kQv{yS?+j`RYk2GUm1G7aCG3ShvecG{f(qTl_?LZ5RdVC$RV)YDFGl)($v zUT%ajXJQd^tKPlH-v)W#R=FVO2f}kFdH*4o9n)h=0G?!O&z7x`Jx5No zYPYsSP3)~G9XQJybvAtn(5^thx&R)6_ze81)%(9--P#m`9F#d6gpoV&@+D0}jnX4t z6{PsJ;@+MQArXB;o9`o;JUBNhEq%>)3texw55CNkHk;_N|FfzwV?C-Inc{vTV&G&* zOl~KnuZ`v&*xtObJzEXz(x&eGsUfykuNa)}Uh8}s)Y*4y&INa6=^KiU5pKCW`8a$& z^R9V?0fIqn5*?K)Jle&kUxss%A1ItT)b3=6Bi@$-uOVF*=LiE31=(P$ht>15i`1k@ zRvDAn`{wFNg;u2aC8@WGC*LH6yFa@XSQg^Vh=3|^cH&m(b0L@e&q-Eg6I}iIJ1g`@ z)ykkGrm&t_b5W&3+dPr!L#Azm0;K-pJpN&@{X^L^mM7{kt&H zNQVQYu-sf9)8V5KQZOYhv3NDzT?gNKJaVEZJYz~0N;@3H+AA$Dc~$fn{niTknrh*c zN!Dgqv@Fr#pI!{})MCVlRvXP9_olbw+>Bs(M%c5*>;JmwSUrA%t-KUC+Wn4J`-!`L zBFE7}F-ArsWXc=<0Me6=*zp<##kT1)o&b$DLn`wFY@=b!jiiz}e}}&CQG?Tcc3HMO zh_{C9sHUauGu-b8d?LqO&z4#DSfvh2U_+UtI_AX~e0SRcGTn6j!SU}m`Ux^L3;)8m zR(%{2L^iZV{O5a&teg6^&|IoGV^$HMh^@qpu3^1#Z z2N4^_C2X+SzRP4x6=POiJ`p8aH>-iw@#z^WDW12bFdqB#ir*A$wlB0^++n|-Xh+_0 zlo-E15T3C6FtlH2X;5^mAxQ8;*pU`Rh1(5558|3uTh|`9{cl+rwnI_}P<_%as$%g` z)cF6xC(kKk-ix4nR3w2SfxT4G7L!d&k8-c}WzQCS9OF09(YGwD5zWYxQd_SGMOS@a7ZqnpL#)XMk$J*%g7`GF?px5|%F9#L~E+ z-CH0d^1p!s9jXSGTTeBzhEMqKnXvM2xDXHB`Qy@%lCFGhPDbZktGIzxS$IBq?!RoN z-|R{Iod-RE!(*MpE6?##UGZn1wQ0i>joCxaem9({35Sa0^GU~6{h!tEOjO{%fAXDQ zO+10}nv`oz2NWbndf;BzpRh6RaQyI*qQcdk)*jz**f_7|jkFwCYRz+r2OJA`G zH8V?1`83Jqp@?EfEy03fb+Gic~AWi|6Zo2mJ{iF)=MkyV4-_iv0X9@PqIIksW58fVMxRS}to-+u(tytjp!T(l;TssbO1`e&MNX4G-!T zKYkCa<-8;ULOic~nHqkhJi4^#XmPkz_C@8pva+V&E9J=S*$*-dU!0triwd7NHGc7B zFZGZwS@1xNrx+qq4F4>>GMR)K-cspTO^gwvy7wTG=APQSS5s!^79GE&iI~oY=_a_` zRW1sA1w}$H@mC$d>3P?A*Vzy-@ECO5!Y{G`4W-Wyg0OA8msBnx+lG3EWAjf#ECV!x zhBmR)mTahJXjwM(=%>fUhrd-p1_Uank@blbyb5eA8t^4KPDj1j9q}BEAq{71>P>* z=N1JEHq?bq2eGnIeS?f6GS0^L%w20WoK6|;)+s7vx4iXGKRPn7aV4+bF)4uyK-`zps?Gvm;=uRScpD*|);ExZjxP^wh z)mcyV;5EG!NJA{S64?spo1uj7ZhKO@w8b8HV3AxLoK3sEL-cK{>Z+uvuYSBGV>U4?k-v z^gGK0{UB5N5MbVkpr^ZcME39*8(@2Ot_+a7V`fnLN0-ltzhHE6F3{S%NXU` z#mICHM*UA;E2~!)_Lolox5hp zH%;c06F6_MGc?~FYyK#pRA+t5L;G>Tnhf<_YFw+9#oK(x?%%Z9=VO{V)H6=|i#(2Ts!#`WOZve=zw zzQPn{vMS_nX;2-2`V`3rg^$OQl)N7FLR6Dl}iB8Gt9I)F? zfz@sd`J}}ycT)mGUiRC4OlW}RfBHB*NbI5H7J{Olgzp!K&xkMd9XrJO%f-`9@8oPn zo|p1rUgJ$0F)Kf8#RH7?Zhk-|QSv8Q(O=>9PG z2zl`4qTyA!Ic&&Z4oQR`2bZ<)(vT%wb=Srig z2ZJ_6*}}geR-KspqdU}w{C9oiP~c;2&;YmM`)~xJ{Z;mFkkkC+>8 zQ1IoU)^PHPIK9)5kf3fqsA?Y^mI^xk=2elGi~(#7i2X6#yQzFTjWMqI(aZd|eRDTH z<%D)6qz$Ay_ZiIOv+9*jD;XEw$$&(&^21FYU6)?^5RVPv$mwrd@v08J;9)f#i1NQC zm)h?dr!IP96tVR~lv;fGW1=tS^T87|XMk(LSZe11aNdacHHAJ}q3phl*pU@^OvS{b zvqVbyXx1k0>65?acg0fB@R(fUvpM_JNKU7m#xZzRpq04gseAUSWx^xWZ%M$$UQ(|ux zx_6iVD`&STMwfS4YThO-Y)!N8d1EQem12Y|+CDkBuk=h$$xe*!OM`A2Co-M0YJ^r9 z^g`Nx;?ZkqhZ7A~^|?)TYx%#_yp~krM~1TX$;zaj*;FR?L-3}U2lOwbW~>jchs*&H zBYGhA#%F1#YRSPmBkNI#Pr?wuk11=X5^RU3tLVrtgBRFSc+X7DSI5UD2n48nR8GJ4 zFT1UcS%@=P9(G!F^)FYf7}R$BqL^w>1wkoDyk;qFJ1hxr7S+-}OR7Yj9!#xhArBa} z>iwzs?OgGL66?QW;QA)JPO(~J(>cWd)_R>S!tvyraHO*z>cU4jrbw%xF6qPMZ$)(f^Y*QLXWoN%}_Vm<%T5MNcg&AklsxdB-NZr5N{Q5eg_?&P^t&+9UJuY4eb~FT{@-6>RApF zmg=nv5~_eGYUbQAE_|gNxCykqu6SyU95Rrb@?1#h7(PM2+l>BY&|wn?davXEXyBDD z*=EtkZkdPSh#_Oy`W?6PH1s)G!A9wit~_`{evM_tDP6*L=6&ggK@%Y!jmS>`OUn@! z19gW@0e&5Vkr$Srxf=;`RVYY{M-PaKy0YPbgk z9s#e<(4D9~AMBIvW?2gaUO z{r&N7r2<8@)5g{N22Pd6WkTRXa!907r-8OnQx@1F%R{1513}DZ=YD2%ykJ4Nunkfv z);a}v6-7Ktmp`;A{3|0y*8KOyt^ZyG(iE+Y^vRV*T{wF{WcxeM*pv$Fe~iw3i}xx1 zIT@PE^2XK+U441g#MR(fDQt>o8u1Fh_~mD@4dAl3q~nlfb)6@|YL{^i&n(Yd+J-Mf zyE{)gEYR`s@KnE%sE zU{6v=XLZvjTqp|fiSpFEJd7<4XxRDcG#%z|B440rIPU{8`_;Rd@qJ!yq$n`O?}1}&MLp2As-)=ZB`HqP}R-mD;c=>w>+EuKix zP9}0^Tme0PuupsXm@!g9tohno{tqu-Nl7fEFv9O-NXTE^i?#+jzpS)8e=dkJ9`1`9Le%ztYav^en^5Nxc(LUy z#^CQx!(D`t!6Tb}9+xxK6JnoRZjcXT2(}<_LR^FTn1!K9hXsAf@NgUKJLr{i(BzES zD<$HKFlRd!$N(4S_56)2=G#K2yk=xgxa@%x�R4fvX+-fGZt1bWUS)XvtY2QG@fB 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

hqL~ccfDJYo=(cc{t74 zg<~O(V^g3o--XN(&VA4Wclp%7zgp%>m>tb9HfsCGSMW@vTS;kCHLE~4bt0MuQ|Wam z^MiP7Jq{iKoei0Pc2p#r@?By!oH?>40v3MlF^HFD|D2miR1=H_#=hKojvGbVH5KHuttU4w$)0YO1G^Q>F5s8=H{Zm!PGOpLe{v2}wMbA+mO z-rrhV8>{N;8|z!E=q_RpFEALkx|jWzriA$>W9v>kVvC~~6LD-taCi)^(T98&9YVgj zxQA_p)OqqRW0DA`C@HFYdXl#8>sVV6J{yHa$UNui9Qgs8)dVxn6$?hbkEX}fa;*`* z?Eeh5yq=wZs_!K%Z8gk{$qkXHsg(ENsn`!7mZzJbq-7!=Q_mB!728w(Q~1Uox4La< zZm*k?8vea(>+ee@hWb$)F@im8|L_&`4*~q$UAz%NXeHBUX<}k|qIwIGrjA22-^Uw^ zFbwG{R_SSyvoNfSoA;iO>9g0bLf|(q-Ox)GkhMSeTlUJq%zLA}JUgzgiXMgdA z1EQP)&WTCR{^A~d-@W?oLri=yPn2T({oM7B(PGf)*}}rtfXthFzb@4Y(sv{_M5cs$ z7O&#^CiPaoGKp%Pc@5YG^^Ij$7jQ_^=l}}AtaI7j*a}BVIt;}NtNzFz4T<@G*n01{ zrk1E}lp;kbK|w)UM2=!5sPvM6h#UnCf=3X9posJ$J&*z-q9UN8(g_NPNEZ-k2~EI4 z?=|#DCkY{>f17jO_ulXRe)sRp-a9k1)|zLn=b4$rgsxs;G>t?X1e{I&U4Nci4FzsVUhgL;d09VcqQ_y+U-l^GL5JU9Bo-K~Ml zqF2hD?z*;}h?P<%Oo#=`u$hSyl<}GtkdQ?&q&hHG>oE}7ZIC3rMONHv=2uKKt7(PX zG}AP>xzIY<5*=;eB@$8H5_+I|czfIT2}HqZ(&!sD(1*S*OAqzK2W>2#E?p%emn=Gr z0>)m9O5OU>4Wz(;pC!KaLUlp^^RbVctq1W_Sj#MFbA&Z6nR#xXLR$Q_w4l5!eTdC2 z7=+&xJ@W#%Q|*5V0(K|O(#@+Vq{_j>5cYcm2XB< z&9!xON*}A-*#|q(E`|zwNSxRUsTz;+6?%?K6FGpr*fjx4naz^y2=i0hxSOF})K3>E zWO#9IQ$IbVeCBx=;5YI(1SUjdj=p!sAf{(~Sdvao7q5$YLcA`D_^U{1LXutF!r=^W zgF*;F->O*@ZWAY&zA=fxgwh8|+fIy32yioIR4f`xe*LBbKzm?_YTaPxOdx*X)fjtV z;ZI6}UJ)n^0;d&zXV$v}6u_6;KUKw(%icd!ydv-9wSD)o=0)eD0&$Z_wd2X-risSI z`1;A8T3OqQRr0Ti|o0-qUJig>-|NV^H=MT9qx&I)#{i==WU) znRsj?e^OilcCsB#H|g!by~ge2n~WDOmeSlh+aF2F4m~bTdSc57jes@}W(bY(Qt#ZXTw7+eu}GWYYhSZ{u?pTlXcze z#0Caqyc9iCz3m5OeKC11c;!(5M`743?V}Cb*Xz%Q^xVLoLhP|gG_3|q? zL#QIZgnV`LV&4~Z?GF4yWhcmO(K9i|Gr10@@*li-r&uX*LO)cM z{f28FmP)8CFGoK-TfY>G$Zv-)yi)r*3hULMf&3Lnx-bmG>~DgoL>^&CQ!btlg$8o8 zc$;@Rw1%0XcAvO&$nc5e8WysImpa0m0UdkH^S+Bci@MVW-xHOq=Thid=MV)oft`5m^8lS)` zbH^El>FEx#%~)kHW({f?1yTDh5-`!P@z2zFhfY6^^$0=6u_E=hK&ld^Yac7-!{+K! zqphT+8~cKU*<<#08i+jJ3`!Gw z@1AXpp-;AZ%UTf)WCpr_6#AJLDHQh?LSt|K_SIEu^fAQ(vFmR+6CYJ<$lxLB$uZtT z(E8f?#>Qk55n?w^MBl}3OjSg%s$mtpvD4rPS{@u7ZW3K(YYwA zzW6T(BZgvC?Kx#_jD{~IR}Y9zi|hdXRy*{wZpH$tAlL%&^*k`qB& z+C9rOQeJ$(GoQqPzikl56T=Phyqxv!?r!?Vnz@lOk#~bmB9U11?HuLA4c;dfl}hDA zux=!_)3E~_27|$E;CyPIW5JfWY&MEFg&CM5bHKq^3|es&gH_z_3O zkfve1ghNhS!(E`=-3GC#*o?u5w&ECh~N%m zwAh8BJDvR6zQP@P(EJK~rGqDuxfnF`U~M|TV@s9B_*5PAJO6$~_%Bt6t95A(RR4+C zniDSRu&Jj@0gz$rj#eHpS|-j#PyV$qq(ys}U-X?m0HH4WE}X;9r}sxFxms@}0mXxp zA7C(MvUiW)9W({KF^<_PAXYV?$&+5`2Bje0suH(ns%A5mHXDbsJaDl1#yIOX?#FZiA~i1GvL9-HXVBGk*2 zP7>|6G?b`(w$^?jxFX}O(LcN*GJlZC{uO2Oz9|k%ghfZ}Y|?>jd2;E1Kbb;nfW%*FgV*ir07ag$$$;X zb=joddk9`HC3hs!AR|r6PA00HN!C7{NUprS%-Bs}VKhO-k2~+!^;18!pl-3d6<4+D z+RnIPbwmT%l@Lm0SCvlbhjP~x(q#dR?dvE`J=P~_3zw7ng9Xd*>)tt|* ziaLX)E^JV^Ds7*V}#aT0D<1 zwbJ$F;rxnAzGccyxY}tAx=^{p@8zUz=^lV%r)NsjUI(VQ0f zbs~XKEz3RG!U~09cS_hjp&=@wv}STw*ChvcTS$?7=F*#^!ttE z+&PSFyH}64{Hje!T3X1*ul`auqSK*(_K0rys~H#ADsg#{!iKASY-Mn-egRN`Ddy_| zP+e6o*3u8%Di(DpYI~~3vsE-AL3o_Mw zR&0qoS_mw6Hi=kS9=KqXK5hVi4EtSuI1v*L%KGDvhUbQZ!3C_ z%4_}keTHD`=n)FL`DlESi)Lovj_8;8l1aYkw}FpTk;gar(FU>mo-a0N@ghY{u1e8L zE35dnIaS=x*frkPvCD>tW}QnD(jKa`?wls_Akn8r@Kl(FNX8Exk+JDZE8`;L!}pD~ zGqD`%nS1vjf_yCQ9WN`*^_U6_uZ3_~3NID*aAVyIT!UrRfEt)J2(H^*CSO#HN5KMr z(pw{#*%J`#w>fhE^JJ`oj!r&MrvMgj{ch6zKO-7WS}Im%M>6a@FFGFp+z1#Qz>T^( zqQf!Ie}6j}bP&Neg3;xk%cQBmpX*YL!q+4aRBq>KICgxyE06B$ory)E(MkA|%|<;8 zkr!H&*EQJB7Q!wqAoJ0--dUkfK!)ljXv4Le@*iQ)P6F?dUcxO~F?-(Q zH_NbrODBY=5 zdSRZa`agjsQ*Ekt>DlM7{`r#U8C@kzI0B=nsJuFf_Sl%}M2ttQjbhn9{dg1zpStrQ z`-GvOG6x`_WL9-AaAf%cXZ2uhFMd=;eNYjOAE4?T*4I`r&-^6yMga<;9G0D`gTTxx5$kFKXV^lo zc0^(D^0Xy%@{&!v?qKSf^F~4++T@S=@FA^SJX=jLBGQyV zL~hvnl|j*A7hZQy9~yWT%@&O4Xg9m$2lxjn^vbxC$hti*q+9T>LBcW-KP~yS-?-Pe zSYS?Y;z`J?Gf#3=1+4S%)JPdgLFqm*Q{kZL_}oPnVUoJHtKv3q2KSfY2(f`0(-2Y0 z?dwd2k)=ZsI|F_(3~inGzb#OY^T;&lO9mM;(EWaUn48Qv5Q$!FyC0mD?_EF&JRju! z$Kjf&%>Ksx#mD!w0pqgYtB_pjun!8*Ha+9A$QR>3f0LGEzyQnN>(+JI=p;&!P{sJ; zK;$Wr)E#+4nE% zH$HH{Gl%cIE8eiL-{>h#tY!(_Qp!x z+(22v@d~P{8aNLi&K-s&h9KGHGiR5IyKiEC#t}Fl_>dOoY91n$7XE{|;~K}cR9>3# z(T@QBaxwcR*vW5qMY^UuKy}d{^9))Q!v22u%L1EVKmt~xb#l5am=LlSjG)F=t3#O) zI^B+8;pnq$d)WClVPVpRgY*5VMyTl)Q>Ze^I!GaH!bbk1`<=^s(PHFPF_})-^l_h1 zr5zFJx~EQ<+J{p^(WqtrmylK)St@N(i^q?j9J^n$57R0yb8Tf~8Pe3}BoZbhrjl?y znnvCiuGT2RxXByiz1Ixl>Nu1T9D}&5xm=^veU?ImWyV~eBs)6O@zQY9PW`OL>PGY? zNMZsLvav0TU4KhtEPzJ|oW9f7bpDP3*(cDonJ>?mY(2UTgSWHWy`hQ8ddh{>`jl+< zO;c4G>iQGO&bGheOD-Nc5~=j*_?`gKH=Zpk#gBM2N=ZkgVQ?J3L(5!H4oS#P4^xuvz3ik9=A6|Ec`h017 zzqrx9Q8&!y2t0I^rtQ#*Tf*gwV&NOIjY7INR=}IH@|RYXjIS$Bf48&J1o5n1vBPNO zfg~m>n!Lw+`JUymNPMTi2-PD=OHvC9!w?%5H)NllPXE+i>Zdf+?`(t$C-A8^KC|aV z$_^j7x4{=M%Li$D^r2Yf0d}M7*9`374kNuAJs-PKPjSG3mfPf)SiS^ZEtTp0CkA7N9a0TX20Y5QgsM3>#CJCg zzz0VsYUExV4%b}q0w{xk9TFp}o;?u3{jMQ+kSw}F_rA$A17z1*WcOr3(Azaus(>#34HzhQA?n|dP*))bcmJpym0P|9zPD#z;ziK8qiNv<#{r}|A2_hwNEOWeL2_Rg%EaiU4`8#R9iW5d7xS~~caW|^Zo{G2cZ_PW=7I@<9L@xBd5?aoc!Iw2JlV`3TE z;}4_hIIq=jj-&nY^nQshYUR~Mdonm-xK;RF#9ZYtZ0OH4H#0eOqk+<2idvV&y7RVA z>$Ge|O2Sffk$o8nKo23bvWSM{K&TC+PfZ6}sEW(EC88{W3@py=W?T(z+wYFM*0p1( z)!+djxu%tqNkrV4kSoy-@r@l6PZGZEmyx%Mn)LL_p4FE9kr4Sl~%FtFyU zZnt2;CJnB#Zj*3bFwEXp35Dcvy*Lt9S4)za~edVtc-n7o$ z-##Cd2VY-M#yi1mzj8)L%FAynAT7=&vcDozzrM~2>5bjP4~>BR)jz0dE_Gt?(Xh>N zv#XSMkGg*hB=%0k)eZyH^zW76H|LxvTQnJ{(IjJKBWz`G1;O3$UBAOvj_(X~2x`2t z&m6$8MXYBRDa$2X1=h08)_b+>{T10R7b2Sk->(oJ_yuZJi);&}*%Mmuxr~VV9e?38 zFMD2DsDNfC-S#Z}n=tJ&bC?SfwZy0lF-VvXYfdRjP<>v@$DW&j|p7Pj9AcNi+U34A08a`$Q7T6OOVzkF*P-eIDQNuRdG6nA|n6E7x@@tX< z3M&ln6$5NydLIbAYIrZps~fcm$|MAESlVA9PGI(P?B=wEqQIPw;EkA^L70`evXqqD z+>hudZRsc7J<+lFfp2!RiYPo2KGD2+Q8EOstf>vdjE7EZ@wO-eianLwb^hLAc%D;T z=j(kvHRdlOWJ%e~P{`w%%^BtRZxWk*TvDnCs$+%i6_QFM#HT&3%A>+bFwfaf>)5b9 z-H`@P^JF@Ny>lAJOa)6gGLi#>5`pWh>Pu-ry6T{O4j_-I3qe5E+V1JP>Aq=B)$}kh za4*L2JhS81JIh?RrasOPG#2Ww2LI5_-3xdW7M$t8}A;uqpSo4ZSoM~F0n#zQ5U=lTN*a z5VE%~Y~ST*(Rf#uj-%^s7`_G~hpAV_;)tM&)Kbo+ z&?$JN5m(s{!lLyu=>#eJ5FQd`1SBehyRlz*-DMH;#hb$jr=DjCoAq;txo(~Bl{=z8 z40aWM4M~cSazjP=hxvbt;4S8_8}i5m?s6cQOZqc~>M>fF5DWkD08OHFc(|azA(oZ5 zJ=dmto&WEt{#$Cgu+#LsJb~1WB z_M9pm&S_$K;94+mduV%MrM{ps$`I_FUC~J_t0z>C+ihxL{pjRqcFO*QfSw*t9XlM6 zMBRQ7JE(4pC}D^a1arbOSiCRiB~%QH?;_94aa}(kRA#gtBc>#xqlE)#Rd0erR8daThD`W~Q z_#3WH33!B7$%|^2g3uKNO0tpfv=@q*p{&s+t=3@%EBtE}$O*WLMrY3xS`nw)DBaVG z%{CaR3#CqZb#pg=;g~%B0jMuiIN>e>7(5JvP$3?^HRl(%$&x}wnN$*Oo z2jmmC|9UTX1LwwNr$pe9hUoh44cAQUuLgg~P4RtM$g)?%T=?IN@M~l1%PYgI?yAsK zYQ{2b5W8c*AKD_z4`cSJVVEL<)+dJh+*V^UwXNqn$Xc0+r{iP7&!{7VK6YR39!~EN8p6C`_JtHJSw>oiX5U9nYQRUh~)`g|2Go-Yo~s`pDy6Ak|2Ou zqAyqQep%qysl0O&UN84RR@~=J808Sk)OCkv_E_dTC0qe*xFA;$`$G6|XMgV6)|=we z!Dp}>(RreH*Pr`Z#jEMrrTjm$1fYL*$meNUlw-4h#faZ(Tc@87*4w8f ze-HI*sXlT3LZoqczn-9Uugqt_2gjCwL_DAWch1f(SQrGkpt>$QKKlY5LxX#{9SVeX z1$F555Uj2SO_mqVc#>El@j#QZRKH@O+0v8Z_KiyBysu8iuJgo4R6)k@2YH z(*8U<*ayrdL?64o_j74-B7H@tHKINXRo@N!oljFc{uz5j!|7b>J{g}02U;l=%dV%; zl0JW~lU(sJM98M<;;<=j68K!|BRRB5@A6Ip@Cx|tx|K2l8Rd9?7R9}bUl`)~)3;Ao zJ8`!V+&&V9>}Hzyw;v4kFt&?w>m&Zsl7)e5&$+A+xT&4ri|%^L~n|JVJq4JkH+Gp>l=rh0wUVIt$VDjdH-m>8>}eAM-P5q7cA zsAtd`*h@imIjxcK9&{(Q(qsPt3VA|Z$Vi6Q1 zgS1LH#Jle~r5wz`YQZcfgg>AVuSq-tgFQV7VCK1v3yx*&cA8U{JRQ)L*%xZw(H zA%Y0hdS}M!FhsB<^Vcv zDGq-XrD(pR#^DyP6_)bm($mKslHg}_kEZd@o0taP3(8SPZk1MQ&6rOUc~77c84ckD zu&+hAjL< zD@;9;-gc{w?M;Vom}|gzcKM0?3w=t7YolG%@r>M8H11Sa zgy38Qp_&!8-dW+#-Po84=MEAXgS^!tF)-^=iduG~8vW7rN#HNOrGy)`PoI1jrB8X6 z07w^9oYusTHa4Jy^S688C#%0Ynd_MsOQM!qWP?Tk_688mq(?)|CgVfV?Q}m!%xttm zXDMs$&7;7j$`2AUoy3=(5UF$Lk{<7T#;)eoX;iL(^_pYnj=>{<*Z_k8U=)g(1`Ftl z?7S8+Qz zrZ_m)Ocj*oRlBw7iQbOG>L1`g#dZ_CIk9w3ON0)Bohl3425~R`iGz^k zg#XQKdk)~@D)hTeyU>qn2a~#Q{ONhwQk~Jo`Wxyh>c5fk<*wJagFsC3q|LXV@U8+O z2CmG&mERcB-V~qB&mrV{*&=v@h|ncw9dEX6vBrX3-KeU(p76pr!#>-6K0oVz)lOOi zQ^HjKR%r3WD`AchwiCv-KjLrN&x}0^vf~qJ+z%~6cFZeOX3P8RwpFsYix$odTovM( zV4HxgB|t*w7gE^;%fuR*w#?}3LgIb>w;#v=O*wba_ZB^x^gKeQI?DE}0^lF>| zy10u{W}5Xm2b<`G9S-H@1t#Nm8p3`=@v9v;A-{o+p~pIJ5HvOy_%+*N#R89;b#%p^ zI?g*l59hL;rggz=`+kF|X>EVQbC=u$RbH zA3`{)b11QBqx2n|1v}De3O%CRYC!_~-O%UN*}MA=*YcLQp+R&)q|c5|BrXCVQW&J> zsR{^Hcd>mQkn5`_%K~TKn+u|Pt`3ZSa0z@*0D!@F(P`z+Az!6BiVFr`(Pd5;>(E+c zkvmD%1C*`uTEz{{iBbfTX|z#;nmf&*3L?Vm3%g?VVnFSQdfRD>iDu?AW_Fd_rC(3`WTlFZSyjH> zupPuk2%AOTPVZFc=i^OZQeyw8UU325mG*CS>Wob6zf;)%@K#0)3D?q4r-O)CZ^3R? zxxpx}9$ZDGwr@2i-}j={C%Q^nkD26S9(j2K$>zZF2h+g!IG}%dqMK9btFfhb4fX`A zcm(TZ>Gu?X@%1gZ`n-4fprB($=zt&&(}1uB8^couz~V4!B9A0?lXd=|aOYyfX@k`# zpvo#BzjRQ73;IVqx;4w$nXT^>yjevU+i?`(1G@;C&2=8u)ZU()Hi|G**~@y$(8L-F z3H~v}Um15i9kze`$I7s4!o=!M&D`FGvc!?Q$EGA8Gn$WN1HHI=YnHjeeBEuOUog?A z_ps%A!*9cM42!Wskb7d!KLRDZ%T5tP6z*GR&(aEVdY$raVZ)%fwOUZshV15Bj1^e@ zwhUY=elWHUkQ;3@Y?85T+vdOh1KVRED|+^QZ?|etx7wjzGR93J@j?Ho|9Wkn@^wGA z(G8LqRk5x8cbAc$OQxMl)u^p+g z*)IPC?%?NZ;x1@LWmIwJVC1Wxf^8Gs+^%FS69;p4r@7nEdp|{=Hybt6EscfRk_B>S za;UIoy>C(qR+@<$Eg`(|hEOUMf>}1jdh)j8Ac6^t(aGWX5mWP2eG88qZ~^Uom-QyQ zCpkc)O8d{h=ABcc(e|k|OM8ztd^}QX>ZpKWXmRx5tL__xf_|_yF=S4@ z*G!SD8k62q!VFFML`srO4GJ^z>DTD{3)iR{PAA5LK|8H9_p*6;vk^nQ)fnvv{AU$k zr{-b;N2qRyz>7&R^a#TRE5CM!p*5WWJX`3rO>4%_YUt!%6GDB4bewgNH8=C7gT z`g}a`-uc7V)dcQr_Unbmrva#?cex|+#>iDL{!`2KDeGVTBKnvj^>!QCCFu>OEw7)N zjANIy_)D3-!g3y|*!kO$lyx7amS?+jBYj(&ARSyJO2wrZ09^%}I>SxxTzs)KXmw1;QX zQWm!p+5Oy5I(Wj(&J_Rkfb)r;^V0L4=<~AfU9dl?s`}r=fEVwE!0rpb-(Ef7*icn9 zHUM(5(Ndb@f^r%ns5QK~?}?lNtdbq~64V^nIr6etay%zDIzEJQO#aTex%W;s(lfaC zeN9jIax3oWo^LwWdtYkZj5YR0`62heW@4ah2)a|s(t-qfhO~KZRCXaNiNn8@ zcbAt&U^Tlg(e}nQAM0hd*V=Ntcq?oRojsYnbs_XN=N~$3lGh^fuG?j;^Hw)3OmU)) zH^YyQT%?__WyW-@gptWgaM#1p{53!?IMN^kCzy62C;!IEOuU)bmz5$H*|fCRUbk& zZwzPN>uQkjfuF$_MK7jB@X61oGcM|fy$_x90V_YB+spr-P$cxU)13%}^EZy>NNJZ7 z(N9INTU2_y5o}3yN~z>c%7zQ~H-^})u;cv2)l0JfLubEbzuj%|k8|F5>DTR*6Ww4m z9!I+N^bVOpAVkoI3cI;KakVsdJLbZRMtoT!d1}$s@Ze(5Ef9WtuX?W<0KN~W--i}|50K{uUPU~LVBcwr{2A+dDjJypa;L964KodcSnqu6< zkyf9gvmi@XE^I9KNq|4~bqeW>ju^`c3b7V-rhE;fMYzuS>Wfra9AtZZMdOmcl(QBu zM8fc$&HamF@@Ydvm{lS7wsg7iH=1!T8xzcW6`=(15iur!Np}yg)4rPC-;4X`?1(+y zuX&3`j85{Bc;1$LI+J&eQ@%A8Ag)_3tm@pV$3Ul`@1p|B z7r%)*v%)}>L76e0kuG?E3)3B$Brs;xc(a8#P7Lq#nH%fhmGn!ym#oAG9I(r0g}OoL zlOi@=|56KC63Bt;DgsQ^$bA3aG?o1PH#0j0YB3^VKi^sVokVOF4z(oAd7{g5!{(d;0gmY|me3IzBWJ&7uG6(c2%(}bWV zj>K5Rx(cRzFDpO(Vy}DQ{mA!Ihb*H}ca=UQtTffB#)i~syS?vBXVNGb1IK_jfW*Md&Y;{ahZkcW2aKQ%7UoV_* zEC^mHeE)UG-tO5k^5yz2-cv^by(4Cps_C0T?XEF%RTBl^BXK zzCfBfG?ScU0bvX!L!D#;p$u0dBRNDJ#xdHqTI&Qm)t`(qR}~spY+K^HF1-95QHp9qdop_WjL&t33ueY8>l?73&RWY8tmx2``*YsZ~jl*0kq+oD%2& z^dwj)dxf3uAC^n~n}lVa)Y*v0P2$_Sz8mSV4{+F_;jf{}jL&h2_?c7(2!?HXZ7Drp zft5S%EAExw2NbdPVYBt}&&9_7SSoU4pS)-shkSnMm$+87?=5qv`FjAyiW#7a?}QiR zXK~;7obgGAqrcO+YsH!7Q=L?$z(U?n*PyF8aQDk#eqi=(W_G>}(zIU{T_ zErqa_&OPZBS_S#X#9*~#`}DBNQ_#!R0b_M9 zP~OXEwwRg7N=mu^u#$DyP~PJ;({=?E_b-n9fH-;Md6n$9Z{T3=5*t+YfrJyi80!0C z75Dp{{6FNPMAn+<23Enr0DcDrh)*ypdEH!2RWxho0H4H#yA?p~K(Ar&_Eg*LDVWcw z+^=M2MNKR2{GKhL#*+?Z8g~dM&k62Qi#-ca)a}@KV6g&<>}Opim+g@&eJ-{*6up`= z*|}z+b1~~~v>NzPQ_id79&GzKH}#$gkb0)D$$$*bZqzKeU*mF{DCxlkwG{({#p zfF>Fkt8nGjsT$&X%}m0X`%m{DQ=sKqCQcgWF?3rk2e18r;9M>mBlr}hH!Xrzbh6(5 zAe{(u0kiIbV5dP&iwdliEWHoii{Dpu~>t4Mpwvt#& zs9}(p5JU3u%}=cKbZiM>k)SAG9#5ZO1Ka-7(fV--l=@TyTn#sk7a*#rH5EwhrXhT{ zKl5_hB7C_uTzlSvl&)v}X+{g-QSi)cjvph-pwx(-6kAulQS;--SapUZ^jJeYfDpPf za&1`U=;yGzqJP99fpcu$&Wv=L1Gytezm2+(g~|R;vX;ryKn(afAtz}Hz}}$5Cy3zM z-zJbtuY%hI^)X&z5(AVg4urOp9>h8P4;Vu%K0J9&`9H6^u3bmpSJ$?V{3Saf;d(aOZA-obG*8|C;>1l5)74^pLHIw z_+D+p$7bu?@p?A#8~L{mx1$Y^k3Hr8@=o4IaX`kO4#<@=t8i}q$0m_EIBmvON+YOF1Cm|dsOXLm?CZji5|U|)JGB_^z-Y6!N* z-+0OLC~;c&gj(ZhSZHfoB(O6!ww!T$=bDiNUkul@JY5>oIRG@OQQ|&pg!z*O zcW{CeS_N{hHw8nUgQ9y@-n3<8U4Qnrd#@Sbc^N%14_jtGR(JwN3=r8iBF~e9PCHCa z3r+3xYhO-1&GdE*Xa9^3#r#VAj$pYZs@yfu-`sAEJFke`cR8l6r|rTohUul#+sVJb z7bcQmqpjHE8pjvqUdFIL%oVhPgzd@nIdj9{=sSmn&?e+wwxA@xXff92O!G*RlvenM3UJp3n)kzFvG?Co``}Q?dny@!AS40}7hX7{lCnz9^u9@=T z>@IYcpB=oeA;6S~l&;t9?C}?8JP4jQF@ifL%zjr2$2TU2jkSM@pF~j_h)Uc7Lfj0F z87=R|cLM63JMA&2cb5|w^2a-nYiMVp3Z}en(;yL-Q=~&Qtzr24Yb8(|y*gy^FSB>& zco_{VdJna+J?>nIGyJk81|F5V_4bbYR)#XY=7(c3x&PZV0ce`Zc|FE9$)~{^`9%Jo zmi^2M?JH>X0X6GHK;pXl*8i(16W;iVTg5}JziRvG&&nIj`_jv3uXr|qgSS2#m|Nl$ zXX;&fHHur~4MY&=*pV^(PVLA!^X4;Sn17l1<{{)BG2{S?O%8TURZa@}}$fwNcWF zK2d(aW5GAWmKUW-^6J^IK-}DO`}fRe3nCXKJ`Sj)+?~5)au&8_DW7ZR)gOz=N#L}I z;yna;*-!QF1b|yF66fOzn)CMOoL!a^Yuc$cTx(Jc&Q5(m@@Bm7x~X#3J{`IR4qoGn zJ}0SBTjf~Ije5rY_hU9OV(#BH>7S`sBSF17K06kn~S zfh-4Z%oL=K{1i8|E?jzR+|3e2csc-AQV@B=s^{az0hIg%9B# z()AAVt-4@r>pd|gi=!%q>K_SS0jGS*X|`#-IQ;*Pn#jMSCMU9Uu_Ev*s;S}H;71Ur zeQS-+^uD6=&&Co5UgIUDgITqV)|)c|@{DP?ytIk2r2b1cCvid4`%ygy{PfgEPhe_X z!nYHRH?O%27h|12Bt%KqciWBS zB;TZe<~?q?gSwqM#w+Lu(@ySonYJ8YkGlCNeaIwi^Cp;zlg!?fWhWMFMne{HE5Nq6 zmM7hE)5i31EvJYBqci-_D-*QMQo(x8P#l=&QBhs;+<^l6(9W4yh7Ygh+l(K~x|p63dFU7k5!6b0xG#&44lV<9Dm^8N>Y+_~YW zFfL2{f?jCb&P>KolOLsA)tGa*5&u1}CEH?pq1^7$;Hu+;hwTZGcs||jV<#k!RKNP9 zkW-BbYiyJ z*^+`L1s_lRI_truUAc8xetoXuVGq)HXIElqk;tM-=IcZkelycE@+YV-7b(H|m*V3k zw;2pA9uh|WQ)-M5U@F7wj=vjb0^6Q8uB@;k?EDtg)NLl8s8x|=$T`%DoO}T(Rz%yWdb%4Ubp^8A?;;aml7zAWt;T91FSRc=-?5?6+kG!wo6(cB zZneYc%6G!0$Ql`559h*Mh_V4XHxAEj$J2NldLj7>op!U>rRcYRuK1?rT(Sb zG@+0mo*Rh+qhqHA9g@8~x!iCDv*d@ONP&{-j%NC$q-!fUjnrCbEidy26Qr<6ne$_D zC+kX4l&(N9=l+Po$i=YZ)e-=al^80P$Dq#27b9#S9I+8B>A(_YQH^Ajv`>GQ*;kSxo5w?P6Eq*1V*zc!KMI>WT$)~?x z2z=f5cfm@Y$74-fpag-jw=KpttGpFOTDzYm%2=D)c`Yza&ou06&vzNs6>dHYTkG6DN}EidYs%DR;^j9iY9Gus5n)$l#+& z!FXgOLiTi$2o2y&DP~}O{4{_y)Oytjvn zj{?^*Omy3V{fLBj4O!i8n}8YL1mv4HPgXHEX*ZKtjAhywz0|doL_Lhc?3Ti{qU=>b z&lguzJ@OI%^DE7!n{;7^jlpQub=&SfGPlMW{xVEuh(igB#6iQd&ogu6q8}U-jkf&1 zKk!BoyarY&npuM-OJjA9MB#QI6#li#<%uqufe7gCFxEPf}F{@g@Jx=6%Gxi4DZjuj#bt!#z=BSJBI=StaCNm%1s9hQ;!5%|Z%`Xvsye&i^z1Moyv!1oioOzqK8{i*gS$HNq zjHkC>tNDjl%n%tQ8JTx&C%Dvha}5LQHbQHGTMKNe|EWki1*&z@V8a(M@15mFrJ?8N zPtFTN^0uTVx5It=uRi{=#guRzKWv(5r=>~fBl!dFyFD3x%oULUYFP}JZdvSjwe}Y` zQOpZhKT1TNrSS63&el6m@eXa4eP)?IuArpE%wSl<%z~GuFq+S@5Z1({5C!6!!O+AB zu$$FCqDLiP^Q-;f+#PUsC{Sy)5t?|(L!EVc)bZ8)&-Z-b4cgc*K$uZ|r?;prk-b0fl#Ymt!`bUZ=!?#OURUo9h7cLmW39d;ABRQbwOHOYV z@Fj-ACoLLY)wL{Q)-=b#gjy!t;`%Fp3_K=ZN`CJ4h$b6|5bHj^G|FZ500bC`m%{J3 z*Ut!i)_neK2G2>p+GVLsdMJ?EE7XmhGArf zA*7W)8%k7bK&KBd)IyZ$HDuvZ>hbvoGu>-6?^5dp(Wn*y2Ht?lIJp=Rmd>QJ>c?eu zC+NP;zGCevIF0FOfHKx-5R5nBJ}=HrRiKH~3@;b;W)9en?C{leqv8ZR3^T$y z|9l*$0K+}?2C`m|3vdo-zV(D;S}6y13ej3@Fq#2-ht5x7ToA99tx=$Ak*LA1C>S4X zvG^iyALfW5w6=KeZ)3h2f&DLvjR8~<<%kcU+Q zt#%LA94@RBgZ}~E{PpqA6^P;j-2ERWFj!i8776rI6o3OJHJzXM^C&p*`_Kj|z;Bw6 zmH7YQ2$mPdRkqZK*(<>49>=;w$)+9OkE(a z@`m&sg2M{_9B3)Ead!L@s6+s(sVN8U(Gih((7kuTdmRO^iyd4p5>&N9nMb&cTKSzl z#Bu(1qa8z$Ts3yOB+{X;MR`eEJW|qh=^D`muHqBCvJirEVeZco-t=530@BeBW)xB+ zTw{?x{-etSCspPlE*VMwb}uqbWQPMCdz`mZ{%T6)l@a=;yfi=~=`Kf}6++h%_`?hX z`7B>SGhywV%7Mw^hY75J4No0~-{mTAhV7^l(SA97>8X{nq?`y5Tj1ez)|rDiea5#8$7H7_2@e z{%ii|UAnKtDL;vfFzb39ya!N5p=D?*S4U#`EsCoGM!BDr5iQW{C5-RGBkG ziCN;Adn*psdOnhegOh*5=?BLwE0G$6PxjYz`M)}KG~ zSEmh32Azg;`R>>`9OnvCO(1syj{i*t?Hw$%9Bxv1$e}>jLJ||y@Vj(i2v*ANQ**ef+fs~&Mhb- zGv_g^Oll&lT+1qJ08*&<>H%>9v=WlIq6Om_9V)3>W0ZCC1}1*sUE2pxqz*4sURu6a&=pV(}b4WZJW&->a@M#InF19 zx!Rbe^w2^|E{H-v&Em78UBdPOZj?%=`9;slCjt308xr1NazV$3OvD_i z`mS#4RQ<11_jJ@SrNe8hf^A*uDkA>py33PoeT(8&57fmV6JNs~@nag~2@gpok@ySZ z_q{lyK$myqG#AQzl1sO^%xKJf`sVlCHAfKVbydR-mV1$kllzU-t(?r=JT0F@i}(WN zvMqzbAYiQsR_*eygn4q=A(}nnX_IT!FlGb3E;c!YZ16?K{7+H9Bnr`Sf~yO-=pv#b z;DipMT6P4~n*X8$R!pP_@$a+(;P|S3nE1^70>^4Vsrn^vlU!4?mGqEA)8XBP8jc-T)kO^>kjV%~ne$zZs$uDFj31*B?B7hvpS#Y?h0=BYv5{ zNcqX?%r_<0p!lUu@i6r-qCi=2vDI_!?-}9W;SW9{f|u{zop(wg0ZiK-NX*3-Y;u4U zkaml*_29cpeTv5fDsW4a%TNh*3>X9t+!-ow_%k-nDB9K`lP?X_o(5|y2;>ZOUDVk* z-xRN&^-2uec=VXg_+PRL{aQ9j%7LQT4LdBJggguLr+^U03PbTzm-= zHNkwkc@J_wIdsvFurpA%MbO_#>?*VcR|zJZx?}BltvKBm@p)0tcB!Rn*CX48^pn{W zeszuwu|+7Qa=G5Zy;UcKW}Id?lSNW};RW^3S~@{#UIN_R$Ttl4l<_O!1eJIubkQbd zpdA0oC-Xs2>mJMNBx;t@$Y+y!J0Q^VN-qBsv&W@`242#C=j41g(LMyqrcBgGI(zc9 z2=yM*C^!1tSsp^|7Qh3Spdgov&tm&P<)BldL3GV&^dU!_NCzG3z(lhvmA%Z4u9GCb zPK?%5^>LlJ(&96oelj59A5Oj4ihg!Sg96uG091bqp~hN%QSti?M}V!Y%x*mo>X^i# z6A}g!{*Zg#AIOO(O>%$d4_$gmFOd2dP<;&A`uWe21Dx^KwXbn2TCF4Ags!uV9`_z6 zzf;GROQlXF2$EbIDVg_ObVB(nP%KCn9din64@a-;l;4;|^&hLXx+PfV;-|TCV3&U) zy4CpP0%OmXg!Z#a%o(Z3DZs5aeIDyA zPBYp;+VUMe1IYKN<{ql{CDLcpl|iW6YV6!+)^oMMp(3uRbJUEC=Uy6%WqhfZO{bjw z>f`UCx%X)HJfz3*mGfaN&-M+5hxd{AWpL2aB$1HEC`rtK?7_(_?|itO*i^h&&etiX z6QvvQ;lra`Ux_#v$z}W~K++p=>oC}!7g!k6Cs50(&JA}!uu(G9KVEmzo^lY_Q|yAk zaq|^(nX4MJC8)i<{VFp^AU^O=Xt18ihS^g)-!7MHRV$0MhCOB_?2XRe5`u~uSu&_+ zHSq9{$sh<)>V!9;s^ZWNO8Wgc=4BZ=niKb!KjjrZ?1KW+9Uz#ss;n}UNpzw%JG{Rz ztbFT?L?9)D?&UPqKN%3Eo~fZv0Y*=fOrwbLo7hX0^MR|F&Ai(PELHJyremP*Lt6Qy zc}b~G2FWd`&dTpnf@#oaYPrE>eecec_k}d3d$2|3D(dqj)zZ(=UgHG!I!D#n5Pe3; z7u*_Zs738u!UUs62vAx8jLPsMxVhy9tSl_eGwsd$Z#fjvBp8c+ZG3DrW5KGYF8(X6 zj^Me)6R}nfKmXs1aDi1Lx0jmFcnVb)-5m!6${{y6b<5EK0UYEsCS3X8UXF_p%=P~J ziP3e&9^RL~v?k>@M_9WI=}iyCHy|v69Ah zBNNyj&X(H-gj*%D>jJfhGm^-M+Z65FWBdw(MALZmai9srrsPnHCR-WbIWj~$|p^Au4vpZLY4XN9(NcVU54Fg5xs7sK0`u1Rli@P z-YRHb76T4+4P)XFpUnL4CPpLk|FGsjw>-{^e*j^rcDl+h_!wVxCwg{vb}F|M z?D#qcz|0-2i&gw(x?Gh`ZP7Am5zO})EQedZ7uysqeEWU+7qX6-&r&*QMq*n0kpVSp zUw46C)-`f|yFPg~^ z;^y~^yVMv(0)}r>e523rvG7wfaIk+V#cutUjxS5Ve#Ekrt{-h=^CPx!+(1A{sR9=- zc z8ZVrp`s5Y<%}O=(OuozWgD!usGeq!E8D=D>pAx6s6NgRBdcUWZP2w!rg$XUOBh}XA zDOfJk++=%Jtb$Tj65}|?~6QX@)0|5wn)0g~ltR$U)HzruE$`9S^MX0{B600r^x z#V%<0=UyR|;;CmDU!NLtN=We0i8$ZS#bj_~O6~1A)xdr$boykptu{S7Z*{|VE=mnk|bVPW{IQ-|uw9&j`lwYyJ{5?q0*CN{N0b zRONd8jhZ=67RlpvY+Y?wGS!dX*mnlc>aN~FN?fgZ{g7UB8t`|f0yn|f7(k} z<(nhHk>Dcskwi%2s*RhVir>1tcIx?);K_4(Z*BUHjHf(zOxf)7IZLsZgAW{WAZ4!| zqh6li8c7;ci__sXcJ<{^w{)uPhuKkKJ?m@w59^}@dfiEO3%o@iiS!29Av>ZA5k@$X zA;RN4EN4J9Zyx?P2&wF`do?2o#2)ktZT{!|E+r;P+Je+l?xxhzpk$^toY!IqrRx;I zCkttzAE_XZEm9Fl5p$r~NVxLJ8nzGXhONUUTFZ#OCRN|bDpmdP5;mb$efK_U$P6_} zl0g zGGPs$av^2wK=;NhrmXi#GtS3wBmJ9`-t_^N+^CIA6NPi|MfqwZ7|j#YlHD6#Z3Oa?+nAdE~RY0Xv!o!^0%d3gj3->0G!TL*!I8R zf8#CFc!L}1k##j!=;Mx`rQ&Gbxnm9$(t++9MaG4ChKh7I+i3$7~;B0&Ms-( z!fkd?-biUz%|_q|Ts4MFc2L9tInJg+@8QbPve zfVUge6J(*{LowN72S3MqI;rl)*kAg=G7L4}vIX(NdHmFTr<#!UFffSw2`9yqBR)Zw zG_A2F3N*|u$do`f^(lo3w%jZ${>J{(M!wPSsNn(up>u(=uUKD;^8ePRE!+>i z@hlLsN@wtPKDVPf_lnvXGBEAPy(&cWz4Vmiq*gWTFl~e@A?a7CEdO$Mops%u;{uy@ z@C(Rz>Z|Qn7Kfw!no}h31m!i`L8sb|&cBFrC87gPr>Jm4`K}XyafbCp9S{$aUXy5#cfq$D+a@q0!@hwbYY!vZmTm+1=Ih-^)a##*e(U_v&NzIiy`qx&?dU0iPEPn7(Dd=9Zq6 zX8d5(JkB&UIUoF~ZmWWYO0~q_F-sS)+hFhUp7tj#uLxEeukbtD8n?fnMq1pHrL}Xp znfsH_cr58;Lwq21^nT(ZycOiVsTI$>&+D0AxQ3R-_5`M%!8k1Do7N<~dK$;TK0i5YoKld> zUU|UqN9P;gxn60#7|XZN7fcaA-#1oCmw&Jjhxu?tBVL4VAG#ywkTU=Ggw}cgm>Q#i z<+OT4Xf;@i`Gv}!`{uakyZFL?X$dY z5Wkwm68Sh44X%VmliQ?Gvq*QAoUd(voP-UEpfWH;&`11YCHhnxO#2;Z*iNr0q48(2 zszj8xubq!%Y=T#d5nmEEcYVtgCzgl5>VIkVwH=3Ku7+g=F^MEecQ1@EB8Jlf z_}^BLlTmWLT*Q+nU6F;M#<*sM3a1Ed|rSR++$a>s%s7_`? z>K#mMh|Ijt%tB}cwku0W5j)_f7r~1`OhSD>6t%a8>*DL0hz&mvh_!GeX6#LR)fg&K zcqT=NAt`y;4eH-Hf~ZCO2dm&6pXj-ymUl1i9x2<5{x|>+!sSoIA2#F;oF>n@s|1c! z2Y+j3{rp?!y_v>x$Y@!c5z@F+;O!GF)q1V2h+1n`t5S}1wU2%tXjM=~?s-oXY=Mhu zLv+P^FoX8Uy48&yXOKsodEw4z+w@0&o$bZw=6A*5yBaG`j}3OqXLA&PQI$C9ogeM3CuTltt0p1{8GFHVKLu8XIkn^8`u3Cf{z$Pe)= zNEkBG?L~jj`^VG(c;OFs2L33zTWbi3UhZfMAUXMs$lyPB{tRm3KTB3tb&b6(xqHg) zc_g)KO+uA&LFMOE83)q{8kO;9p{hM)#O;(aA}fO*zJDigoUuEUgK?a}%*a@-VtXgA zEcjx0#3~QE*x@cMVWw2i))Z=2mT)BYa1*`wToIm<<^fXsQN?6uy#r{B=2FR5Ns-Bh z;17Dg57Hd$D1h|?+} z?;`Ig4+f|Yk*T|h@}&yy zfMJ<@dM4)@yANO!ifi0|G@xpbCUT~c@15%Gwa5Ar3s)M@x6oQ~HdJokv=FmdA0A@w zUD_v)%YhA1hs1id}1XrU|dpnK|~tABLUX^Gw>W`56)uLQqn^fh_#9 z5PMer=sWqd-K2P@@0`{|UcMAy9?MSPN zM~*0^xkRa2-v{hfn_Msmn)3FGp|xbF69^a zZF;WEFbH&pm$*^|{Jv+?6S_DBeNECWw(Bums<+u`nX1+oMw*?VjA|CBJ#s&xpM7P>#@8ZsC!NVJnt)_m{J1M0avPz3+Mz-X0V3uH9eAa6_T5_=!-aQ; zyq0s|-Xz5vmpsZvvF`lee(3YqOZr89bV7+Sr_p2k452|7)_Ee8^dXntvPds;BVcY; zAIAzMVTzqT9U_0!P{pFgq|DTpc=VV2ZT}B?-ef>qwp;L@a*n7Ss&T*qIEy? z6LFdSh!WS$)Iw#t;^U&dlc>>cVHLB_`q#|+ol z(fDji00$!-aR7MQXAwroU;pnhV+C6^N%-z0<>p`Bv8dBneCWU8`Wu|Yp(Q#1b z6$E`dI!$Omqo9P;5fwN*>?k5V+6y1>p8r?By=hlgD_Rm2}@3eL@RD{hW)wX?V@xzDdtco$3AgOI| z#e6EHp8?RcKwQNm&+&O0a^~LzpD!0+qz}jGno7Z zQ}*wNDUW#EugJraHk`*%zt*SZT8|8>YZ45KGG1AP4k&B$>}tu9qLWPNLPKowd$h)= z4%#>31v@Or>bS4ozZ0J0oEc`Y=+9GC30@XF1|C#??mqUHkZx@ClSw4y&H-Uax9wIWj1I62sCwOL7;04(lUNzizS^LAjxV5EAQ5 z1M_RnVjcD7}X9j75E&tSH&@R}#pj(LH;N1&Z zOuA9J(9B+1e@JbmBFgj@sjM_)Hc8Gae2t2vU^(KSA=TXsswYjKS&uDb!bfgRYc_-(6=3P;XIli36U-I8kP{K)+ z3jIxu!=ux)$QQofeDWM~SGz>vmb((CaIeh2+>TH>aT!^f9$PmyAG zy4&B7iuk&3Dlz|Wid?%4aS0uh(mM%|LfEDhae^KbT zvFRpF@WeFcp39W9uwVFhh^lGY{G)OHtFu{Pl2^~#V>s|mv+)VDziG~w650xH@!=Iy zw#9_o`2sH+BHB7gv+_y5;osA7xh35z7H!uk{1=yhr*Q~OuocA*($48|+p7s+&af%qZn^6|`k@hR>qW&F+PF0jC}fv011 zC$w8W6qww7=TuZ6JfP7=@#udexc&AgkLa&}g&vu9>wt?&BwDKc@DO+D&o^V<>xDNw7rP zqj=RL9+&;o95esh;y2w z(Qf~4aFgj{BQHkrXb#k1>HdJV+tqMAo=%EMYpKAW4<`F4?VVUJCvcQ0XYKsAo|K1i zZ|TFilYgU=q8Jv;z1AW>=RpQZYywX1B0R%AADyZCNURH%HauEFjHqa^JTF{MDlidb zC!@_BCK-wTZm>layxxkriE3piz4eP3EMzld-5NegOdvm|ACV{CdBw@NRV#HoydfOJ zrcI;F<4~5q5p)?UOub95`vXr-rhar~$i57GT~GDxlsh9oY`xsYYF4TC-re$ms^q1c zoVN|kGBO^`#^QayriK;xT_Mj~VP&L!b-U!tTIZ5w(RFr8Qm2OCqmSpWmap76gS@0D z=s4FIIN@1|Wgd{p{>6+`x7wu8=()aXLz);pYaP*Zc~Nv)ha91ce;FPRixb7ZyxH=o zFNgbK_=|+4qKI?fGZgpF?V6JZP?K9L#HY2b)XgeX zZ>Q|65EhE47hSb3IcDpy$2CgjBWXGa9+lOE4jD_jiHW*5ozqwdIvmpcKZ$V6YHd>WAhVt#vrwn_90R02 z(27>5vC>lyP51`kdsyH=X}|-u+9T7sda=#vFfHf&Di+a!a|CYEycgTUZb94r&Vncv z)g(QGExu=9O}lwiM&xl^aNN!dw5+$`%(5LiUs@MbvuwF#P&9EW~S_u+diMh z(XbxzNBul6)lgU?*?k@%zs*7xOT^F{JFA~1eoa^J+rRrSE*^sQJ57>7OY8hHtbt#}bqk%&q-GOZbE&{rG~AzclFz*k1(J(6#b^gdB}7X%;5-#r+XB zI=&W<3cH@;Qgz?j_7m0H7wq1X`uQRWP!o6QTjLv+GzBZ3CqZLq>Vh)S$^QwH^3aIb zFKJd^%O&e9MT3G)YkcTSBzL?gh)o1%$<%1!q4Vf7PHG*7c2?_7?xf*D#=FpR(UV^< zKT{oJA^TMW1%5>Dw+lqqH~fSvF#)d_UUaH6h_`DLAjFPE70*zZPag?`fIGtSbOqU9W3DZfG) z2d#=ksyYGhQaiPB30jKV+Ty!wvcXmm&nCV3QN{~Dl{ZI>hdpHP*3PQZ*ig^KfGsV` zCYFHW8!fRN2(Lk*hE;3^8aelI65Qy4#tC|+O~M+Lmg$loc>M=M4m07#LJQAaZBUa+ zzC(E`826!U=(!1t&eUNV&FfvGk^6Aw-_y4MbWho~;HsIW^DVYDeoq5AeDQ z8li{QOC!K8JScbP|J{>z#n-u+SFAzdA6=g+jjV6?|^HDM9z9c8>|QnSZY$J}$RT99n_I(StpZG;t^_385b#*^aC3O8aF- z3gWDmhfrdmg7hP3ssuisH~WPt)NrD|(rO{dY(8%I%8~ zwc9s6wbh!);lrrES)av;z2(wa$iy`}nO-l3SIJrOOm2%4y|$d5v2qIe@U`Fub<8cAZJNK)EQ{kZl5Jtr~JfBEVI>mqW$%BivNP;7DtO^!ajl@`Nj2%i6iC-Gwq2woNPg z<%78&$#ST=)#`0(MzJVMvEj0-mUpE1^J{p^wmRUWha6X_c!Z3va~1K~N+=mEOdFf3 z!v6H(0_68PO*2Bb>B=_&?~{J0vk!hLx{~MF7QMMhdu-vQj)Jt)E5P5vneD^#I_1H2Z-rR}&ny^MJ1!5;px%6BsFnJQ@sdq%aqfv1C!EKgPmk+uVKhc2}ezWDW zOj+N3Wr^={h}DDSJx8u4*>MKzq^zFp_jSOEy+bamY(psq3;#<`WEHNHr`1Qbl`sak z8*Q$sF(@GoNZaW(FiYkW;;Q#GRU8B5|; z3f1P3jKwe@Kqy(=7-Zs9bPFHl?!A*qbA}+tSi^WBal3F}OCtN9Bl@9DDVuhvQ&AwO zf)rjywHjnde$qnO8Qwy^y3YWWZa3YfRal`b^6h{@u(PTJsp#WsWkb3L4H99 zLc(<{C03b_Nk)=V9`xf{Z*C*kPnT){I$}4cw$Xq6SxUHub0>|^ir=_Cltw*Cn&jC* zATjU}-|gih7A#fZ@Jtl;Gw+3+fA!9rfF*Etxw`3!=Mk&fAk&M?-5c9lkDLvcn=gbTig&Pfa1FQOoz`9rT zVv*fJ-LM5fvl>4USLa`xLI*-~? zlAJ)lgP5gPjs$DnpSQk(*Rd^H_A`7K)lW%;g!NeAL!jJns;ZzJtU^Fj4#{NZ&Lr<$ zG+mG|e`}P0JD&6ZxpG6mU9_!cov=Z;Fc%|TwwqdU!%=khL)D`9tR{P!dS+?Pff=aJ z6`)Q3n4*Cnf_b*C0#f~d^ZV)wbt~6pot4A6ksa?qX>uN{+7wm1)b_pLQ?t#`z)QA2 zRvgsSNmHVmxg0h`{=1V#q+BIdbpx!H|5G9*adH(e;IEe9+-TFiQm#q) zbx`!I6{>&&?|@Lb#$Mku!9v8>#~M-U0t3IE13&(={P*MQDHupoCa^;hO`@rD-`0(K zTQi#wws0yrw@F}ITrCx>!C1Rt;Er@^cIvW9RReCkX69QupI(KhQ|QpWcKs$ z?4dFm*mk0;1qEq*^k-i2`fj>*rh_{Qn+=0tWrhLjV512#~g06RW>($Z$Vi~NArcAcQH%a%tfNhyHkfJC###SZ{EB_~w zu+XGP{Ff+`Y3gj!$MRk#5Z~5*zgs3!u=C*qsTe8l0Ll8kOKTID2xQnh039R5jpqBJgKbZP2e>m2erj^f#jh z@l7{>V3OcLffBqm%na2eZArApo0r@V z)&Q6)vA81x3&``!CVba&A6qf?2R4A|F+d6%`hgwcb+|d$TgpK7b@u}~_!vy#@-`0u zn;iIH;h>>M{9t4EC=JHFOPuLmCk>kTK?OCG(tw9(=!(qm089go8emROC${dBDZ=3+ zy^hPw{uV|ej8lASz8hA7CP&H)cQ@nWmtV#hk8#qzR)1}YByL@YoBF?ia21&VQJv#r zJ^zg-)-5Y{l@)EqqufSu%AG2lnqA)ba-hF90`FK)pAXl9n|d%q+65D21!SL*9!al& zQGjRst*6Z4o^-EzbISu{C*UYab~6`UiPrJFo~<&C6M$yZk`rmesnBQFERWU|w~PF4v($|qX^sg}fIi`TFn zVU&UgQ_N#h7P0?et%$WV>cq?qkM$Nde@oG|+cQG)5zhqAkTwf%l_W2vSQf+?)hFAg zR_^9{)Wsj-|6ye^ujX9xJ1lC^U@LQAS3m7JU%hS(5I~e|v^grav43U$_?pET zzK>b9?WwtD2iu$4lZK&ZTorj+LVrEv7H!MgI3|;~LD~!Bjmjc9^AB|!0)KZIIU0wFpxP~abt!*H|)skfKo{9 zKqg>!^*AK}1`GtOpWQ2!!r~mOWRXSl4 z=D+)IE{~S)O7r}VtaIS!M-~aPyGj7Vb?(;7wxGr>FULost|7+u`i*l*zl=0kfn`*M zap{n>nb@8$#qx%EVJ^c!mj2nE?I6%#D)>CXbNEcmf9_rT94sb)6TB_rJ*({qgyRY} z7fa=K_P8>U4{n$Y&i_>gqU2FPM>)?M|87(m!jq^e~?7} zEN+d%5!BJWzIL=28hl2=T3p>)BGAi860l*DMRb;d z%Xg;y`@!aZU=!Yf^tSac%a$b<7v053NXAmFge{u?r7(3`X%&he%LFNWCsBGSXxQBf&!4)*R247>r zccm+CGOb2@m;^(+ENzG=Le!aSH1%?blONG6u>IIH@bl0J8~2q$;&?*EV%aglJt*j5 zuF9`$Pu1-BgOn@1VOvHjnuo&kV`}?oR%G$zcDuz7G$VXbt{|-g2qOJ?Jefi(6{^d# zpetIXx+hvTcl*fI@9r%vJjxf$XL-yyy!rSs#Tp&yrd94^7O(epr{#3dUG5p8!(VpO zf41_FPt%VU!*q?V?a*P$fO5duuJK zGD(REoPqyP@#>MVkpL4aeVSiTchkidXRMm(%h&{zfS~Q<_3V}$PqUc4|K(4IJzDyv zwq3o^|CtU{dz(9KI5c}?Sf{XTxj1n#h0HCClSg-5Wzsz$`2w>1q~oDsaom))lWcb+ z6ONmBLgN*yjB0pm?^%kGL$!j8)|JksyR9x2j=cp%;pwjcmsf=}*o2m!YfvWLfwUx&MG5COlOO2`VD@pRU=N zXML2P1G*J{!<{XW@+L>UHO%wHerqJ#=LDJgZZlp~zCF%Lsu!mxfBPAI+Br}|1^aF3 zh>p&y;JVA-kL}5%KXAaOhiGoDq7m&{Lw@*lmk<8r=+A0x$B|#HTf~ztm;Fv&9}-2U=2q3BN@Ijp}0bwdYV&ijP@V~FWK_<8b?d&2^3aS-R;|M42H z_vlMqFTE6*oxEosmJ`KcBLE5ZaBy%Z?|CqQw$?iL1I{1wo|#0-L7?T6A4GWkRnn~@ z{s#VMZKA0-nu$HtQO_rTZ?J5kraRv}h|?rlEGWIl)%mk=@;?i+XG?di-1o#QWQbf< zpoF2av9`AAt#|92d#~*e7G*pfUwt3ojKYaE3NM%-twaBGgKOL-n z@m$^TIFz#gv;3;(MjNWfbN-P-8tP9jgyjokde{^iqO#!nbu0fx9{Ui6B~&>t8H0Vz zJW!`o`>Oug&4eiz2fZZg);$GW2nRmr0@)fG36~{&a_tEDi>rG-PV5fvjQ8e#S?VT& z?u+XV3>R>6t6nNw4^w_w`H^4iC*iGv(Yz?3&}bNeO-CcVe)RX15p$%M8FVIK61`?d z_`LEJ?$r^*+`GI~9UDJt6X9v-nk`ZXfvQg@)qrz4RJZ0|n%MV=md5m2s+9n_QhC%X z>RQVqvoX+u{<}>%0`Zii_u}I$@63vL4 z1j*^~nwVp)Ic>9z_@s{u|3bqub`IWT7FG^}&?QLeujBo=`5D z^A3tiKU70O<~esvPPQ_@C4o%OC)C?ZK|SOkgrGs8G|7wh17?rYKd1z?v)WRJFeTj* z%><){aJox$Cb?J?+xsLbK;cSY5k|v9dc(61Qw31lzBcVeMIlkE2G@V3Q`$U)_6~C# zPAx+h5h-wN75EjRzZ2sxa99dSf#XVzvOM`t(?6Chu=7=KIIIM!GeO5I|LO&gmkcU1 zUi9~wy3;03qkej}1s$S4c{K}8kGBxy< z#SC?6-i;l9qrUt?mH`agVqc|4R+`?n=ab}58rNJSxJ7sirQM5Rb}k{G+} zV}=w;j6%vbmZZp*eH~=q_nonh?95=ynB~2De$Vf}_kI5|^O-T{+~+>`IoEZ4mviOS zia67g@x1guEI5aRO206#_qd~X(G&-1zROybvvx+cdVm|W&`lnt9~j)C!f(P)Yi_1VIf+)PJIz&(ase1EQRR{?WX2`+RwV;KB54m8|(mV>OJ4< zsTKW#qwI?>>&wrCUh1}uok05qF<&jXjos6|{rAEd1$IF0Qausl^D&uX{cqZAwFI&M zh`apSTpS!9xDsL`RaO)9+`xuTud|C#Ii5P}~)F&tI zWq$-%{d3FZ?Tr1L+_V)#7q0V!mO1l;LMb(+FJBv^=_?m`s-mf!m4*Kj6%4ZUOdw-C zK!`#C;+}hn5}Ed)+pfPaa~CN9ftEU}$s{`fZmu4Ji604&T=m3c)*xCGDNJa34*4bO zp3;+yHcmYUUsqP?MAXW52NnXI73jxl@TJ7Y#9LtjRdv6c(?!jYwSY~8qgT@})h5lOoXcj}M zNBYc7$f`TRgcqZ`nNPEMr^(vu*cI=hR5<{Z2Do)~3ys4*v=iA6(v5=U2anb?fOz*m z0t(3J+9z?k#(BANg7Co@e2jfk_l+( zX=#4z!c~B_xIs0V4CD`5;iD0&eu|ZtI@q1(d?jLP{w7GHEO9L<7`R8jM$=uTnbF{8 zd^BIM8Zu(nsT^J5N15Q3UAVYzYUtVfCX?sN*V(fh0tN*Dsn{oCALy=U@LU8Tui*n9DxduC8pWi~ljg|z3gieI#I6UYn|Zi9 z;)9OpxSYfZJ?{qJ_L^MnV;y6E%*1-$T=v0hhHOsuI8BlJh$Zn#yV}sjOf#?A&T-q1 zu`{8$kO`#luYa&h&lj(__heQAO=nN}oBhQAAtVquKUnDTl?Xr|2pg zUs-BwLWc7c)qxeu4w8c><Gez?h%E*e~pwsP~b-RDyrPys83F5!Y z`0O9k2svL??s@Q0wviaj7W}KqhMzDQV@q=`i#7Qtkwg3t%=Pxll9L{k@DGb(&Bldm&_Ydy~T?dFk&Jk73`^b&*39PNgQE?X91j;<5X0PdaCeoNRk z+M@M*PUM}B#jTd@kR>TR*gNd=VKJg%uom@?M4$T$duS^XPFoFdXg!e!QbgQ5G`}(P z=-HgS;6M;9fdC1$l1)ov+O-&|!A|T8c zDZ6hEB|yi6#h&+N-?U0t^mv4A?U71%OTS5FzeTTLO z11PD*GAyQ?zO(0TB$WAMH5e_-_w;;KQwqEYQ=fd|DHiqD-bgSP@nV+u_2&L{et{U8 zdr)XygFJNH=>b{OrnR6^W>i@EX{u7*K!#_3bdBknP}}*Dxn4Kvbi;OZ?OVUFMux|< z#An@_%QnJQJ#YJS^2alN7~DxM)ZT|IXD0UiLbPRB(0UHtxQh|OXEq-_O5yw|BV6Y9 zQ%2xa|4KkCLTBv7U%9@{7Qb#9>`w2H2SLndV&gOi2FP( z1WXf3$mMdTpb)fPE0(lAWzUM2V+ykGF10o0jc~ZUF+I>HCK{@C{M(=7e9fA4c{&!5 zp&ZMr8vR!T)2F6M?K+{yNij;-Gfh=~EW(G~l2Wf;D9;{Kl~suF|A57P0Y#eL&y{S+chwdln9) zSaK=#6-G^@rg9glsEB+~>frjN)Zss#>NpeIk~0JC^RtBZ^+$^MIKsW44Qg_F^WsaN zq18c%M;?M$?J<7638WG`wL&uu8t7o}C zs6!NdKxiaH+dt;6sT<_qH75IVdP5W>!*^WD7hPO6ObYhtF4PM_Co;~F1^~6`E;l9K z_$7z|rM~AVB(wY1-Ih{uQw2jY)Sbcev#YXcx(L>rX}j`$lhG~@56wzYZa2@>)OfOH z9u)p4_1PG_i1r?XfT<%+{5JcnCp;AcE}WBOAq4_XA0@MCJ+a3^T8#lJpYhAgHLo|b zR*j;Pp_k^_n#Mm|1OQw!k(oY5va zv-M$XVtawCKXZ8rJb{&$HA;^C98Tvh4*|06c&ATmZus0W>zs+#h@(trj%BFps^mf4}{rCXkEs%LyBVExbi?^ z`p-IRG69yNKwznkrtZ0q;43^I^E&^unXYKkVm#Y#cEKZ#1y=N%IzXBj*ui31FeA;o z<|D0B&pBlPVqeKC44jdokjx6ee5SC@021~@*mZ){Y|@G zSB;I4Jh%SB)u{25I?qYB3||0eV~*Qg&7wtXtk_nGp;!}PZ^bZIY!>d1qsHT^*1X2) zbs?XZ&dhX|30A2y)K6xD#9oO2r;p97M=}ccHK_b7oj;8+Bi;Hb0ZtP5fVk|p8u7?P z3kUd=K8*mn!j&ju&r6xNN%A`ei1Y;YfADI{mTNIB)xaar$rpi_`2ColQChfqHv%gh z*okFrFyhSfxPP7446II~s?YW3g6P3fVba{VZ1@bu3bOa)?E82D3Ee$So4TflpG&TQ zJCLC^9H&cJWnSwKI){$xBc}G%Jas4c0{cKtr=hqKNU`_an;L3?Bh4A-TRNcGI8?v# zxexnR#Zz$r?X7phvX==NpXJ;QyAKjOrJ?nJ_08P;p827YtJ8xbcMYgLR+*yc(-1Uf zsuGd_Gip4)*UU1Y431x>_%PDKqgGznEW9h5dxFk;px%Q8<3IDf(6A{puxLpkPo`qp z?fARKv8UohPgO+wOy`I!lf_L|9fIoWce@JEE&cx7ru@d+;ew0P?VG1>H^+>uiC9kj zL6W{{P;xTJOmQsziUdusO5&iN^D2RNQbZ6vxF0B!anM}44HWPv8&icC!6Tx9$uAvF7426yIli$rjq>g zb>sBVva5XqBnQ|FXxEl#KghFNhMse7jo7QHeTFN6A2klZiK+fj%9nx{I)}_PU)-BN z9a(B#tQ*Tqzgqo`@*55-B`hP;+~)&mU$Fx#%+!Hhh>R>t|GKPTs?gQp<<76PP~>w; z9eD%uSz~nRt(a?PgGv|sEwv08jNr!ng(HI8=ILFpD?8WjX*TaF5%IDaF7p)PNM9Y= zt1^f{ab68bOY1oS@6&-_I#Ooc5nXrYw!IiBs_=1Iqrp)f6{YEmW48^ad7^EUyAj)% zWm5=)+|eOoIR4lOzSKBSEgDM!3P5zc3EN8=3!yA&6nPQFMmoWW3KqoPp?*^#p>bWa z&&?FB`Lx0Q!E+{w+hhj?CyS5}8C!hQ@8#m@`QOX3Qs@n05%6l$!VEr!&u!4ddC@AW z+?9%9)8An!4Kk?y!cFsuSoDnalB7AC)nSoQu`v1$2Wq2%VCbAmfGrx~uLoWALX(Ta zDf#9j3JU$dGbW+LCj$XvM?RGMz6s><{^YsJp?V`5(WT2vc+bgCm>&Z|3`jooq-62- z^lH1U^0n(8)Z-g;@aGF?7}W@GfKnT)$NM@TB%k#;h$+2itzACdzg~V zDErmW@Y(hdX70Y5Vo4(^JnL_fqoWAN3*Zx~6WybC++^O@QH+3g-{dW>yqbk4 zXX<)C<9tBV6!;HukQ?N!b&$}ml}Q^#Z+d!o%#p|{4ONtTwq!-`EL^O8I)Rys3>J z%y)`MhB@}3dBFVczN-Rem-uF1>oVG40o|P!yqhRYq>+fGea@hS0j1+4taZEPZ1+Eq zHXwxZXW%b->8E91&nnYJ5D9f1{i}FF7v`+VyE`bMX;ApX)D88q0|`kIc)xqR^%Z*o zr8PH8$RyV=EE2O6XbQA|R9diSe@K*d0<1ye8a-S_-||u<^)fkV|&{S(qI0fxE*=qN|~FUDTJrl_SwXOx5r3z6;+lLRdh;2 z5ZRsBzg>_~IaW_ySFKzH)b>opVP=N7>Nj2=Z4M1nRA+4n5LxtPGV;*0VWf{nm>MBD zvy~3KRGPTe=pqBp5HN{(I#mc4$@#xF{W`)DMK54m-%=5OWE`bJQ z5jWoxwmd+DA_7hp`Vb*LwG}bGZ;^M!HUUO9qiez6Do==5l-bFfq89_dq~S$V1$({} zKgs+~~BHAy9c$@frumG5Va0}G;?-{rKbC>YVqTYPj zT`3LW^*k^6G?q;9v7}OP`lB-Q)ZP4T>sA;T5}HMfFhRLe|8$sa8q$;1Ov_a(0D%K5 z^qR;m5uU=zi7R92$w|A49i=G^Y>-Hw9fbMy(ZzE#dzwi)Y=H(p{zKaxh}Hma(65D3 zFg?wyst4C>yoNQ-e(sLd8T>8tH$0|+L;58(pFd|;Y#mG%2%@KR-fKqjlm|A2fBR3^ z$fQ|G;CeLQj#2^gObE1k%%NkSOQc@qZs&``+ zU@u!9a>^vGEc|l3TVM@?lE(wTa(7&tYZiWk7ll|i!iS(Vayz0{ zWI0~WJ-`1(WCH~Kw87L;w5Ge9yYSXS>FAa5&RJ~2q37-fMibWsv~U>$dNtwiyBnKY z`@};+U2*HKp9wG_ZVPp|0>29eSLBRu1$>H>kpdq&qF;ClkY!P|gXDe#V`cPVmP9I{ z(^gP$%e~Lv(&`4yZw5e#CPY-TUQ%XhM3|&gFB{j{=OZGJD)gw;JjjjEm zp1n1C%$VVT0+^>|z!YK<_tb$-#C;(MduxOCPI>Fnnz)6)d#81v;G+xZT+P*j`GcY< zx8=Qqx#*XDE`ni7f)0RVhoz+I*z1aKFh4EBX<_RKakCEJ-|w&Aq<+GU5v5&ZjUh(` z%ldDfI&2>`s-rG%Ur1n~puGv`ZFX6>GUcUsQK>&~9O+?WJ94>=>T#IUFo`@8 zhZITRa^Q)@`@maFA-r7Rq>Y-lV!}lYA-><;g>NO2ljv){CjTqNK!mJSVD=Z!_fUjb z|70yaY-c$JIyRk;M?8-P{ZyWBz{O~z4=rZ;gi+p^HJia!{szyi9MAszRkM-v68%Cp z4sA(!DSMPd=`8M3*)SjqLE;2X>c)^q_h>n`S~KJrZmlUYiN#paw!K(Y7-|@29n<0Z z>qDPCSE0JisI`w~Pk)s<%Bcg1=ca4~>cFQCrKN1s2hLSf@oRv3vJHJ4n;avxrn$E) zOkYiY6dA9`9ggQIZ`0BHCpp3^Vi=>F-FiZrPGVI>xu0I*>Wo4l@y!SjKw)pOv^~b2 zq*P3C+#cj1HqCHEGcoEmK9Q}wXq1z0uqiSm9Px{F6B*F~Rw@%+#CwkJnbq~_Eng*!Xwr~fiI zetUwwIXanoh_-^y9C0@+WWpV)eiF8=qI*S)f>eKMzr*F8~spPn-a2PR$aui7)8L-6bx6Lbd;fp!wNDUDn?=ykj)mYMZ(Rjex~1cRUsR zu=f2&t#_-P7hoUyjp^ZL`+1g=QKnn$c+O!)&q_yA%&%8#cHB7G8YffGF^~*>o$1@2 z$O?RX`<=4-=JHdg`w?meCl4WUXI8}_M^GS=T9R+Snr1NTfGgL?C+`@b+^AD58E*43 zXy0$K52MWp*`81ysJG|qhI`NbYgleN%1jEkXGnU#&j%ZD+?E2G?N?nB*fQT@TKCT{ zSAJ@erd@=N;XQt_;8~@=Q3r^%{qw>bVd9(Q8ax3*fD+#GfHsI@2wkneCOU4H1H*XN zloQrYE*8Zq9`TuJgk9*btY@jmgq1-eBiaS%-h8{SNyay$$cKyuyY;J+>9z!x?ZY6q z{?aDSdzT{h*GBV(>%U|AuS|fgA+-=j z#O*?A6?{++lUnG)+J>%lVfDB(w#Q>C+AAW)p`p%O z&Sb*(=6<_I3h{|b0l%R; z?-H+IzE6TkqQef7sY|!;sffepzW3k(O-W~H{LjT7+RhY&cPD+N)llCp{m!P)a1yj0 zC<^db*-7?#$4xB-+ny55^xC^jC$=8lbTv(*$&EeO?&2B#(MJEa{~1p})X^E=&I03R z;iko-spoBJ+bqags!8q5Q>x(a@!U?wUz&LKW#&b16NNA{3^xDJTTK52s5bkieD+P> zLOz_grm^XeDoWCJd3W4qbl@GS53TR=JG>C z)_5F>^3f|FT#sDIm!k@>TXx(<>4&u3db;C2wE7si64nw0;ZqI_;tRE&Nd=%>K!nVT z6;9KGsnc$LW4{=e z>iD(&GX#Z{=o8*mD4A(H{kS`iMiobq2WZb)feZ>vas=Oi4I+8iuW+ggEeINe>?)Zw z<<7aV=i8P!9LxwFC~EzUh>QrOHSN0OaMK3s7S)PrES4;uBu|;69-fF({CB@GJIhH* zKL1QUJvV1t^1692>Fc2|_U?JFXvS=P_@xz6o59khV|!kNJ_|S5+N~M;QdlzjtjhyY zF4Zleq4Z@cvmbiyICuv`+|cVIbPpVRLfeh{hNf85qd%6k3qigJPsYG@I=*p7=(>~t zy?q5GDZv#5SH)@9p3tAv>H1`S;%*y zLwNC5yC-mEwE^Bb-c3#1bB3kVx>b8x$Bw2Xg6X*dbt|veZlUY}H**5Vn-q8~xR(>l zM;y~U%~r7wCEIxn7Oqbm57{Lm$x<&V$LtK~qvnLZfri~%_@4$VYHK&5X&z_1+`c^c zTS+*xM+B}=HT3~2dGNxQUx`Q^;`=v(H-~Hzi~EX&p#NO{#i(b%J1>)#9iUoQZS zgT*SEqlGMZXIT$&vd81ZdY5sfIhoE&%%!zM^yL)5NGIO6V88Rd^9D%UscF%2%V1su zUZynz|K6_Eg5Z?U{=wS++FB|)AA0Qp_$>KHvF`rkcC%2mp(}tT(<7-L-u04@g&6w_ zIUMnuDtWeTCy0rmSZtn+&ky3xnum(8mKKk2jIyj@+ThF^&B43EWhNleXNh^%x(O+W z42d3Z*5yFFjPS&agz-~LlXBaHJnNPpka3hmj~}1zXJ5fGrQ7(UcYMR2%h2+vO2u`K zhDd`|K236969hr)!ZPBLY^AiF;vd0h?jhqZab6tqe+bpFGE4;TIniBUlyFgZZl0T+ zkx!D9NHQ=G+RvWvHeOFPQ#G~dw1VD!n@_QGiN7gj1+fvN9`xjTXgC|p*Y>2T85AYS zkyQJ=e+^91BUSC)IfrcSXZWAap*}@qNKf2nwdl9+9T)5aI(^47X1cg*9T3caL6bssZaQ}xRP3kN0OYkbCOy*ZnGn3I%0IA?ODFwvV< zBvaRJJW}%H#B1ybh^Yn+1A^!aPVzvMu%SCQf&u^?Y*7ppNhF zX_l#6&zOiv{th9w=y(O5?E>C__ea^VBn-HpZ6a+yxx&CSq?#&IlfBeT>p;8aO>Ldy zT9ZJUP4KvWpI`!oiV%~h;b|sYwj0E@{bjQMCI$$#mXE#bfmQP1{QiDh7UIypo;9$Z zxH752z+B04`r`Ngr{(P!{zYu_T;r)|WsX&AX26^}YV>f8cD zwk1xvZw+2(zbgdbNaH=eV~p!$O1_xmODWs;|+29z3=eBm={LURzYSFGj?xa6hl2&LElNWWTB zeO3NW_WW<$L&Oxk2sqqxZ%%)Uwwx!ch?bG1hv&ki=qx|*Tc;kHXtW%gb2B+d6}|6O z)8GH1F54JXK>RvkJ)vP~1IpjEk6HFjz)qqi&1Mf&yJBu^BraWq0u`xwPQQo1;SL32 zUJISnWqmsM!vzRsq$71ei^|Stv6%)X(GLOJiPHIixQ*@SfK4+EH-#X7 zDnfli?bC*e)hFF#@ey|`zW6N4y&petBlhx!cVJJAXu*BcNR1 z58=cK^vUEpR(kVdDCl_^^{(8$P47J2F*vQ%00R8;MbNjLH|1u1*8{3JOtjayV` zO_(rV{m1t{q-dzn{^i!02d?9(5h2p6v98d8OhJ-AVqrd?if7x z`F#W6r3KqTyrNw@0lbSwT@BjiZMJD|+1L8#XpOdOC&~N8d>*#!(2+n-9XcgJMDFtZXHWjmv1@+m!p@$d?bst zCBqGf!%UpdMOrNxoaj4JuAPqXvIv^|R@8CVvK8c%8KG45fM@mGos-11!N*r&Kh{Vw z+<&E-O27A?VP5==%dV{{{f_>&DL^9)VV{`HxGM7;HrksEz4E-^F-i)mB31BK`)eaj z)@5#@oK}~s|D`<+nBuABhjA3AwX$Ct9b&VETH8iVdEEmkh)o49AiJ(->^fvZM-@NoX{XBG{N; z^gUbq#e|*dA%#JnHL%>GHZ%M~M4(}&^uklq%l16irMOc^4^>@aCUZDm0T!DkhY_l` zLQO@1Lv)4?Tg)cTY3+U1wg5)5rA1U42Nifx)&Zur$||l69NVTaUgv&_H@hn#quVJv zteT+z-1PQ85y59}M)31i`kumbSI@kQarg8PcD7w?BDSfAQf z!hK(Xk~8GjkJ&Ri8bsWF^U5xFaGR3%@`uYT67NQ6mpC&S|E+=hVHQ>{YlgC2rmhkW}OO+L?@EY%6pM8(JcF2Rk@UBh!MKjh(INs_>i zb$#dxN$HT-*10qN9*5zcn$8Wq?%z*BcB2@Hhr6oN_1Gi#C+LgDZEtHekhZ)Dw#}C) zDVmK8Bj#^TTj1l!9`7f#LhFce&Ymi$&+7Z$K`uXHON&ZWqZC;K5%lPGJY>Sw!Qr<} zo#6Qx0TcZ3H%->NdPX;{pN<`A|GP!rO3wq)$ZKyoppGnx*r8^Fn6j%0axXFh*8WKl z>uE5$Gb}6gF|C>_j176Qi-5ImS%gW@j*7b*7{0o|{HXlyrnQJ%guc*i11X-bk+2u- zyb~f(AK1>SZsnhU&l)mF_Ils*t|V{PSlehYlhp@6>p{j=f!%i+T*_3TY=hyB#_7W~Z(eb-o(B&u3uxwAq1AA> z8HClz9!^-EoFSX>5zHPh5IKo2{mBN1muF4MVXs8F0w2~eXqGc5nc87~ya2mRo70P( z4Igx`ihB|Bl|SCt_rU|9(|C^ey-DS?wNwSb&b$W&^V~Pv|5D3vTpvNr<`P>!{XdG& zdIcrt7$H-EIeEYGD7Hw&ZTgnd@ZMY2zSzE!WrIQ!MxQK<*H^yNBc>gpWNllTRJO$5-$4(SLkFC=-PbT$9mLoX|9OS(3!Jy8XbSG#C%g#-}pR_!IzDu4Azg#d_YjFs=TS^+57NhuH-xB zK0QS)eNp0-j-4EeX@eHoN$#$)!od1uV8;Z$HtC}rdn4tgH{bXo^$E&xH+q_$%vio~ z>m~uc`NpxBzgFiOlD2kZusBK8zPxM_rm{+4TnJdG-v^3IEFi5J*p4@0Sx>P`jL!Si zNCfPR*s$GeRnh6BFEpp68fp)WHqnm?-Fn9YA>aIM%UY?=N~?xExmK#8`) zN+L`7IeW3@@204xgHNia7hn5O9{sp9xVA-$kQh*b5Y{A8Y(GQ916yb|oMh2fuT6Ob$V*bm$H*`l_i&>|~46tiZEh`*nbVRB-D#o$d ztPoDhVjUZg7deX>+s<9peJbi<)x}HkeBf-z5A#q?XvSp)O9(sdC9;D%@4p|5qsEW zoN(>j9|$(=yRUck!`Mb)AWUhVK7Qy4oSZ<;q7R<^QD=+!jbE&O)3YHfwL7u3uisG| zYrgE8cD4E!K)?s|y=$=6`PiFfSa9Rg1H1$VQBUkj7IEY9O!{(ly$RMw%40GvX54E7 z$~JT4X!6%V<>G~rY3kPJfzZ^~N+R8#1CuC+Z$Mmol2BsiG*0_A7`*SoH*fzfJhv=e{Z=3FfLQKo1_Gn+?4w6StXwg#;E_Kne{K}=k#bUh*sh{x+QP+L}mV;YNu;uqD8WunNvJ7kRh9wL~ z^cIVN6aD%ypG+u01LGF4Pt$J63AH6}FGm7KQL*Nu5}rQUNksjVCn4Sp=T08NrU2}7 zw79?O2Kpu1Rn|Du0rFmU28oX$jSZKthXg4D(oAqWg#A7D2B9530(8zz?f z1V7MP>uZ$WJxaI+5MIAd#2p2nTbt;cEs?WDQ)jq}RS?IaDvA?bPrTslx0&@l8~u}n z2{*4jCZPX6Z9U#H*mpYdhCf8BW4g(&DflyJ2(3{f1wocTs^kqso)kAv>ShM*0grYY zIR=*CI-o@}()HlKcBzTHf7Wh$+9;ZQ@%u$TL!?ACb~)f|sMqN;tg zF;8@3PzCwJ!Gl|AEt>1GN<&$0`$O8-NP%*>)^SzHeZ{B3tTph{qramZxoV9Fv_Jo_ zlhzrqyVhPN*@zCSI^HtRrAuJzM_7BzrvB@dY^CfvwZ{gw*k~Kt==Kpo-^4-b#;XUa#4%y@FUyzSpSLp7Y1gR_wa8LT`}Osjhi-?kv^Uq> zJ++rA2ic^w^Al@;ejKNEw8ANq_lSQBSSp}?6v!pp0m@qy5qA%@Ej^1PAr&jP*3*CV zQypa9h&Y9H=&77J>dEp?7vZ>C_!QvzDem1-xM!PDEAu$lUsYeOJPw?713R-knZ|8& zj9p{V0M$^JY>#V?qe|w=esk|Z52^^V6l2YUcU2GGsVBOtwM#3~ZJvGFo?G;rv9HhB zvZ&pbHrj`kA&9v3cghUfcK1p3gcmhzvDRM`nc>?_d}p}3NqJWW%hA60GMm~8U$Ujw;;3*fL*WEO z-1+f~Y`fPTQC8Ba$CHe*+dL`tG9S)QS4~WT%2+}v+v7|O6Nop%re7Hf9Tgi~0C#1y zAWK^a_K#2F;}gWIka6;?iZzCO+;DSMg}k{lb=opIjQ$`{_?GU<{nV{eW!+cNe2@G$ zm_Y@Jw2+wV+1RMof=O5i1%B>WdbvXFdBmp3Ga+AlSDH~UY0I~BxqR(EO|>8^ zh=Kq5lDxUQs}wvWp@cBmg*m4x9|h7bpG%q046Uml#VTSHJgdC zpM%$kUjr*iYVFvWco{XgvNpBphMbC^wyi)8UJbNXsVDy%<~5RRF$EGp?SiNscCKlO z-Hd*4z~Y}2y5N?(J`)lM-jeKpx=tUR(|Bz|LWWILi+u9~dRhv3cdRkUUEloOC@ zff}-6(PqoF2QhoipOE^gH>K+HmeuB%6|HFHaI?&CgxrX=M!Q;8^SA5M6>bP*C0}c1 ztcP;1Y#9eOT|>&hW5m%#sfzAeX~--&-uTwXH5W(-0E7+I9UM7Lj&xuC>e-U>*e?3f z4;e-I+l^e}XwS$$Foqk+VW+CpmS<%U51Ll5+;U;q&(_^O$9gijD!9cbG%z0q98z?d z6yy1JZI}DoReQOk(dWAd4Fhv7@O2>N>R8=P+iX9m&Ry<%{ajep0K_)@9OGvUVwe!F zW_aS2ECD!~z_g>W4Ih@iT*2}&0Vk*~r087Ky2{PuQu0o7|3L?@zV5l%)pwP2ni-qm zfM0}?uuof1M^Ar^vEu@~)CoGL-e={$=uA^!2m5+fD2jBaY+WeN=w&T0^@{KiccDO|+H5 zajP%IasGzwYev*XTaCvM^CRck4aG6Q*ya7^`XS4B%I-(->Eoq$cwCO|T2VK7Vvrwv zqr9-)UZ6BNG*D?AEfq=;=ufAR-$iMWP|piW5=$9pRFPa-LeKf#`9)&x*)ID4bo?5+ zy=Hxpc$Qzhgm~aYapg$Bm!zKy10!I7)C!|zvmmWt$J)o1qq!H9MqGA@nNg^VuR6`= zFFyULPC?+s8C6R?{)i5@BVV}!Wiwt`aiP8*f9~TNIO(jG0(gf)?NLcgrENc%P@*kB zxmARZZL+zp^QdUFatdc0e!tTh7Sn0Ko-bRFF-%atx-7Q!@XGWRr0jV~gFDo--$WL_ zh1zRlfdkPro7`*i6`DgwdMr%NoH@hF6o2cChjz5&jW3t7@4DA@WTn4E7?13@2z!xh zJD3M12L+9gXnP|sVaE@=3?E9n+U;J`i$&<1a@3a8Y7lXzK(`SS`A?qY_16$)Pj*78 zjzXSnVx+b!Cb*vD?VKJJcOOA&{EZU^&+MJ0-mXx2huDpA4+MEhZ`giW-l&=`?%Rh1 z;N5~3S5t>I1Nt{?sXMB#tO&#P8)j~l98GB1bcMW8hI!j#PEo?O2WQcF<{2-)4*TxZ zh-n(eQHgBM4pEG6M_qFU1}}`822j5k4p!#KViHy+P8=*VCX_#I*q&P6FaRgCb|`CH zkZP1CNd0TyCOXvkRYo9^(5Hkr9M&5WJYhF+6HAFo3_gLgmjAppZ?=XDN zRpghD8?%+>f)EmPjophJ8S@MiX=r<38>G})+2?s+(>CmGyFM5&HgWCfy}|R*C1KJ4 z`J&Wkz87WPVyfP6rrQz8u=7JG4JU)(jklXp5}?I)rxi{=v+H_EIYP^k9V#R5Vlcm& zxvDOUZTPFFYs(!V1ZhqgG{UqJ3jTLqc*LK$6KjHbb?HJ&z%}Sklb5Nayd5gxy@6+f z?Af-um~Rr+CwdJ%-=nOH3{gLYl5f*KKc2a>$moO1-l=x~Ff4qOU{4Og(ir=n-Qm^> z5Np_lMvXK(rhQ2JoVPGZb5;I#u+CXY7lx zt=8sH70lk}-nZ+jETiXd{9L0ydBllc;|l!6v7nEAU}nJlOaj+CJL_**`}FezdDlr- z4lp}E(2){f$_#&el1>#tC%gFCEVhQGtcxZR$j+6=ZqkQm& z-`}J}@A!*cc+CTFP!nY3(^*xlsYSjM>$KT)2ki_Im$jMEpkRGfML3;FMM^RgB&?$R z@i48CS*9|LwWMreJ-v!Unnas<-9M+ILS*ebC*I541@>8q6|jQ@wj$1%z=%_8FdIpP7KiJoqUWMYX&h zjEk-6pmm&h-b2u`=hmZE?dj&DBP!w?3J|1Ca3cjC!}Cz^+Ve=xsrj;M3aznjZGUaCxLR5z+3+zci?$}BMX&C6qQ`d~iRQApKtU+7@{r{_rU zvsL;UL6WD%&-Q}umm%$6i7%+dp9Cm~!`}3D7}4Rzed#A3eG*;cIfbi&urWPF0%^)R zYWl6os^j5jH{4^^UeeJAF`O*nWv^*`N7OPLe!*$cM#T?9_rCaGnD+*0_GC5gXV@Mt zqV%38#0MHYfcMWWSkb4uqCxxlbyVAX!RVf`{*x?#s8h3<64i~tZPQHJZ+;+8=j&sy zX-R-g+tl6WPJzY~RvCg`_LyggJ!y%*bA^|vXU+DbqdPGHqo;Wt$B}>cv9B$D3X6i@ z2?c9>Ps=LP{7V;6xt`oOOc(d}l>ZMy`opJReQ=>i)tw4|z5e@EYw%8Gs8++W#my1% z0ZUnr$P@4dY#NJjv#UWt`)I6;@3Y7szkgrgF~k13)Kf(hdvom>R4GHA{(i?5@pcgn zs@{h*mM12?`0fJ)KYFgaWlAF-axb=>~<+hDAW!YpjxncYK&EW#DglB5KRyL>WUQM#ej4j=Q~uQ! zs$x;I<;`8XBO7-N3-_GIZg4K`8~aK`-z$B#9QHEoBqjReBtI!#abDfROT~b^Kg6XqB7ph_)Lhy@T=xZtKo)x$0RJzWH%mjVJ;i z*Nm;TQC^7S;NYfHKU1c=Re4u9uxvF?=#P!3p~?6KbEcK1%wV2~?yTQNg&$q->bX)2 zgcV5)N;9NLNB2(Uw3!&v%X1n~>k_jM8;pU+=RlLHDZ;h;x4yM4@!1%JEEJ%m1s<*H zlvF&dU^xX+!y^8o2X+ltBs!@Jr8C2?8^3Gc&V8EAVaf2dMSZ}PFT+<@-BB}Ru&;b{ z%KQowyo(V;LP~QiBZC&!USDyNmw>Ekf(8UU$Pn^t>vd;Pi3K{-G_UYW3tx+P#RKy- zY7~l6=T=oe^77n@Z~ZTm%(e!{Nt680L^T~D%9jugo%fshaLypBiD#ukC$%4zk3(yfgDG z+s%sdH%B5Pw-(tq_5REb@L%+2TIK076c-aVayV@#{W}7s#~M8Ed7Fed9v~9P=k{oT z?CBr?R~lxJRwd_f#bVET6U`s@Sm@(vy&%<~ETPKS(ZCZLJLY78H0X3N(HfYHBX zz{EkOpq~@_svCY|p&TIR(B0wX>j4cg%>QBQJENKkmuNu*lqv$!K~$tj2dRNjq$s@! zN)r%}-b;WG5CM@A5a}iKDov1HrT5;u^bR4kgd{KCd*52`$NQOel5^&KIp>?1z4y$7 z^@<{*ebqsEX1yClvj$f}zUdb&YS5;o3pkjGb#hGnuh3SU9}6_s%y7AP&28q2U*_0- zwxd^OOs!(!d*IQ!@x;%y#Ke!FcLb$Q=xViARQoP9_X$dMT+vcn<`+3q7P0zk0FYIC zxFHadRbf$rFn)lxWwU9!%&OY`_ar~cXe?$zg5PAg%!4kwKe{W$DzNi^@L%9Gndcac z2Y#3)2Uz@}Lj*9f=0?BwcYPL$$&~?n4RAdCJ>Eyf=@(o4;oO!+g7p&<;L%*pM}daX zQ#xdcRB#fCe0h%I;MCM%H1!3B{rs}av5h@RHKF&bk`6X_zyisMQ81ka^?zrDN)-r=V#swhR+?>T!3LR zF-QEhD|9X}K&`YB80T88nz&8tt@zOug(s?Q0ub`93ZLE9XZCM6c#Q}`YFSSE9tCBq zy^+>EAqBu+8}OqD?dGp43BG+_#h#XJFLMxQ`$Kz8ADQ)9?N^6qvFf$z?=K>f17$|7 z2A*C>{cq#>fs*xCztP2cIxFM_0Ll%WI`Cz_FuKBRkjJP)p)Iw1+%~v3dk!c7=;8+N zOjRSHt1ec4p(cZ8pN(eoD-*tP`yI@3?qh<8r`}~nPFQQ1XTXJK+n^m*HKvg&S9lTO z*k`hfhVD^Ov{P2Q{rKj2tt^&Qvx_0r+!<&O*}WwF#qV6Id?U;aTsLUwk;=E)4y)jo zZ!Rs}vY$K3JSKR{5?*a$Ma8^m!P4q*)DcI6gkG-2t2DbU&eNqCNcvxeN(RNp|BF&5 z&W~sNCR#w09%;v6Jao)6FiUMo&noMZ(l?r+e(EfvAy2MT?(AHcijj>H@HgQ*eo~PN zOD{=i0Q?TkP%0~OcLWo9>_SHHJ@9#m-Bn5?6ZEoKZI>7H+_*l&xCH}-NeKk#%{&>* zZuL}2_$;-d?D3yFo*3{-jz(4rdo`_}?1Fulwz>{{)xo3Z5`(yi=t(SbjP#x$ z2K3!^#Z?pZhM}m}%=RH5{*;B4Ef7cVZ?GPU>b zbw*7^bn5TMiFY3zz>fb;sHiuR$!!Gzb;)1j79rj1_iB5rypWZmA{MtJ4&CzF-0q0Y zIVAtJYJ3d+>zPr`;nGPEw8i42Ii_k=`Xj#LV&02sWSU&lzR}`!XicrLZD}icwPWUA z6}PNUGy&S3)8_e$ncBKZt&Eolg0}s|%y)@x*I$E?wBG{d!>6oH=UgVTvEO-Vp7GE{ z5s>r}7<6Z`s^#xWG;4Z?%*whM`^|8`2~Bv%xvw{Mdi7WyTl|n8tlxe7kS4lstw^(o z$LT|&kTjn0me=(lAI(Iik8I?3g7|$(bloQT227mqHSx|Dn=p?CtJcfmy0C!en>MS? zpBRo)CwNSdns=X8sI`ceb((=}(RzF}c2%);xS3|#IazBOV5uqkn$uP`;mmGGB`((R z2Xj^wHRTg{`upI+13`=$Bi7ce*{tocrzX5_N~Q7r4A4iJOwyIXuNV*Ne~=ZpnMY&O zmBrVK+h9rI9~z917@&09x`|u(out+-DeFy@H#c&H$@o6`89B2Z(?w)aGF7%(L(7B% zzHw2)xoLI0ZkR>H1^7=bBG3PefOB-nUo_q#?+}d_{g}vp z)vNF+!i1a-(#woboi8&}-rCl(Gyi1S-G2FTJ=oB0w$kcF9xHb;Yf|g>by;vxUislG zE8RJKTmK`vzq!nRADL{V?Cc*RaL+F$>}+<=E&n!s2bJ0M6uk;4EK5(B>2IunUZ|0Z1Te|7elNpN*zuXPqw;_ol&&#PTZ+{Uw z_rr1Pei#u&jncl*);?QfWt^gyu+yw~TTYG}JH>}bmIK4EZu}^P5+li(t88qd+IEM# z#LD$xVaOyKz`3;L8X6Lmk`M6pP7c`wiIHkRt5u!OgZx)PhmHvCeoO*odSsnNT4oN* zbjK_AWGO!b8rh-|c9q{6H6l4OeN#YB<^ss59`GwcBg@&>Jh43kx}ih1%G0~{iz&Y} zv~Vt>UUW0=SLZ)T znIs+C#1_g0j+gaEhJ!!J)9>_Y(QlW=oYu#vD&fCL-}$CP2qkRfnwceG;%Th3lip2x zr@gA_W2BW>XCaet(dA|B5#v!|)oNk3pFVINpX?zt;KiyiJM2HA^E>p@?G1L5bf#Y# zzm_v#RI6&xsg$mxA%7v&#e8(q9NYJvg7bt^_1qO3$($9-^EPH!ih{Fv>j6p_Zife2IT17atG|_GbRGG=_SMoa z?yf&vRgK}R|E!^20x*v_lT$WJl1blGFwFblZ^H9&Lj02&15)57-XuXo z&b{jgyJ_dOv4SY2s;b($e#qBkeNgM4%}muKm{3wV_%a5Os&%4OalMK0R9&HHm(e#~ zuivcQ3-y$|#AY0HOEXnIMwkedURP&nhFyGgurbmS6A(<2ux@9#j7LmG&g7$2>hfWz zMVZDUTzp@$#V*3Z;ZxtV3BR-pS778b|<0kNDk}6XnGIn zL-D;a>>d>BB4g=*#J~BTg(MYZi2II+8YIuZ=NljG_M`v+HDy1|mD(KhVfD%wT-zeb z1^%X6{pP^>z%4}M-1@9jA?>} z7+RPF_N*xPU?TB9Gn^@mqMdbR4s%1;sX^kNSZenEg`JBH)@1N9A7cw3*j}LtfZB0G zc&;Yd!rM=SZqdjASjzOQpwlJ6$)@cGUpzq!{pokf)=;|`2jZ60a(%Iz?%BFES0Mv* zbHpD8@poTx=%tD_{|bTfOy=?s2G#ve#pG@UQQcR;t;Jd_QxQ$L?e)rWjNZ7V5fQzce! zwd*EV#1q}D&I`5rBBXCse1ukBZ&)nljl;}h=_l#WULIPt@8;}iB%8aCFfSVSW7<>$ zA0F;tGY*Gkz(s!P{<2lFQar@AKq2b;tub-he1DR&zYBg+88{pA7;@YtLw=qHoPYK3 z+C#_5(GxIo@}E^^t5enW;8}Bb>FYJ<&jEvQi&>QZU^}!X&gD6+{s1m^i`lwgaIT@* z@IH9R^?(5qfGvI@manqv1orD+U5aaUJ=Ym5mZlR=br4$f&C*PtRahELX9{&f!6-zm za9eecZ$639wqg$%SwOU6(IX48gV(}w)IH5{VM5jELMejOKW|0=e@$Mp{}c|fA}(=U zkGCb;5O{As^pa?YNJEIV`;*8Ze%M*k$IUN-0x6gY0`Yuf3T7>r>p%GK#MUu!+@3$| z-ahL6ZS|9`8pA+vOC3^6yK)sLhTx2ZAnGY13+bi^L{3RcK+4;*8yq z^`kZ=9%ZQn-XeS=SsPD=8g#jKC@(_%r*kQuJT3^Fzrt~@4DmWn!ff^fxvVd;bcMNV zDq3*=Lyg6)^iH)VkI#U=N1<$?5H~`Scs-0>Uo9_1BWKdsQ=%ffyg60D=55|YxKoV0 zhJyc-CiPaPwMN^M?>=`9)vy~PsF$ybdeHjAoDxc?dbZB#%ue{lCfAU|kk-9AM7|ni zPKy`DLD}%;*PU~_IW2gq;>bq8wnVHuYrHbrIvLw`amu&1dJIU#AemG=qdkt?jPaEs z4rc2&V7cH$Br!BJS#&`UDvFFmg542JVv1NNvH}rIHIgyRYV*^}u^7oa!s%2zA)hAx zC8y7a=Oz%G=?IbPM)n3V2QU8nC(~B8#73R&AR2pJ)Gpr{>vX#cLGAm`2KB8!rx>=#{fY6j*Uxap_n{mA94ULW z3ltwA1v(YOx(!ehG$6+W{@){S3Lcx8R|(4N9Nhj1 zh4e-?EXYa%dL}nM7LRXjlCEc-aGR# z=R6^ri*K#kN%qX$;VW`AL>8~8WEQEBY@HnSp2Tm>b$mD$)zYDsG0nfJd6f%1 z4AG`J(kd9ZSD1~RPJa5b^V-lsp>SYg2b=!$VipkS@7J^FlH`MCF0Li&e>n?CeL|$v z(a532kfO4r(s2A5g`&rly;j^lFTGjP@07s+O0&q)>yWu~fS74V1*4vXq&YajKHd?Wam zFlO#}*-;j@NAlsY_DOR`a}=LK{*x(L+Y+OXc`Jz+`WGAF6oQ}9T6aLE3!MVsJ6i7p zaM2%Z4l{-Yy!}qUX_*j>4HdIDJ~M|>U>d*eGz4!i>Ec9o%g{%7g-UG4(n(=xdC z=0^I{4@k*Pp^scsgwu+ep(IAuH&e-+k~g^ApKbTWw@U{7AY85-hA{+uznd?s zAY7H?r-S6A7(fa?H^E}e?{Yz8Ws z7pE*#>=ld?3J^>38cle;GUuXEmfj+pvRM_AkGWM7&pTq;6sDhjI7`JH6+j_V(5ad& za>$ujR~sNymzQdl^a?I7@6R-@qBnA>gDCM(AHqDvS#~S0!o3~MG`A~gVn#WHM=6p7NUsms(l~P2N&z{fD5hF zEQtr4#XM75j->kNPv+f^uUz;pu>YcMXq>>6J0pDB$PGbi+%{>NE} zqY$QPfhFk#hf-Z;?g(vxX>hw~=}or@*@lY<=Ln^%h$!=*WXN?DxJF&f;0l%ouCEcug*G;_tfnVhQnY}-*bmqu5 z+8j}PredQ7=XJw{4sCu#LJyniHqcY+ z-cFb7)_Y_ZK`1+QC42-yxCs54)O~ulJ2)_&BudF@=xV8b#xQq`(18O9xjn^DGB2E$ zRTw)kzI?L7t}VoUlf}y#FFwZoF>8ZInsBrbM#@X~LyTl4Ph}?(a=lQ0kQ2`DIOl!w zj47F@cUOClyOH~AjE=~&ie671%jKXNaM$CeAYKn$(IBHR%%VnZOyo9k#4m&c#(jil z$0W5z3fl)3?{@+3m|Lyi;&tPowuA^po#26g?z9@WcUeqK3fIwXL=V6G6HS6!+1F3| zzummT^f_$0GxrGZq*Vp~{W|{y9>t#GZ%8(|buu6+N6N;npCo%9U&D|#5ie7Uz&vCeuuZbjmbThfD zW4Q$jZF3x#3Z#;=V%-6Exw_stIk&EDMs{UFmmlEvFw9qZI)lUpnXZUGzE&R3y>^N! zkw{2NfYw;1V((;O>*6i#gV{Ka&hm><8S8qiw9GLC$|8AM98w2Cp9@`)HWKW+ZGCYDT?Bst$TNKQQ};iav>Iq32m6+k z84OP^oYp1)^?G`o)1?3YdCu7+Z+4tlc_su>hZvZzCk4mOpq3s%ul9t{{!DAWa!?#R zI(klK?D%3H`(jI^SpafvV<&r3mumYAQ2pScU{R={j)g%21>ps!J%X4T9)$YlqF0r! zx_IaB-Mv&%a>&CdxO)&|TDrS|b^F*^Er;_cLgGacumeFX26|`x?2MmwFrtPp3kNZb zSln7?Zbc}68xq})-{lg}(ta7IS6)I!FSYWhwZ^w#{U&7|F=*%<2cwT;7Y?yYOScRY5b?d(=oR7Oz=}@!?3CdB{pBCz>G8?IDf5!NslV=nhRBK1 z9tnP0B$}zJ^SLBps{TERxsx$_Pj~khIh4C@?k8nbd=1~qQZo)Czg9j2AlaSHd*|pW z17eAp2lr;M%S^VD4zS0o*FgJ|tzm3Pt=XyK4yFYPqSUqD6oV`KF-h6IXS+BUzt*>)$Q}S)k3R5w!n9G3nOOsM; zDE5_w2H*_(^*@7{tYcjjSzS9}8}h7bifx7HeDTLrl{&;7y53r-!-Fxb{Za~b6rY|k ze9t$wcVg_MA3MPmdvC5NgkvT1a(-42b4?pN+uzc89PQB<)=(g$G^=CFUO-`TXY%Fo zc+_lQ8wmr+MMOf%&E`1eo-5Xtpabw&%ME~=Yo2J`gw04>Wm#n0FMawCH<%Wpm_x!Z zZSjXkMvkJgH5#*W!K*}r!vs?syWtmskb;PM@L>(?haVd&^q>&9VoxnIje@qe?pG~t z8iQ}R*?`^ldVG1%Uv3v6pauqcez$hY;wzS;fAoTF19@BnB6*v9ABUK050T->g`~hu zR2B{}cDn_!Ks}_#W#%@!wQG)X$4hwC|9f17UYq$;UMdEN`{>NsNSxOEQ#kMc$qGhjmaZ*6` zlgIZH|ME8P5jTxS>mlga;V4Y!}-eXzSRjd`68 z9&uLuw6weM=WuZ>ha4R2r`}Qk^tW^{ybH+}Bt0RZzsy^qL#MG#t(C6A>~+Z#v~ffj z@o(MeGX*);$}5ZODV)B*w2E6s7;8-dei6z-bWzWBcg>4tM}7vKHVLDt?aE@E6dzz6Vr^T!_LBktR}a5)J)Z(oI1B!qG}`R_ zFz1Nch_Py4sn*pMIK*7R#_9TUuKXq++%&qt2={2XOMA2zUc+i7^z>9#<{wv;YlKvh zI^>izX7*p8iP80~5x@C%8x0=NvGLbUXBU5k$Spfg9UVj~AbStzq~fIRY-A~jBHG>< ztJ0PH{-ZBE{NQ`xH;Y?)Kp7(aGg=Vyy+Y3e1B5rn`2^y zHS!AH0riOX-Ns4I4R1DS^QnRwv)t7!FxbB#;)O3pYM?jz^5TkX?};yxQ{#U^s_%n! zo`Z&q?+Fza&d-hpBK_6*7Ctkw9z#;X?Uy9&Qht9!3SCL;Ii$Q9&E~h=RM}T2mWckB zNEgI)pMhN!pcX<;SC^E%w3~FEbQ2YrOdL`*m$+8n?JgjT(1CVTcWz4L;`)49#s4+A za-j+?jI;jDlT#E;->#P$`p8g>1$puhzjt|;{JH|PR9hn$vN;eO{B&CWhg9)4T|_T^ zunm(gOO)d%&dWi$4S~3s4BnL;!3sRui+wnbILctrDFP>I*dhb1)yLas3LW?10AbAI z4GI4tx6|gows#=*1Yp)Xv>|WCYelTnGLgwR1YZ!7*h$NX^{5@n144Sr5mr}&ZBl=G z9*U50jEWi$6nQLIlVzk)rmN1b_|h{=hTZ7@3j64m`66*CHt}WG@4Me{m-iCEHQNMx z{Lo+BBR}YOuT?mB%{aKR)01J9XH|W>0Tj?^-mfyAP{!Z&Hh4h&CZ_DC9+ytsR84cO zri@W!gExoOu2FW`O?53FkwzOAEhF#G_t4P=`eR6GfMLb`{oMF!*kpyIw?yH4RhhPF zS*MMJmU|rzDOGCDc8+LyxBLH5J+Ol!MK4No7rPIKdbki;(?J((c4kp)2B8l9sWqvN zSvhM})9t~0cE2TEJGCQ8-u4pOW672j|^2#-u1ZIXMXR^2EV+Ew0~Ll{tJNv zNLUbnXO!6-b1;1geY1#@uvO}oMIpnC%NXp}!bl`mjNY->_Hl8PBkUHBp@~^xFDUmV z%>%CQOrHmp?gsxP*Nt4Paa9f`BFQ~t<>AlI+GXJOf2Z?U`S5IS0;`fy7i$*4kjeD= zMDURlF+6wU$GNh)sYaYN@tCK`MI$rqXfCCemqeGjvnDg+Ox2yYgt_gXQfcTzaa`^X z0Zv`KXihyg;^|?%NWg)+T&~Q8>NuIJvnEfN*I1B22Ul;# z4~tlPfW&KjtsD0zgk!$K)?}F!8!(R_#IMddk|igW>=lB4ITx5E5$uByaeFW6%Ct`E zZB(QnrC_SPJQQ{nySR>MI9cb!U-sSijPs)~|I!(t!0$=*m@YNTLWZ4kNkx@I=Dn&8 zI}t80)5XhJO+kWGo5L>S*%|{B{SY^#-xJm`F7?=Hr5q9YS(-#>Ic+X$p=eXEl!8|s zqmEAwKEKaDC7Rl(Yx>xRAG3A1`KFO=8d=@q_UA!E9y>~5X4ls|b;k9l_bI2aUEzYg zJn<`)1`dl0WtLUf&yPm)kPZ#lv@K45$kjO9^o7*6aPvRWl;xZ?uc}x1M^;ti2ux*v zNH5dlYu!xkwfAWQf|>V=^>tc4MV-=ZO#8Z%$%>s7b)VZ;eWh7{1+o8fGq01=mW>t5 z#9?FSfyvm~+V?Zq79WAUD}b%7-xol7x+gFk;{pkAx#S!1;*(maEhIxlIDY(|TYkZC z1svY1ge`cHQq%7Z;{G#4pLPBKA z-inj4>e|icwEN^bMZJfgX9+JcGOQ|WmkVzTiu~EyKg9CLb+cf??(bL0B{<0`FEPOA zi(BR_i3cavQt|gnSP2C{=9n zCYL0y;56QPDq}o5n%6dNzPGCulSI&)n>kc_F^YQ3L_L>FpHDXB+6)O{T?}HWBG0Tl zn?q!+8Y-h)oR!C}RLlWmVX~xEIsHoX_M1gsVj^oKteljfpo)N^Hc{O!psw z%F7KNv<>*%MJC&qVG9%|Z?l;5(D`{EBLd!U52K&5I=9u*8xKP}EL5B7BIxEwbeev= z&wN7kqulp#wQSn<7-YKx9>e&{>8AQjI*!$IBmlYYe{TBLLcfNGQ(NGB39Uo0>;(}K z#_jwwPQuRPCuTdQ6RKNQob_-i>C_^ez+LwPTJ2u3V9jyS{+o=;oDzC{(z{&m(}B_JQ%cg3FjLI!Y4~5V=gpWE zZxYG14;oB;`}TH>{+k$&Fc%4tb=zwLO7|a!+NEQ|yx-(>KKoA-K#^TpetpH-Yp(6i zyO0Fn>+tP|`lJuPe5d|tAh~5v#od0tUwJ$)l9h%~3v_#y6lUIJujZFn*qy9f@r`(U99)`wt_`AHJ zY#vm2t!K-_OLffbp3B-ULiyaO95TRHIQDx9jwwW85?VD@Z)3x~$$S>8ZS!_XDrs}P zxqkzc{JBieT0AmV5MBw2!|8;$>n0-Ne4dDg7uuKstPx$`F{eNmmXr2>2B!!JPaKs-cHcZyTsoY

z-1iOo7I=(fynNIYg*G-RtTX z+LGU_rW#aVMa$2~454dOVXXIycsWIQWf{jE9Pu$?M=%#>O3Afk`Y&3$*@yfV_ox)S z3d${?Ko!$U?t{+mM*hB~$L@Qx?da3{V4ok{y}d#9`B2q&f`QzgjOO4Ru8q8_nG8eW zE-sHVfX#k2n2ws};-EQ@dt;B%-#SJilBXMu;dXb)^XnrGlIUq-M5M~_;w2Pfvj+HT z6|+uS*SSH;?y@w;qFOz10D!nu+w##xnr$l6D)rh4TB8i_$g)lp5qQn99BU-Lqwv*v z6K40q@AR*!BuRvvF=JoR^RFqb8L;rVhrg|G=TY-)dVrS1N zhC_`qB@%Gi^Bp^0$eQ#SO?aomgR67}iY|{BNQxP&5bST4Ru>*E9}ez?v8l)jNd%G0#JLf6 zOni<_!@!`)RX9IF-+vIME7x_B4))`cGbrE@zk;JMdt~yQX(#^X`We!0ucb4-2D`R| zRJyf7>hA>fQSt)d@^d~EVfd3}7^%a?Uc&jkW}T~_%@erWV(kMm0Ju#fSk{{F`^tJt z>0Rr45u4UI(RW?+0z#R;ui>73xhsVZw3bG@_S=$<`Y+}$6n>ywREGFD z?S~%^eIR}$PxFT`4Ikw0>biNn^YBrMeL6K8Z&UJa@Q(kFM|u60gCoQ($+(Z%I6Q8R zAg;;esP@C-t(zZpsP0{!K4}@U1kEAVdO+!t6IG6+HJ>Y5h5>*Iu=i>oWNYYR*E*zoNrv58>7|$xv9DTm{O$AVb1yGDv@-c(v-+yE7A)zaPeNzfR>*Xa}BU zm=$kvB(z5v4P5l`Rr7vq9 zvW^-YX<7yOVCF26M0z9!o)z@n(-ywCa7N|t;4Y9Ih)1Y!m2^wDsz8-lzNpuBMFo)H zGR!g|tnzQnMCg-k^p|nr=lONT`uP$$-#UlO5;dtm%;(}=!dWMT^jg+0BZR~yX4@v16_LSx5G&p zpIGL1fd}U@lcf*-%EPx=%k%bjYwX1LnT7J}Thx}+vg#ub`toj!Wd2Or3|dbM>B-uI zVt>Yp*;CHN%3PPZe=UY4b(HJpdyZ_=93?`U4LUDjLR-a8{B07Ys$cdPKHw|POL?wp z`>feHpiYycHmO>bP~;ML+8Cv9h(_5A#yu?F3td`W7#x>tIyV>9fq(_WI8Aj7(n!_* z=s%V>CK9W|yfOu|ksb!S#lZJtPe(es<={e;8#jN0zE0ZEO?Ycf{D{Gcx=@*E=)qMgqvpgq)% z6@G-;gqEt=MFsw!O)pI)Wx6o9iXbtJ2lm9aaNIrXora>TXw#Ueymp zl#-a`LK9R8Z#Ufv{c^-b|4JBoU37zJ1I&w@q(Lz*qZeaV&KqicXGJ7nS1oSP4QGIM z^W%|+j|Yk$s}nKkqQcWWrr+Z|{6lktlHQcKHfB)-=)^u95gJs^hFO+;G)$5hIg%oX(zHP51$Yjm{JSX4*SIlvVBME{aAyDqRJ1Q(%|`CSXq zlP}CW{&Xip0tWtk11*T&Hmvp`!p&anRWQY^f7+2j0pKQO&UwU(2B73H6n*c>?*GaM zJ~9q{3=P)g@N#8x42XE>+~zG=r10S0@txQ-$WfRcdsXWaRoU^ju;e{}YpDKGsN*o? zMbx$xEbS}erA_SLPlvI+iXmH(Y2)F`^dDLKnxC+bQ=$A61)v0>i{F3R0;Le~E;N)Y1b4i~|_WEv-rZ#@l`6<_(DOH=VBm{Uy^t zZ&26Kt!>X}r~xA-iXZ0=5o|V7npg|4;*`wg)A{?F+4$d9Xg*uzDc~XhtQ^6Y&=!{3 zd73^(P{`?t<~0WOI4CeACssxd2j4Qu_l^w4Jdws-dE)>fW5eMmUnX1E6>JeP%o^;c z3RxA%hR8@$?O6Zs(ti!ek>32th}x27O94Hsiozzmi;ipuxej)&GLjk+yE6L5WZ9?} zBxXIIKYjR)-%&O0#o+vQcbek*%Eetkv=2Yel5ueP{HM`>%(}C@Kz8%9 zTL0R28+;>q`rDc>5zWVloiN|Sx6Tg!f8UK{Bgk@43)V@HI>yRQ;YsX7TVkGFc{_v0 zPkOdRGQC%o=kP$iYz9F}&o3}kN7y#NCILA`4-paXFjIt_TwIjcUA5Gy!o*O=U2RF6 z5B!E$ay~;N*Ic0wY|>4)IObtxi(7u%fRu6H%8@$*IM;UK(16){TIzJ%?0;V*1Tap# zpNSSlnQ8Nh=MFKqQw4HrBa4_=>>t;_mHO~GG70#kOJC%t(qNH*nOp<6*x({B;2bUL?phMJLU6+ z=1cU~=TKbTo-(&{y#HPHPdizlSC?-l?|!c&3k(R6m5Fq=xdm`JvjNSdy8VaPB58C` zJ7mP%GVw8KnKI=cuC4_8#+-f^jP$a~d?zu%*VB;ZQ76tqURkL{ZwP#hZAgz@Sd0qg8k#CtpNBY{P2BK;l<&$=8h9_fc{XSKXhWZ8}n5d4LL zwKFCmR=#RTI1K|YC~EpKd7B|CssC!dCyy9=C4y9k4nV>J*N1%{8SN2dVTloszvW*~ zg)j}F?=x@xz&)thR^v_)-6%16wnRtB`XYn!84_R~r#HZ#uS|1JNh=QutMbsesLVP9 zj((G?1Kg;$zw}P1lO-|W;8*5yYojIMihRI8zcnqzb}xqo7CW9u z@ezt6I#f<;R?z0=h}npys%-v;-dBUg-Ej?rmNGg3fg{2%52b%~4{S24C$S;xgD0FH zgnxaQxFresdAldQ@B2f^ki7{ZtA)36YBo_^;WWW>Ij`p|CKZ&F*mY`Q{#^pJj}|)j z1FE|A@{m7*w*R~bK;PAmx3T095eZg?O!4gu0ah)J71P`hujCxC#)Nr1XQVh}KpdqH#lm-sg$u`7A(3( zVcy5j+3a+m+J8Oz0<05~g`I=K{R4`Ej%}i3tyztNWuE6hmVt@GJfz5yv*2&d$+u`4 z#r;16cI{}0CS%MxN4*u&`Imiyv{5e+YlFZl!(YUpaMWfy0}=%ge0HGy$>@ON(8UOM z#hrZ;+&_#ELlK#~Ey!$ZY?i0peZ#oZM-&c2SjCszW6lx)<_0o`1;{ucl|Yb}pX=`V z$R#Pa^83kiOKh+b>AhMs3XqTDwn!PLLtlNjmbJ;PzKxo)`iNm7EvcSb*4RE!piDl? zID^1^6=!M}WQ1tJQrpm%2lI6Qfzx4HtdSDt6H|Y`+%Cd=FE=`Rk{&U#Ai_CO>X5L)N?Aj?JVM$%d1u}v1DSRc($681%qILj+uCtGT38SXF3Mys} zN^lueUq-SW?+!8FjeOQup(48nP4^2yhkHcTZv?q+2F0f;3vW|+T!Y~<>-|s+3`*69 zp*((-==M4yY)omyG#EbIWvtH1$&Bs`rn%1qPkqWOOv~bwh8}p-x%qH#G6VqpB#cvl z{QB-EBCOVA28|FO%IEX@WtRE#Q=vf4gRv=18G#He4p%;JFH zack7%+-I?}@y)zlC!+-C2k$uNLa>)-kkSErpc!5fX0C5-db?0hHFyTpM=Q1laaLYL ze$`-OD`GIKin71%y?X7tiJogl+nYVYi%kzlbaeW=`}YklX}g!OlOYvGGZ&+}k(SUc zH9uxs!`@8S72elo>PmhUVK-kFNQ8wIWXx8iGvkuQso`%)0Wvb8T>U(qiXpCDzL^@# z^(iNcxJ>z)A6~&Tzbx!4yW)IgGKZ6^zwKT^->56!Xih@Z34>l$aD z8#QhJOcJLJ_31g(KN==w5UrT=0tE$Q+7Ss^x= zG@Qw2*Y&k{-?igXTBz(hU-7JN=1~Twt75_ncvC))*|Stt!eXC9QY+Tyj=|WcVO~!g z(a;^eAnY1T*gK1~cT&1y==`wUX(i${R~c5PE*aNOQ{UtA=PZY25S8roWU6~189Ujw zDOa9BCxL3KAM=b^4zxDVHNX>dRJFqyHEy|c-~cJ)Mpc(YEI+luFl40hNP?Z-r`Y>b zO`Td!%(t}LEFvMaPHIJR^b{-L&!_`>1aI27j6+|^J&+g2;V z^|gADNB2p^y6UCL0JcsobQ|MKE@_a87E-`-7p z%*iY4_AY|$Y3gk|yfVY>UtZp>JkvXJ+tKAlsKD75*&|EA%L-S-zW?axjt}S47ak`B z>l=c7zce{-OlU=oj1(l60#+K zqHlxTQiZ9Dx6_iT6u*kD__jI6vD$*UTXrXPzKpjy?n-PZ`tRI4`bt_P|GvsJ7y{NB zvn#`1id&1}mhyh5gE7b-KjqD-TYXA5D~7JA_oH7pC(L zar(O@>PsLj`UWCk%6v>IcuZ+0cAKwg>Wx@pJvO5?a-raNBg26Y^_Pa{p4(PIDh|IU zk@?Vrr?{AkrlWWN=s|aES;sZeAhSl5e^iRVZ`#)uZHD)D7`k*TY2dBA0^y3aw{lnM z4b2jaQE#E_WlG-VuUZU5TU=;Zb~m$1w8hXEmzJMawW0Y6)WSOqq0;1B8xQ7mnpZDy z2~RNm1QCQV;XlF+A}mT$`Bx=u>eX*eRg~@=k`I5>2<)R|IaE$~*z@|1haP8JX?};l z7WTf74UuqSV7R11HzR5a3c1PMgw@`UN$VCQQw3FWlJjoy6`cDhD8H5B-j_EHXh~@o zs5ur+&@(lNI3v`#Q;KS3lb-h@h!(&A=w14N*Y4TO=dn+q z<4`wt$pai0f)tE9`lgrRw-Jw>)%u)X>hN<$LR)&zhJ)4jh_}XS%=6UgonjlLC(wudsulX@~QfU^$q$XHM}M{*B6UuPdUvfY@H0}g#S{# zo2Zd>bTw-JqXDN3dsr^-_GjP|a0D-DEZ$G6yBm1EGB{3B8YEqBdk>61UD02Zp#EGn z$dy%aT=n!SDav~q5{$PCGr}|S$fE_mL=y^e$4Iss+vcvBW8D4&tIR`K(YC*LMZ#}@ z7Nb+Qo4TC4iUeQ5jcC8N4QR*4_i*SFUW6z(!YfE_Kd#01yL26si*x%0jb7|_|1Y&j z3~)5OdP7Pm({4UNmB{0eHPPIamuWk3p-xAnNJthCGLq1|XdYqXHfi%SnMDBscOeo@>nBlIpCILY+!-!1%&si`@Fj&gMvbf^t z5||4FTE-k$AC2;F_$I?kyKhU8o0z@*M*GtF;1GBI=LGC`js?5-Jh^H8SJO7*?aq!2 zWJDFeXp<+c^o@$`a=5>g0-teTuF+{FJAn|_0=_It^5LfI3Wu2(llIpv18>>9bk%iHpzfQq$H~o~J!zBmpxglq9jw0w})w`c11~uhv zx964zjO&uTWh!%vUxBSR>8C8P8wr@6Tp!&yD|8N;9AfAO?AZJM(Y9m?w}Y8i(d>yKyR zHpj|FJJ;LCcQ1hjm=N6D3AhG+gZnz#GHSE*@5`Qh4CSz#>vh~XeW8ju$b$0h083#oQ^I7Cst5zaEoH zvb7KopCX%eRM?6o{(?t0r$QaTDg3lS4dpKBC=p-6Nx{S5E2-nzsOSKGAHzz5FTvQA z=+UMXejtimuHo)=9%9V7%~@e;B@?Kovc@Rn_6IJSNUr$C@)Oh|oFe-s{>BvG>OimsBW_EnPlwX%l#VkvsNoib-czd=8L zf~MkWM~dFOEUjg~%ZrbOnG9Z!G@dc+VBQ92=BUo8YrkF_tC{UZT> zFAVvd&(&$Jr3mSoXC4z^;MH7xr00q66k@GqR6 zBTUuIC;5pwn!tr8LgRGceNl%I9I7l3)*iG3{Vr~_Wvx%)+$U}7D#|Ugwjz6-G#G-j zYf<4YNks1Gp6oo%$cp`GUN4mLeNJrAN^{Mvh!U!wq&gYtJ$2#dS{0N~tB`d(q zErUBT6M%vT09jsr!V=Ln$g@YX)4mrsyMO+vs(D*fKlyW5gt(k~_fmuM_eru!_J}upl91ObN$Y8OzGHKH;@}1mq#hsU z3l(vu_p6#XTCjM0u3EK<^;b#(1Zx$2DA5xt|4>wRVw!42oa z9CfR|f{U!S1O~J#lm1fGf;_)5>Oo{ zUkIXkGlITJKK^Fi)|UhLEzkSdeKPAe2;1yrswG@@IE5xn6$IxKJ(G@Adup*tEiYV5& zu^le>3o-pFPh4+TyA^oe>+{UYrE9 z5K#H7v%fwmmOHW#JoyDuQp<{q?8si%JsS)VnD*}{@BLFNXMSa>5`ljOs6QKx(}QA+ z@e{Sbp>CCoLxspFTkH05xSOt)|KtUV0mx6fN0?_C+EK-%YMa(;<3LMJG?8KbBOH;1 zx8OB@Q=ygo4OTzy`KMlT0aF`Ohw`R|DWzOT-(sfKbd52r`z)?6M&2p#U$yiz>so94 zXV4N~l1d5Fn}gc1`NyPI-J5q(WnAYswrPVNcxZB2Zx>Cz|HFp6Q|Zfgm6)-J_&ogmiZ|8w?ir{5}8w zb3gB%w}X8?+qLg?#&I6UaU$RnRYlRYnwfB&G|aHoWQ4XOZnlG)KPzX*tJErEyGfab zqXab{Zh)LBF%XYZFj0{BCD{dqE{PtQ{9sUFD8UGEiuk|zhWJ~n5lh|V$HJl1u3gU= zvVY@0(As=1`7$OBg?I9m6z@`%CXD6YRWVMuSiI=I2z~y0w5-&PvPx?umh?>I_?;#) zgsNYKHLh{!YvI@|;j2J=Ws4u`3e>DxvEuQh52Tq^l^f-KGmbW#?KE=UU-Vl;u;7Q8ANl3 zEN8(PDl< zekYB(Xc{;muvZxioWqYgz#c#XYyaHc7}_w*!O`kBW~Cm|hGWEx3WCMtJb83m_d_%0 zc~)wPBh$7;77SSj)fBe?LpFij(5(!3C6b<=$9U8b0>1YQ^LsmzFhta2n=)WM$la+U z#B4r{z?FFrF(qgf+}LlR(Ok4CsfN7;%e@Ue#BtvcE;^L9qu{&=wDDrB*HQ z|0AE%Q0{ z@>=PZn!Li#hiJ%X4QljS69C(PUyJ9$0Q<1>pt8k&IJU=R%Mee3jWbc5@+CQbA-jdy zLyH-DIz7xT32M(b=$eC>YwJ!p@nN7km5L`W3%24C529)rf&^?Tt;)W4Ba zyZyP}$M3Z9`!84u@)U7(nYF)38FmGPHAj;BwqVicUSO%Gk(Os-(`ZZ|qA(D2F@jT$+JIsSF-J3i@)V=5w{p(}!ihK~QX5(;1^z)zrUY(?kcG;@-Mg_a!7QDvRWv zCV0FS-L`ZP8S7+MJ4CGMKRdb}t|1?abcESJIV%&T{RPFDmPS%qo!UkK9&scJGHNlI zzu{+BNSiiAFWh5=A`5yb2%LS-*A&M*r_U$8j$*cWe(NXw?3ktfK@rM5Se-X20Z7{s z=%SKBE5jEb=s1Aw+rn2_;trF&j!7%pOH@=qo(pZ@N3#?j3rnnIYp_^gFvkZoj)bP18A%kZX^O6YYc z4l*_1E8nMhJVx5hu0t2XVJ+P7uz*vojyZ*uoretsKa0rL`XGhP;aPXp#n?M7xfgw zX2fLbcDi&e5QP$&L$NJQC)mccCyQVHYtsH~=N4 zkQU>|L#DnHOVEFRN98q6GI_D`VATFQxw>*NJ2-tGdu(-0uR;^2&rGfmG;#S^%VpV< zwa)CqEMw^>3G$vLq7TQ6W~Z-JJJ3sY%!*-3e}oQICNAWEglP{mcap|Jsm%kb|2X#a(juC z;&u+{S}cZ5HT2~pHrJPul=kT!_KKYSqEnIbT-6Vnr<*z4t-t9Jvi`V+7t z!`_V9f2F2~_w;|kF97k+~4euV;+OGldTJjz@P=k5F!FV8dT z^UH2g*rA)bwjLN$E3)Y$_YTMQSH}Kk`kZzHy*L%yBAdQHt!hrRNb3`-%w_T|gG>(|p&Nfdg?Vl`ozv?k$6$~XVwtgvEKof}-* zHvmeG3@ao;ARDq>l=z>~!H~xJ7?@eBY2ezC&5#}xt}P#sw}tsMhi&q7ox}ejUyN(c zuFfjkAshjMv_HGunTqeR{2Tz8^u8tAOHDJEh^g9pPeos(EFGs{?c~||sq&AzmOti6DlCCUUKHk+g#fT6Z)!S7An_T&e;%B(9zBREmLDVwu#k4xj9i z#Wpa86mk4T=SGW?jWO|}G(ekp1ixJge{>UrMJ+x{ojx;`)-qAE5TypVzH28+Jlu?4 zg88>{Scx1NYPzg);s8$acM(C@Y`^Iba%4Mh5@5oORd|K&* z7$2t#+^es{Ufc>x57M$XP?^PBg!FkNv^bS@YH$PScjA0`8QpMXr=6QA@Wt%!|DDX= z!!B;9PS;HG-e{2$z`|WnYUOpJDmLxBg~$CXyQ1ln{T{>ouCj)(5;nbaK>f_99MMwW z$xfS4EetwCL@{`0G+JX{Al0zc|AP_FUeU2yjqhh?*UOMM@)j=(H-+625(&2-K1`cL{jc?3E@V&CU=XQ)d)GY6p zIgey7sUqtyJ9t`#v|Jsp`!dD9COtoQRv0;1CGL3mp#Hav1jR zBGzR8##8gZBD()+j|2#8WJQRe8ce+);c|MQ+a1QE0a|=p*F^D_ybt*5KBPC9oe|`k zE^~+ePLk;93*4F%Dp|8FicqObxFR7Hr`_1=OoZS=DQ&W($b=Rg=pI!P#v#JP2CXVf zSdr{`N@pK0_12H~TcZ2XURhm7W|Pr2mS>mf;D5aUM)`dx{5k73%JwOf9!vy@OV3Ve zH}SYObZ*kCJy@@p>Ii9LGl_k)K9X#Cfk14PLyKTTPHCm?;{6xk8G~(Vro9;4{e#^S zdVkYv2^))&yZJ4KjH!No{p4mG;Howod!5`SpzK8V>DHH03sDU^uFMDA9b%-a$BCxS z?9ypv_;JZk3-RNSu{}QUQZF0#LigAblFZ zKadli_LCG}sw-BAX||MYgcfnJatz0HJ#Rlku~|)6U|sPOoSWo7xUFK(9A>06w)qi% zl-=Bx#zoeUhUavP8?Mttry)D$R_*rn)$123EmeEeEJLjUqCPm;{y;B&0qdEMtOwxT11=)W48RWk){@y7dGlKdd1 z@ts?vXn7`2$$cjr{&jYbhRH1GS$0g>z6LuT`0{YA63eCwUz+AqCj_Aez4Z%x+)(=} z4>uAwEy$hSYIu4S5bS{hl@KxAM3+5yRQdKE7)2N&4 za?tAPO0OolJ~ffbd#O$)LI-=7zW(#!8VN#KVC9CY?n(3EkZjn3%pi~rZ-a06EJ-&5 zDc#Iw9V;jA&)6IrD$)1Vok)J7aB-5_vi={Md3EOh?iST>9^GX7SNM?OTmS-qr`2rz zIhhk-{KI@Vba<7wrk8d)wNsBmT>}VzbaL74 zUQn~siiy9<9|hVujJD5b8v-}O&cNr{^1ODR(U9~L($cuDFs(ClG3XPoj|1juzu}RR%NXv8}I3vsYuNW)arMEekDflf4R_|-C3Cq zo0dBF4PD08u^!LX|7XACPm&B?%D4GEnksbesct&kQJO2V8!po5v?8&YCGKt8L+|n< z%;%NEBZ-haR6lnw@1UG*ZD-=-n5+rOIE*y8w>lW4oTzcuA*2>0Db=&Be@hp%o_3|r88iZu#YgBl_>h0yEoy5X(0z{h_@S72ZThiihCW2+pAk-AmShLnJ} zt~ta{u7*D^c{QP<9Rb)R&dt1f)#L1a$8xJp76*E&YE#JWAmBU0X~CA?@*7NeF__&R zdi?yK?1J+fS$L-oUch<{d!)1S;%N=aR(*AQgZez&@D^Sv%n^l&>8LMDOt@m>yHWNk?TZgF(g8c=-$!$w2i)j|A!Xk=29 z1&itME`!i8MfQ_0-%hKVSD6&FkeYP3XLs=NV(_Myh>QH7$akr|&1+4})9~M_>2j9S zILv0w97kRTmy?Urue-xj4vMDFq$|K^GCb$Ut1+_lzZ;{zHdH8H9tKW`RjFqXJr^t+ zObzLK~E9mujw)iW5HpD@iCoNDe}n zd~DIm5z&43tR;`gHl6k`Qs_*PCq71^55RUDDdCX_cM#<*RK}!9ZiiICpyf21_+nF& z=yO&q1bjPpUV5_p6?mC=a!IQ^>dGLUeUK$(J^00=D-eS-K#|Z38+IB=-x;&%b>lwI zy!Zz9d}z7opfnUJZEMO9Wz#H!h@5H`^^^1zhx z=5`D5mR7X{D+}>IY0RKyp2yt#g#Pl%@!fgxI8Z&qiqZ%?pzN_nDCa)Ym*CjH!&{zn zgKEf%Rc*6Rn-`e5sw=@-ol7F>KEED$@Lejb_eSs@%mH){+8?2VG-Sa4sFE0HYpyOG zFK}BjfBt=Dv&2X55Ln$7Y<1T}T6-4$Uisb%k2$*;oo-uhc~{7;%d+62F)X%=As*ZsukSS-}%#9ST-s6BLFty_eF z2kJI|tYCAu1=;q)ZhzH~CNqGRfOgT%K@+$VX3PEH3Y-n0ZpitUa1LHeQ0FQK6Asa6 zrSct1KvmYZpJxE8C>@`Zy2&LA!$Kn^4s5{i({mqN3Ri}nCWKd}5P0#rg} z`pGyFtnnkxfH*@df{hvFO7Jz6LwxgjDWWULQpTgw5ksdJ$-f@#cpShntiGKOSZz`~ zC{ExeZ?@dQ9?_UR09@2h;_OOlfe+1%(aaiY0zpiISMvm}O`*?>6nrfp0iq9zcL zeq@d7V?NTBl1Wo0T8tWw+DyTpxet?Ep|yD1m5Ul=o(Z{7 za$Lme_$4zBwG-(#1b*OIVBd?;=Ada+gDj_SKJ&1)68-P1kIp^aJ_lJRjf2MCL9m%6 z#tv!&NRBPNj}_MFncOFCOaFQo(}p*dC&gU%e{`v9)J>8U#u(VT8fb`XQTRpCn2dX+ zk#g5IrEOWpNbOS|QqZx}i_uqRz2OE361|wYv^%~*(b%VW689Th#cki1tatb3Dl5&@ zYH|m?b~3x|qaI+ugS@Wkz8l2l8j%fFk$On2Hk%jwqCZu}l*??;P(io zQ5)Xb!p`8`9~FJAvTm?H5>b6h((9%N?I9Qa<)cQh&op)1PCXtEkDywhUhOO+u1h)d zLAp;k7dV^+Vx~o0&wPOBUqIi|+W+z=iVb?GLU5)C$|`Bsi>73D>T%`nFBFpJ>5OaB zN1k%IsT?gm#yTPEMFG#puy zF2?3xQwKG%V-$sv#75f}B&_--nNP?13386&aL+7bb(4Va_4_H?%ql^YThh#>CoD9| zbfwG_RCxBEAZl$dqDbk_b&ZcT|bf2uaXK^HByuPP4CE;MTNtOP?3@{+{;#$ zD4HX)zvA%QpWer*q?3Cwu4=>i>p7)3zU3_>5PLP-jE(bu_J2)~B0E+tOQg86 z|Fy)qa^q`Mk8`CLpW!#O2+vmgOD~&I9qkYrzqRZi`4;F;071uH|B#zK{i(?<5i{Kf z$cd5Lp=*d80bYP+Td$BSbvH97cp2tD9yeuIHt)tV0mFLeS7XF0eEX9~idOhzwU?9WQy$_}^?vvP6z=UnSV(FF777BRR?Y?7{MrEiKG z_E)`op$@T8;$b@>Cb4P%Ofpz;pj?5B%=(K;ZD*xXtfvY&V=YLv1@s+QW|14P#Od|w zupQDBjX2^IgDcqIg7)a8O*qWetC9Ub5}p;8F=HTxq21uAoizy@qgjW4A`Xm|Z4EZ~(0X|` zBX84w2oK%zDM#KRUAFSXO-J!4T!qQ~n4?iQKeS})>T>O1f`adkKH(JZC2fv;Wx|7W zC7@ODIPuf1qV9u>I~hiA^`KX4?puA(Lm8tnF7`Zc5i*2`7++-8$|!M-nXlo7wQe%-0T4!Yd?n+zAVs$T)r83>%B}sh3hduw31nYxiPLfX6uS$4K}0#K#X5 zeOY^FJ{)b#EF$ejkWsb2Jlrbez%nNR81Hfn&F#&E{3a)Bo{n4A@|4!oa6y8xtpbLk zHp-d+v!QYgw}$z`?@V)2^^AH5y%qx4`jjRWUN_q@VWE93jiTFc)GrV<66f-?BfRVW z;D-e`x*YQ+{A-tK280R50nE2AWF1-r70Wqk*>ECn-dLC+nc_!*2-evERC^#wd&p+Y z${NEh=ik(Ntz-i)$DBD~MDws{HId8Wx^bad&TZH*-YlQjJ{;@fh zZt^Ak5hv^6jvK)!n7)}{spzI=zFG`19C4`}lq1&x2X?;!s6qnC?INlYaD>6>E**0$ zsl4*TFnl70nBFhf>$PI`j4vj6bjfskvI;D79p z2^b2;DEZ>KL?n1?2+u+^nUgI0Tjn!WbR>A!SQjsQ=V- zLfaewAsC%1K|0iDKHM3)WZsABc=vhn{KKB9wbdw9nd!*i7tLxf_;Ie9`h;mwj9u3Y zcRyRsZE7SJCX|~>J5&{#A3UGiwr%7mpp+4d&#Fq`M&_&pxnmy57Vws4i*!AAFmh(W zagwT)CQ^jcMfIMn**0quCAE_8tZ?BCFIBmBg!RAc`PL>9mhGq}4INaLEzlHM_Vs7p z^zM2_4lK5Xb3I1oj5Kfk*iTB|aa>_9d`9@jD2KetObxD}=ld8oF z?c+z74JI-N&gYzih;sEoAed_^1#l%X|Xo*Iu4sO>4`mhc;a$ZK)OIbTydaPJW zuhJ*{=8$!3fH)WSZ}#WS?6Y}uR9n~C z%ANoy@tyJ z^s3H!u)1gr#IJ{Ce^E&v9b34_S%UlQE2<=n0LU98Zzz*B7%k8j;4~rR+TWT4W6GM2eNM~ zB)eMg;uN=1IMZb7K#GrkZm)`^9be)cLWKR%D2lI6llqmufj3C;yF2ho_=9wF?92@Y znr5Z>|K2z1U{dqtsy@;ZNNuQs%0%^Bg+x_Oj@&&?Jj562Ng1EUb1}o&hc9Bhve3H} zvSf*}&wVFq6JlzW1BcWh;8N~Q{G(6h;IiQPNQMx)vj=jaoA*@HrHW}upALa=h+ST| z3uz2vZPcS-lW#j?@hXSU_D6Mg#>8kd?Auqs>mPgv{9&mlfyV|HkD?9x3si9hAX}ft zS1l0EE}dhu5b2agx$u_@2j)d%?U9D;&eJfoa-fMxKjHiK(g^-|l_UMu(|f2MOw7W(inuvtP|t%^UV z#{r*8wnFfW+=nD--KOG417c0f1*}%K;lb3Q;PEWobZ6*}OA{fWQoV7FS^66>or0C0PijG-E6^nI^V_(qlXL*;I7-O=ItRfrfZ0Rjhb%ydO0>2+NfLm96ZoxJ|e*URcY=&HVC1KoLSI0 zof0rM8$|5OD%(d%zRpB?KSdfGfa*{GXkBQIBgeG8=c@g2DVTOw*$lF2jLzFOMhBB9 zIClF<=}hEC8N&`Jq-095otk+4e6_C{CJg5zg9vhQ6clAb0bN#+jJu#m#O~71V}8@i z)M-M)#3sT=PA?VkzRaA%IviWw53rpz%tLx@a7Sbm0NjGi_+2?HrN}OXjwJH`=epTt{E)R5n44pY+u zKLP_a7$%3wWwLDu{3dT-i%6SzyP}PnRA<;O-ozO67mUntpMWoI51Ll6!{2wr8q!Xh z&=@t@|u;f&Xh)x6`>)S4{+={?V~;;fH0WJ6HO_w>HA zj7yzH^X(+rp9r<*wA_Qz{^+!w@qImUfLHFw7smGtsL!LDv2hVit11m?DK+olN~SqCG2YE89eEHf z%pXQ`48DFC=5qU@@+|2Q&9Nt*3ptyZxH zAt1l|(Tt^w+#Do2#_9ZSaOF#uC;d0sMR%TMTYV99#W4qm2GX{%*`QeL7Pk`4Rx0#% zi{^Mqyhwa?0kv%(;}NQOaEt7#8;60XiPHQ^`KRMPKU8Y~1;4yg@E?KbcjhW4q=aP| z8=TC>4CK2yhWG!&Uw z5{o^fOAP0p0pOowkr1mmuMqXeKk2 zKd$M>VU&cA4MC1jR7LA9c&}||@Yls7Vb4{y&H1xBBWAypsAg7RId09-5Dcf1kkOOB z+O^7&BRx3ZSbW7y>okz_qVB*RjZ@@7l(A)%mI8W9*oOK2*@yoD!x+)psn=G&br$O# z{KqNZ1$NC-N02kvJ2b8(9M$o6nod%$>4Oi&FGlWEZV>VTEQ{`~jV;S9m-+`u)X((6 zVizb~mdOW*pAvqTx{UftW9)zvSpknoESoXnwp>ciphxN%K$p5p+@`a^ATWtq7ohH3|g=EuQ0XAM|v;Cu3!}E#E&YIB9Kd<33P0>67k`?+~`gBIGT<)Ui1kInKUPu8}#&bSYjrSlORD4-(x$)eaM88DrI>n#b?CotRht z$NLwVG+ec54Vs^jbnhmXJDelvN$MnwbOy-SGBr-=%t<=e4&qZvKX;mzN#eHOG3SOl zHD&`gZ88G3*<4yV)hl7x;~6Y*DRf5n0`;j^aaPk>_zV-kIYp<$DS1(GN3^0|w*C|1P+qDnRAwd_4XP=297mic z*Uk%!C|2#);Yhj$L7kQk7Z*@=nwX1 zS$utx9F#irYFr06mQwee8|+MdOCiU%hqCYBzVf&rl^YJ?(7^c!Z;vAJC>>sL^-vij z(l>&IdzoA(ssp{gA6X*>lt1Ah;I9_n+MJz@V zIcTkKdOY)Bxk9kh;sz-T5znf53;LL3^rd#>%hAhh^095>2jS!MA->}|BAk|!A-)!{ zyY^!5iDE-(=L0ZFaeJV;!_l)Oc*Y2hr&Cl~0v6f)zvcvwC>Lr~U$9^A;w8n!NJ)2$ z#Am$=r{&YR8=hVkACM&VOsAn&$PC^6GbsR&N_{oCKSZ3ug;XO01ow8A%%5zeQ}TJ| z=#NVC7`Mw;R*%Avh63lclC5KRpRJ}zJ&QBg(QL+vL^b;hwzZPiC#?qvyU#ye_A{&g z!ct`DjSBY|){SQit(o#il`rjzEM!eU*^t86B#nizVDG2O1gExt0qrcO(A#c?t=Z0v ztt^PEl+SxY+}Ke+%Ey@B6HxJ(FSn7I_-s%Qk*4;8Yb{yvI;;Pa|6hS&#Lcw;G?@{y zth0k%ex1Ilg+joI0f7}K+UV`TdpG01z!bEIF_3um=;@%V{=E^gs{pkO+(Y^N7w(gG z85cJp!X7A{e;GRg-wRHYIEL4?`=Pan^SoMft4J?edj|!mi{o&%s;nBBcPPb;I6u93 z{r7MRnRNW=VC6DG*WWqoKd$pP^oteCKU>6Q2d3AiCu0fM{pQA@8Q|U^gs>77YP=X_Uu*{p{V0dLCGEq;!5IFpY`J;j03gbtAEzoRqeZzflS&nYIx#q#^ z4{wdNReCyW*?NG6(4D(NL*gJO;gz#8lLZ#}cWT9&AUjlwfNAo~DM+y+eKY;qUfi@A zWFZ_aCrn7NAs8#QnnL{4WCEBh%kMWel#HT>8a);r!tHQDB_=fWBhoIJH@TI)n_9Oq z=<^6}1t6vkl(cfY<%EHUKj|BEk2-y@zoNZhoQU6CM5KxM(joboF^jhUU>9wA}AY^djyycS_OQ-%jcDdcpd$ac` zQcv6|7&T~D~VCJXO=P`SQ^Jlttmv!+0Gr`f(G5Ea;eJgH1kV!qK z;YO36297Th6m8XNF|hm?#k`-=uP-#)UyJ~SHOU4dVGg?P#afLY6Wqd#0_{M-;04}? zqTz)Ux=#vBl;-%Ds&T89ehuGp2l5^{x9@-SwLN)-Y~NYZ$XAbSWF?g2Ahj%@k}lVj z?-IrNCT%8tO4-r5=XjcZP9=9- z=lf~Z3dmoYgz@ZduM@2=y8Jb)raKCjwyh!rf38dbzT#~vmVJ}VuA`UnHA7F2eX{76 z02OB&9fKK^_qA2QFNuF{a4z$FhZo{aPXiTsdt}S7k~^*v;Sl`4wGplE7*!i37ivw1 zryqFZyo$R6!&o%|GW(?xcWa=3(35Q5lc!L{+4q85#JoquYua6f$F;y@%bH*bSgm%ywP{be>>j8 zXC%Bs%My>~kEEL=`JnJhn?3$JZM7&d-HY+{Vtsl6nWD7*Pc=4PSN`b2zgKSq9A?b@ zPikPk-cPRD>y{N-?59df-o7ohFAqQ{T`fY-$9G!v0xyiOVosi{e1}7&GcqVssnqx5 z;4*M9C&qE05OuC2aX{a0udR5{<#{-CQQi&NSxy1ElYcVnGK`C|A;EchUIbo@iy#WI zZdY{v0qehqmSP-MuK0|;tyXY)^#k-@Ve+MxU9a|zm9D-YFZ;~w9mB=t$J=h|zb=uZ zwkQWwHGHm#PV06mJ!t^$b+?Uk2!k59)u){Xl<>WKW#uM7nRJ9=B)Jf!l8c=#cQ^^v z@BwlqM!-ZqJ&r8IE+`_fCuQVgl$It{=e^84dIlm@<Y-C+aBoV)_p#Jxm@4e-{ujn2>JHOM^r0VCFR26uJTu`kMrcb*RxzD zyx9tPvfd<06Nh`phCBwxExvkC5#YDJ?|ywp%fyWtiFLx6sES(7$(; z;NBJUdCviJb3H18!yQCd~KU0M2)(j@|W4k1Iw54)23?f^GjYA%79=F#hmd`o_Qs?OxXe zy4U+(p^I8fj)c(pg^Tn=>RpxyG_&q09E~`|^Phfu>(SB2)iQye=uU42-<a) z+&ugu{KLdWhoX&xCj5!4J+anJhu+czvqlQi-rq4uhA7kpNm0FBP%;K!naQ>&&isf! z&IEspNEuM?Om5!>n}7@5He+1WhM)$RH^}-eBru1fDr4T7oo|c_&$0UfrK3vS$#^h+ z+q`0A#BrnVnqrTY@~PuQfdWsvi?-Wd@I5|7CNCiFYb=P+@ zCO#y(XnPRqG!x2T7j8s$l61PXn%^RSpuqSm_%82tWV=JE_&4gz4Ag#B?6Z|4y~hES zlP|W6U~NMXoH*%h$bH3n=7vC^n z3zQZtzJY-5hKMo zTq#NMrMN2NINQ!jsk71?x(JL)J6^#eYL>KMjFvhr3bq=MT2hLW<{d^LZa4775*9th z+yX_;#VkR8L!Vbw52OSgp&WJ~Zd|_n9Es)O4%@hW-@eJf&959WdYv{gjenn)U(fP?Yk}PbPydCYc+cJPvBC#~(Nsi0!F=a5;@kM64h-XCiWdeQQU%>cRgUIW_*AWR zN*uHKG23+6nc{OMj^r5Jt-x+1Vy}Nxwc{J|20c(@R)YoXW*oEFobbukRb62PeDD&N zjw6aa#1FWg5rL>~n1^!!^4GD=vR|G*mJo5aTkd!#k|%cf`W;(agvNkj=h7`Uduj!1 z1020Fy4}OrkROd;z8a#uuTtIpcU29Dmuy2^22Lpu;~0qV0`eGu9Cb%bB6+yF*q{f? zlS?hfZTYw9z^1?G#lD;daoWX=ga-r8ox+9}k3;noDc>q!!f_QM9yd~%?-2Vk| zR%v!MU>oxwCuhQnjp9C(I1B59PWXV?pnASia&ITdR%u4G{xTe72YUCLq=av0QawMl zD6%)*HL9Fys}%edsZz$xXXGi~$PC9VEPrtVL7z@8g*HGAF{p!4PXJ=|+p%@Ssscf+ zhJ@N9miIf~&H5T9nr3buz=zC`ewXOJ^s|7qJ&~o8`ED-7Q>58#6lRAqaG;$Qrc73S zcV`9S1OIl++4cN|bDH;G;^$~nRG!P>{nEUQpu ztf&s&K3c#1S4r$44DRKg&a~$x{+(^bSXDbiOzkb?3+cnRt@Ae~AtHh$5tPo&C`}3D zg~ETwwouDvNkRQqio@}VM~K9%zQ;9FV9Oa(9O`xY+X&|o@#QAMnQLUYmfKyZqcFm0 zA6AGO!PaBmDTHRf`F!Vr<`j_lPYVnQz0tz+B&Cb6ncB*7TKe0~+=gTtu8mE1ef?bV z(qx26hP9v4_%367l#In?ykm@#Q%`WypVw5&mJqyEF10Mjs>e>X(2R%U58eiNfBB{K zwauVb_6zuE1Llcca#mVd8=6?Yik@7$J<=IFxc(9>l4ARu_N8Qw5dOoJyO1YbrtG`V zUp{XC)t(9dUFl2An|SszIGl)i_Np6v`?>FUt*GKO>lk`^Sa*DNScLSM#6B~b?5wG8 zPJ3V&owXLTYk7p!C!Ef=v$U2FddwvBN*_<5jo@7_c&Qq{B`+5eAtDrrFG2*^?(fvwS`#6hYiQB8RAj5V8j=0v%&Cwfp$!ZlZW?8wH4Uw z=*`tldJb+ak3dl~>51G_+j*C+Hw^ie@+Ywr2g6g@wU{#^|8*Lti`-~{wN;!zZB)C! zZCY-(RA=sX^xAH8G^v0 zmG-vV80<~c@eCC4p|0-QH%=};&+D!`y$buTV5cV$puS}8qU`(`DCE7v`XlgBmgBcN zJxjNX5ynXyEW#hXCYiagjDncK>M>~oFDI1sK5e|6Lgej$H|4*!nbq?VycQaL$Ko}~ zmPg2iKi2uMIE$6jWh#7^)I52^-15$hl0^xXdPE)1yp7&d~pI=@xI9)*i<8AvgV4l;U zPSVdyd&f1TgjdUMKU(@Qmu%3^wmZ!EyvBFgyfl>!wIkzv2K8SRKkE4PZb)Q0B=K9< zvr>5I!}(2+sdwh|BNKY=lqQ|0sYAwX>rP zHLXk!NI&%Y>-I?r+Wu95eLuvk(JW7+H?bGx$LZ}Zzn{3Fm zsmGYdiC{ESzm3VWJS}J)7#)~oZ(($fH4vmO6y<#RzVbQIGEDz{5v?%kQwIzUfpSwI zQ;BkOM=c2*;13-?li^}4ellfd+xDjXC*~b4F;dTaYX#-c?`76hwsMW#0DQ-nN1p1W zgyjr0bu~Q1hR_7`@N3{1*Z*35BVA=H?$Q5lRSQvYmcFNBAo@V3M(h2{x(^Zd^~%0w zCJOw0B-*@k_yst5)49^liNI|^M|w_O!ncJ|+sf;+N6w0rjWb+~i&m8_RN!w)n~fKX zQO6ZEh~7JmA05`20t5-6nl^CJwNA}&1f%E~Vs`8ESKF8YW zLeYbh6c+r*q@We=t6#_5s1DAhqwDq;aRYhZvPD1#=i+;%R&OGPwb`YH9iE)c5MW0A zKW_+qz!a?n28(9hKn_Un+~-cbBXUQlhJwd9e7 zo&p)y1bsahloQf(#{G)ZD4PfM@Dm0>p#*C(NkJY-!SOMmt^aqd&^k;{RC^(u=Hb7H zis?1J+lUY?v*u(>*)KKGnNWJW(2#x#OICo0BIe#Y zUXF4n##f;e#s*Q6-5s4L46Q+EF2PujveBb5=0!)I6EX+}COvfpP$9vJsGe~TCNtm{ zJC}*6w10(h$*RKgDM8>|Vhkc2mF#KWNZ?c6^6`{dB_H5?U4tM`vk{9RKPtLfmbG<0 zKif;&NnL@@>)o6<&`h?%oALsHw8DXJuAnEsWwF--*ruD4+n=Z+tY<7EQ|FUHE=Kmc z)m#H^!Y#BhP^{90Vj5NP#V`^hi)bV6Pk`RGFn>?$O-Hcl@RFG8TpIpBomVC{EOdEa8AOpJ;@UX@WR;(M0!dWD15(__LbHWmbGTL{`Y-pNz^ zNJVN-`7bTjn6HxrBUYnr<=o-Ll8?tll1ZA3*TLeHWqfJpi@=~(jD=( z-+)X_{dO5_z@PaWXB!OQ%L!g2+cVBsW3EyM#^Hb$I*h4tr7jxLLSz{tQ=m>&_0qoY z{UYLZ0o6K*=XpixnLRFYOLop4+Iqk#^}h!iUKl( zgmj4@F@$uZ0s@i>h=g>A3VFPymGl9{KLGl9M>5Sra zradYt60P^02G%X(jnCEyHtJ)YVvlRHxBMlcW0+P^5XQRC9n& zFUidhU9T8~A6y;^PYXT^=JOZAefQXU+@yJ#5f;~}*ZAnL=zOp7g{#(m*EsEtiovu@ z+p8QzTIvrC=2*F?03x@ZzA4TNy}w;mEpf>A-!%=J?ide6n)2(#X#LW^dzcqk+j5Yf zu=Ck5?A`3%AR5257}rlTlA@(7r9tM{376DY-Uz2trj#AGrP?Dw_|iZ1%^lItIrv3KKT?aHK2Ee0h7NUk);E6Ij#&Ko2ZsJrKl@Hf zej;!V7iY)rxB*GMLom7ppMVU;W}f5h9Vs1a+OOY5ci+mK47NwF;mSninf)X9Rg$ie zGDF-qchkjJJ`+Fui8pdagp&Lu;QOg|?7ny0xi}HdAw!7g*j-+hYE|Zv1B<*o^)=%h z);OqsQGcwNaJPW%Ho|f1!Jz%mPQq?LV5hIxzSD)U_UeS6cMN2Kfg=K05f>nK| zQE3R_y`<>bAROu`ZuY^2V?A10ji<%Vk3;v3v>3q&cuH&J9HR>%V+Zg3=D1irc2$9n4W|X{gk>hhl=U`*easJIG1*O8H zx{tlc>@5C;dM-LHDt}ZI!q=9POqr6ceRfNKXspjR2pT37FO5VCV7m-n8>>sIy2@Lsk9!yE5S) zWj&JbFOE~bS4vaVRnKUPOFLNnETT(pP95N|Y#$m*!gU}Q5FTi{;RZjI4ss(s)280^ z)lxFQM--1QIMF4x?Vdr+&nY>|DCw=V(l8+WVmpod7iAF1cwi#WQ9P5;4ev2W(f^>C z-|zv#WPR_y?+y-$Y9%JLaqx|{H^qRCkMO56!9A*c@gV%@BF<;}cE0=PqLgo3e>*D^ z%?RMtS<+XLUqhFm06jIjFzWfcwMX z9lw7m3RQYJmg(d6k8KZ_UKs`N&Pp^+PoIWD>x9bQma=bvs1}C)REQ!LOK9VK&uxa%V4sJ!WkgZ1JbG777 z>q&TYFBX~#)7UL|zm`XIvTgBjUi_$8w3VOM`Z~<_C&P>Q>20uDyhpaMK&!D!dy8WJDP{5eO2M1+RTsedUP~*) z?Tgo^p!S&%Q7ask!}(r_GUwCNP`SF^5A3&um5fVjun=0U-+)3X7WS-WA%Qyo;ZjkH~sil$xuC*pRZTw+ZEkl0E(v4J}dIULQ}?_SYTjl~5XF;{zVVi%`pl ztW;sf7Y~G2yu9)zNAM0pqeENs3Y2)1y_^H2m_=(LFV8V28~8mgO@cI)Tj5`n2|Fmk z2#ER0^5^W%;|Ci1j@IQY4$`#G<3FeUAGH9WS)KqAdNN5D6v7W&1KQyGz33dZ=O=gL zj$#BJXzIjek1jW?EtO4YQ^{(e_*1E$lWxie6y#ZWYX#qKc3&lwK=ifY<2!}3$kO^e zdM!mtbDEpnI4Z!t)S=!)T-k=Vp{YhnLaQ#wscw(_reVkb`^u~f@rColuD)d9sEnEkfqeF#uTsfZe9?kUOY zYH(MoVYg#v^YM$GVHRh=2EL#tRZ_ zEJ*|n*ZBM;x7i`_eFU)mtOL}aKRriO^R6)(J9Jv)&M}yyagf8Rkew(cEK#dZsj6Ez zGpF=r^Lat*i`R7_l!u6LLLXjlw#AeRUJ6c0E>?0D?nw9O!gvO;74{+#v=)~g9<5h5 ziMgIXIgiJbl9d47=lE95iwqzxy5f&|WXO#pkmOCe^=U$<(sr2MpMxddcRwsH6)C@> z$c}lbiD~6rloU!2{^?OxMp*oo<FK=I3B;W*G(A3oo;Lw0q@LchU&5~Y&p!bt0B%d2%+lzSsKXk zI;O=!eDVZFtM&+(Ij_KSDequI1&zRtvfV9{A& z3L@8=@^8IZIU+`Q9vu_Vds9=eh~xacqDkM#u8wR8 z%~9-gYx_0Nq}>w1il;~Qw=>Np*X5e$>tZQ11+^k`fKzxbloD0LdkfJ_v zahgWNp^Cr0>^k0^`|)+x!k>`X@h1$GIE#x^fMQz~Pj*13W_L>fyW6DADYy{=z=B77ORA1Uw{$1L+>JZCL*1 z^dVY*x6xj=UHBEMnallwj1W~^PEV^%pf44;A_BK>W$kujIoKd%J>;Z~3YyortP}qj zA)`A9`bCvEm*QQ!r1~%!Gc?hzQqldAr8!$Vkj8Wx8+liE>Nx$&Vvr`Pr6l6_dmjPlbwU&>+brPKjCg;H&*K@f6SI=i};Lp=N)p=1Puyyx@!l5}UW2PrF=akL>T? zF(rE=IWi6(Q=cLkM4tk1yZpY7>$88SB zg;?|RQWcg~B_7vBaaalVut~$cwRz_t*E4R^ni1 zb8{Tv@f{AL{JKha5_#syHRmzzhLY$>ePw|+c{vQN8yVjCb^!^{rf=|wB*vjn3F!H- zcUkv2zf=#hyzqL8L=`M9w2=ZEI6>>du`V+C6fn|*DnACEU=_|3>uY1Aj+j?f^eqD7 zUnMSSmg0c^F#9G(fm=CNintzXHO*c8dYD7oK=w+mA0tx^&zULCf18f9W!s2{ZkX`D zUIqt*%fF3qcrIYR;-}Y;Q0GgGPyy)!BwnSmO`Ie1{Yq^PW~#I?wNFoc!j9ivNqSfu7g# zQ7DGGMkqyHi>Ekvq>FK&DIU=SVq zOVp2HVUIqo?r6*5#HAlv>U|uu4y(;}ebFjvj%n23ZhKZ0Jf?R?gshwk8lYqu({qz? zm}bpMGhn08!c+A2bo+W z-EMFKchVNJU#Q==6s&7VfAPE#X*jE<(Gs342%y+(+Biiqgx!jKdGSJc(4H~r5mn2V zL+<3ip*L9ohyynOv4NbYLIc^KzAP~(RpS89*7j@^ITS(>{iumzd1M!X4WK4DAUB%N zWo!zQuBt#Qo^444;0+(bzXC0!{^gV#ijnspY8-Q`Tjf@KP6aBvj@#Z$@oMH?QMB@k zD(OP+5gI6re0jfwNRN|!z0+BCC%2AG8L6}Tjr!?Ga4d(|3M*j|hG3anmMFSwu7*w8 zN}lJ@ggS=;53W%pQZWo2S@^>Q!~I0XeVe_Kg|mW-&7-o=fkI$Jcq z(JXO@5E5U4OY!fkG~OEzsiA2(nD7L7nd)hYnNJ~Y(A9&pdxL}y=7j!^SJR}6h1fjo z)kivEfSy*Z130%B-Gv40VP<@qUl&wWjVn|=J@0lf#rdULwMtBjoA=xlTUp*ecK;EF zZ7?Ge#?f-NN^+mPAEk0cv5Zr(EgYS@OFUr&BA6?-KCklvCQ>J%}K4#@vB`V|D-K- zeci&gJ)?f=1pDV}zf+pAY1mEY_j&lF+;6;V^qC@w^D*iKUI%cO zVNp*9sOLkk7rogpGD61vo53C*OWGOSOXA(!BS{^FA`F#D%kLZ8AjtwB|AjT1Z3`47-)M?go0M_93%&#T(D6ubOh9M?shM&!~MO1`eSNAl5kG z{vMMf`ohFkEu!{PxeO3;1A`}UI_~mPz2701dxRFmmL;*ZZCSSQNj16AczJOJIi{~r z6YMC5D_D*Ko!{9%9qD9(P`NiRl#Xm0Fym;_ggFiLOK*d|#53E+O2Iz3Q}Bp$xUk7A zkaRma{FzJKvh6vUDfg4^yZ?fnN(FT#<1SXjkoY(&jy1259k202zMwA*be2#|&Ynwz zV$H&m1{mj;ojWt=FW19YZKtpe;dgwsIEd==ZwuRc?}!_TP@2zR#L#9!Tx#{mIiNLh zv7q)7=_*o8D8uG}Xid~T-paBlXLpZs+M6FIky~}ZVaJy0aM_UAY#?z;0Ph8pYq?2? zd)o!cS7!X@6Q9Imx}?tPc$%LY?(66$)wH@Tg}(Ss7xBG>^8k4gC9_`PnqT~^ zC7U*LUFkt(>jfJ+^*}{^kJceAL+PqSFUwX5QD;@r6A~iqU2{A3w6;o`1mF46ca% zWRaS0`~=vU1q4!>lWD~NqxOj)dt)2}zsA1)(&Ji|Wif1R=_yDI%%(#H{UUQZTcDCk^#4OS(8*0emMs+DK`9;=dhcq*Z1Q+0pIPDwczhU7q#eX*Ybs4oeeAUNV%JVw9x@C6#oech5FTa~do5RF$Y@(n>=mO|KwNoro} z3_ynrPCB3g!ZhrfquiU^@V*v~w*5Nbb->OL?lj_2^3O^! z|4)GJ%^yQI#E}lBV^97}V%$t~qyXwkz-hvN09uF*oWKy}p6I$L>}Il7L-SI3)ekX(`lkInsp?*?;iuorj6^z! zXE0lXm`lc?IKZt1T_bEf3wZO3l}_MZ9IBwjV`{adNr)rruS5?MmwsKwM9z>UT2ptE zBrVH4MBR^6_7A&i>gCz^JZpH%Pf0Q^`G!~jDr`h1MdoI{HZ(#i0mrXbM` zG>)6Sg!h>GmtL8l7$HUaWoPoQWdxksbo0CLs0v<8;l7wb5opB5PRXKQUP;1Dz>6l5|(s)Kbfv8X>$zL5Ci3PMRWgS zz?TD;#`t&iJR?hm&%5ke(%=bdZ>ZG0sH&~@TQA=e^8N`m%sMa)gjCu*(@VX`61P7H zA=xna*nLAe0lH`L-mb|1b7134T2AdeIW|{Hrgj2G{)abCn=Zi==k)~hy;rIno4N;_ zA+Txl)|88P9VrRs9?IJf$Ss9k>g&gfMCHYaGS7_$jNW;HwkKc{_ati%dtTO)B6kSH zT`?zmois^aat(!SJt@jS3zk9xyDR(7y4#3VFc4`;Py_+fA-jZBZJ>IsmFnt!TCZ0b zjqIY%*pG&`uIjmaK_?4VOTQ{)bQeD5l8}C%e5YZsz0I#IIpb1w9$mRVEs$L+U$ndN z(~awF5#Q@W$N(%+-3(@jI$JnrU56+VyKl_<;w>s7rIwYIc%!XIayV#fiSKv(+PA%} z_iCF-4@=3zzRjaYrM!~+O5-w`BwG@f`dj}k^n5b*51O*B{%XiN0q|(=t~HXUH>(SJ zRf7b1eJD#j5uei#Qg*Uv2jEie*eXjcxHHK0O`#7~-e#omW0%;LFkC={Qc99{3HL_n zA7An1A(M(H69BcQ#cK&-gL<=a zJ6Nzrt7z#KyiGOnI?l1F1Iqb=9NYKG2|n9zShjnyyTynnY$0TGYeynpI&hw3%B={? z__prktmgH+-FP_AfF7CH!1yU+I5Z+x^L9qbC)yMDV9v16UX1N~I{e zoEM^HP%~H}`SRjH^yR^BayK9kJ~6%aik5uz z1YS@Bq&1q)*~$My{l2mcHtckY88OrW=A zMo1$=VRlpu2~@U>{L{akqi}^VQW9l8m~I8(<47rq2|Nx8`ThQG>87 zqzeGaz`~H<4S;d1DVqE7HEXO&j*ZcFi4=0n-QgL}bky3^&dGHec{7c<;b${^Mc^Af zt3Io#9BPPC+8UtGTOFY2v9Vk13Eh+<<>C%*&%;ok-C;Fsxq*!r$++u?)BFZ%WIg1C z+|`ZCKV9MvSS<6qvM5RO;@9&65TGVV+sCr@ix6MC4|PH20jmg}HEUYMUwhjQK{7p1 zK`%9*#~A9`oPVb-ROPcX-V@)-BK{+SvwvIKXp5TxX@>OjH_#QFuO0fO)@mYq16__$ z6^#OP`D0yMzqWwY=&!rNzIeY3&J6@WT%yvo)xPV@v#w9#tW41C$+VQ%vR%hHv~L>`}!bwJ`q{L zF~H0*KbR?~L!VBhj@gr0>i0V1Ef82V^x-315V$4o2Qou|G;X3oFB$_WX1O zd=xZzxRGbu#=o>j7WRpw&_1nq4YHgi@VA5p{#?xm3-cK ztrl1qt_DoDDdI+|rsVCe{KD-UFEy}X06{G99&7xLWncXCnO>|A*+N`JH>`wMw2-yi z{k8~mnB!Nv`_dxpcfL8d?GEoU>X4`{y;NKgiJEEloj^6!TtTc;@<}Pu##J<BDs3G>UEr0$Vu-1e8FR=9-lAoisubjr(*oqFNa#%5x^!_>rlm<`;lQ1y zmL*F9Zmw7AEao%6icuo!uthJ9(jy8L!^|7;ZFrxE!_GjZA6EtFP&dh&eOBeU`wp*b zo-Y}IUQSNzrpycGbS5^9JmvYY`EjPbN${~$g#2`o`$#Tb0~00Ex8EcfT;bimR#e|6 zp^2)`<&Rk-tnXjKHs*_d4|NdYC9Nvn5qkFQRI3zbLE)RgF9l$yo(wG`20H0iu?h%xEhr9rfzXK2!ffj6ODR+Dm(ek2&r@W$8w(<+c1 z7t69GN7KQ+K}N=D0}|x2mJwp67qb7-}ZpgqMToZYJzo_nKJr)rzuwGRM`+en)>a2toXF`GDU&x45 zRr_Fw(8~D0)`O+@tNtxz9IW7DD9I+XoNK?7{kd#SA zxkI$V55Z;@Cs9ZvQSR}N;Px(qYx|baM49DFb|~*mgeQ@=wXJD8tpvddrE#w-6Scc2 z7t?F!_t$Jz))JF+6u&vemBB;hNQs6Pc`H#uzpIm$7Vq!tkvIBpjxn2`jQ_IBg(03^ zhKU&S%YpG3-!-t>cP7w&Y?DPdL}q9P8#;Cw0#JU-k53Vg?Cb1A|27vV{M0IqKt3$kIN-uf9qiZ{apMj6X+3f?hNe_vP=jC*#d zqGNfp0;0S=SRA?l^}gcw#|{XjRN^+@f(ZrQIln0!2Rat{??OMkkTw3RI)vER@-q%H zT|Z%b)f{Ew{+K6`oECh}>cCe~MgQ8l#Nr?)6E*|Q<(;NHd2>KG>d@+lNt(agu7P+?3m$+rdAwPrSnC8!&G1AX>aiV9>U+X2~QVc_lKdod0jW&CGXmJuApriT;W{Jrfm5BP$E_-2K9~3$cM$Tp#eAW4)|E}lOaTsyXsj- zyg@1CR~e+gCN)i)loY@MemeEi)Kk3d#6tMfcLf)2q2BKO+0#7@w-*_eL@Ak%c)Wyl zGVrQ93e~s(vkO;y*aIwJDyaNzO%Uc{C>d9M2Y>BPuCFnUHYGIRM-cjbS(oO;J^$A+ zmSqO~lraW$M{!*NIkkH`!FP*CKvheyJ8DHdIv5|6$)`Z5G#?k$0WN#E$SV8!qRTZu zXY$>RDrzs$TLKBSlXX1Tq(j?%m6k*^g#rixe_}R?0K-tw-C~qs+E)pE%bRugl$>*Y z{!YPV-G}?Wzu0Mbi*K{g4;_LIY5EH;V$PrX_Ezwlg@z6g7JO(`B88z1Bd<;{RY-vS0VXv1J1v@z zY^%CwSY0IvB#I{EOPoA^Ck899Bycp1FfKoCL>dDh${^=g51pJ@6kqVO)a>Hv1v@m?!DUzMs8l(k-mqF;o`9456xt9qn8^L`x;2T{73LQTvXSS z0l=-jetA z+Bv+Jo+us%$d_u(<`2X8a??x?V|Q6e8i&i@pcRFwvD{Vpt3v<(wrZNA)dxS1O4kQ=M<_pD>nfd25K&{y$?0p{H0 zg_Uz=Sd2q+UKb*CowEt+^ZQ%7T*p6!m5q?~i>}L*}3>ACq&oDKQsRQ2l&EWQm zmI^meW>B9dfAYtl&(nBPsPynjEH+N5S7AMvQZuh%613nyWn|W(z41YwvH18QNaKtH z_SwxwTOvr5uVZ!JQ9TBQ)U zC>gN&aErkHc&j4$<2`kDpJI9k7|Fh2J4C!uAHu;vk?x`W!yW%7(r%pzA{%GN@tuZV zd9(YWgveFi_~*>Wy(FKwlzSWK!(mF*G$&4yO3OhDHX8>lBv;#nbq*-sCUEHxnUwjN z!B&e8pSpj&eAs^pcwd%bXGnx7?hD7XLj_qEmU7+25XzCQ$~SKBI*;Q&?XPKWRdpM(YyL!L6-Bo8-j=5g95q|Lf#&i)tA-F`fy{t^;%p7^Za z-UuNcvF=nh;nQnX3aFmW)&ptJU)CC&4?WCv{Yk1k`Cwf$p7rTGW&XBxdQ#zgg(rpM z;;aRg>(ptA|M)A;hQ$Jv#ud~}b!@GzRz(bTdgb$AZ-uL1rwfH#kuK5K@`t)oH8)Z&ulKZT1W#@G z%-f3vR-PTG2&SdGx4myR*`*M7DJBv+&Y$`YwIfqm=!#M1XMUWUYoxXCjrRU@T}Doj zYArvLMWGYOyqnP6%8WSwHpfufRWAoXj(r2@b197UpGt|9BpcnomvK+fR`ss>IPxxm zOg*^E zj&QR7xzp=S4O_k~L(GdhfvEA7F}o8tQ*3(71CafgBkh~#B2mM5iFCYZ5}Vr#eT{s6(UvA*VFfiV=we*)l*uO+cHPn483C@uTG=gW(3}P?@~rcS$mEB z9a{eV#8!m$1EHx|ufV?>-fS@l+hV%0(0X0Bui$4Nc%#kjFvCo*4*ng z`~F|~KBaQc9%cm&5wAZT%$mB-sb#5%Z)P?o+R(P+D*ouo4iWugQOFZj;$hc!1N3$4u+x7c_DM2`t@KLSvkF^zk+H_e9%)_KocT2HcHtWMQJ@1XG_>wh8Xa6&sy z8;5C)_VWW6w>XYYRs+3Pt}e!^(~cj_$3+nZvioFdo!!=L3bMI-T{}1fkNstpU7mWp z&zhlH!d;2|=Uu1L*#y5EPVlF~Q~0;&9dhud8Kf_*NH4JoT~B>#IGHcUfv~>uGG4XzP6$8 zFNc5(+@lfuu|gjXjmsat3ijb)N_1c~5ho9QUN6r-oj*iED&TCb!Lpq@EW3(N8K^p)SfX^l-rDbP-h=>1y?O zm`te|LzFuo=E@Sc-A%P9;hwL%0{WxT^H{E5qIe&olLv`K?VJohd}{TUm7j1g>>S2> zx?FJnP1Bu+^Q*k{sC`(Nlr)J51#6l1bNAbB^7(ANk?BdQW6NA0kGV<1^hYOpbTFv) z(a9VSLvr%aVx|M7WTRo1SwMOS)?7c0tV3O#PMozHW~1J4AueUwvpDX2zuG~*44Q8s zc(~C1&$_goBU092GY%Orf^;R)ntM&ucQf&5v8BzUHEzfOl|FZQI_`i*ZT#6IaX`lYW?Yg!KI+McM*kn~eNAlkqMGwzcG>~?fQ%YwAS-t2wqdl> zX~295citz?%I~%b9SnOmxIOLOpXPYcW(s8bt7n29Yv?#2XBaBMM((N)s%v9wh0phd zruK6DhWgIdg%pHGz_Yw7fRa<`F(C_|hA&GR3oh48>AZ$};@VD7TM)4&Jj0DA=&yai z{CSlw;#E+y@`{C4z~YD%e!52*3i#=a-+$I5VgDuWcXI%7zi)*CHtQhU23G0pg+P9l z>U%x6Rz1YFS8JamvEa2Wn(8um)>Lf={yX4AZspI(wim)bAubT%nk{;e*LV;b__8Ct z>QzKFs1D)e;q7_qr8}5r8yu$Xu99n~34xcQr3sY#9#YxMSaNx;?;1?xB#~W67RTeK zIgBx=miVr<8Y+UUg#XRykhoJ$Y;0K_%x`z%5ESp8zQ68<+zE@B3vf86dJfx2TpE+x zC-iP5&xyFzJ~w2;zaxwm%bE}N)7s`s|I9w<+8XB-Of<&h;E>I?V#{?vuuRO~me+a* zI{_FhQsU|Kt+0twVdKW@Y#xqGelcwhW_vdcDj)Zx{=bHL)mb&b27Nbq|D#75m@?mt z%nls%9jb)CsFPbk4`$dt*uCzo4i?u^KXRYil>sF4EI&HzK-_>q58Cx!11pJ^maS%O z+kil7)xqXBuLHVkP5yu&B9S?PgJ&1)5n15vw2dbvY1fw*;FzJ%_9O{#PI!?P$DlwL z3Wb5dqn%hi?Q$d(SB>`6{h7|9yOxdd)&!jSyCNNvaz>MP`WtHO*xfhjKDh1^-{!i{ zQ~m64dxltymiTm7L$DNTIj*K&ta*gzfdIJcpoFsD{rvA*`wd7~RF-W%J}C7PdR-K; zT4asKHBxx?PO}dz4G>rcMwupjuZE_+8Ut8BTG1|A%qg&TU5Y5vSLJqC_$3GlyvFWW z_O&wQ@8VvOk?%pH0fkn{nVb_~2ezmJMsb&@?>}F@I4kwks}c~~2tXiBvTyeOH)ODB zO*5cn{WD{`?sTlh;CWm{#iQ$gAjKAsiU_b9`<7LcF9oi*IS-#SiDz@@9DNmm+3v;< za2lQ8H3wU|$ASj}8_~Ynf8E#2|8Q!zx`wEwMGr)@qGCx*?ix%~;eTNNG8;@(DA~ay z-5;m)389fae&G3IJ}%azYV=QD%LoUPF6m9j;aL7Fsd3gX{31Wspq@TO{!rYbZEaNx zOq@U}@zWr|=awuk%HEU_|1~RlK6AN;oVN*01W-W;Pvb_K^aF;~iF35`@?9135mqoI z<0QCTs)YAxNFl#@K+0#=VRc4>h~cmx@Qe_I%Nbu{i(t8Gh};I)Vevy_oT^dHaV_T! z`|G)$<%4DdS4~3vXw8FPvX~5Tg#N2&t}-AVUZ2d1&Rmd1#$2bUaTbQ@w}fT8$q~dC zsOPFL8s6H^yd+XuVE90|{5|vSd@GX9F09U#(XC7~aFgBuVUy)XKt*~x+p^IwVt(W> zGve(T>BBhya>Wif;TcU^#E5;( zOQlwk@PoI3k!&h!rS4bgl_G&sWOzsf`Jc=UJHZi91Xp@BhoQ>Tn)j}HWqDh0)) z9dKm&oEl9>fYb7_oX{@7R25vIV{=%80qNHPS6@Itb0(8Ey3Zs}amLluY*5%bh*6Q$ zs@wy`fj_{wvnm3aBL^-Ci*`-!KXcUZNA*ohT=GQe!{YD5zPgV3dC{_M64jUlF=l(j zk4X-MCG00{q-C?wXvw!dFvxK0aJ#3yqQ=P5osZv?4LK;0m_Khs5%TQe%nw0quck|; zo*33trpjv~y0S=Bd#?L5y(VP&fA#?ObajRlS{A84?b@z+?krdGaH{Ci|K863Z6t4~{Sx8~y!Iecv!G{aKM20X{{s|PyOkfP z+3tUG$r`AQYrYK2xhOSJq%!p*_FHi`yzAq^U$KYVE^0M@$N3r^KucYUr~-i#{;Y}4 z^oxs5J?9+qdfn{aYtkCW62Y4S+Lk1zmNb{{vt~7gTTpxn3ydFRDEZ*;SJ0YSvEKU6 z2Ce&pGc=?#&Iev00~Y>k05pvjtPxt^Td7)pZc)d(f?+tmzEVJqiCXL_Vq}A;@yt5L zDLr!u?c{yYGPk2B9fISYET$kcnU$6eDgVgKSdyAfN&I^9p14hblTNgo{IO=lBT#)- zE%W*$+!?e%uIz=T;2JYH&>y*;j+l2D&#@FDBM1f%A(lb#MWAe_>jClUmO}M`Q)7|O zm0S_bR&m~Zol&s_BG)WNl??tZ(tKqBEGMJSG8we^!Cc*V@UOw5m*R%(Yl|MF80NM7 z*^A#1KeOps7O(qP6?v(_2+_=K`F~CgMfBx@-5alTlQE5x``5NFJKQso z=@q;Xdhyk;I0L`m_t#dgNM!v%lJr1MeOBBlx9w z7Dm-~gK_TV*MCDoMU1@hJ+V(N*_D36XtjR=fn@+U_QWe2BbVm&p!{z&mB(sbia$YU zjRtPWK%2On@I6g24nQ3qzy=^zOM*AVyoez(_?!4(03d#Ri@WVWc5{v~E6QX6C$an3 z{h2f0La(t>*+-u6dpP{~uS@Dx)fW-le4wTh+yhufg@GRXuhh01OPw>^-4_)BZj1;? z_uT(f6Owa%AHT|0Rxjf#iHyY!{Tq;HqXi{sZ@fpkQ*5a=UML-q4}MA7Jip85@sasH zrhMFFTH=?u-qT^?paJ6a13AA#Nie0O$MB-{@6*hxf_ z#Vx;tJ(paA@U|<9GLJbjm@N%&!fI|63^mh{?U1K?kVmnAc^nnIE@B8`0QC;_Zx@F- z_CSzZ=f`o7$2WnIsIb>$R8>}Ci??C)S!$*@iIH?HxT|S1)zzTwXpUgZVeG!{HTX+( zt14~@;e|T?IRAUEIx)*Ksy$bghYb8ElzSYIJX%u&zZf-rtawGvtDMpbmIx~bj?Ww< z4x%E;O3O)vd?F{yoZwPMgvpFQ5!lb?e(%!+#TFc};MHAfI^XZD(+8j0Q+wxKix}tx zw9(3AUI&CgHjELmF^{w5H3+l7l+KS@W(2mc z#N8X%K$MeLV)nHop7twYa!5ms zZ_6o4&U1TXEwSqYcElm-Bf32FM*PMNRppLUacVM7UWV5VJdtEeDz}w~e-`=@9@?%D zhsvxQ#((6lS(N!C%VUvlOxJL=K`geiZ7EOkts)|eTV7oG%#Bw6HzYErKVLfn6Rwy; z754f|(`r`l8I!2Pm#%~7K#c1BCiUppA-eU9Az4z7r5VzGU$MBA`3n4(Ra72k;DXxy zZ3Y0Xp8akaw}4`lyHc~#jc;h>mCMDD@7K^PcbxS*@dGb0CH`%9uhJLdd8C^xEovUX z-Ib_Xk-^h94rawpN>IMzT&7jxWC@ke^}^Z;ns7@@ZL-{ESKOx;ZfKOI<;o>I$hwnO z#{ZAduc@r(?lpPw{}l&gDyPRN-d;Wa*&LnT;J2@exwyn}}^>#tGXo1P^3Z@gACFreOEcRr_{Q|PVXp`w8I8c}52y2$XKTL_EJq4HOF zMHW$aHt!Tp+Gp7ih*0?Ewp;K}GDJF$x)gV@cB>UA7U*z)&<I3e zNf+`=+sUBEcWWU1Gs0KGGH?O!OnOQ4L+s&P7aRL6=0~(oFM z2e@Y4NX;abF_TtlnIxTQxI-uuNjJU@-@sokoO-Z2S@Bou<{p#9=6=+}x|$Z9 z6_=}LaTYkIvdG95>@lQi?hkr*EHKpR>()0>nH5FSPVpyiN|Fr49{gqf&iDY#BT_UQ zHTl7D;5Bz<^YD$}uSs}%4Zqy4p$n__pCVb?3;ugQc1?tzJH(d6Da+~LQ!w98T5vxg zNU2SMvb&up%13op;UySV#Ms|gBEC2-W)>6GC{20v!0FSF=Tib^ z&V$Tb#3ZF)zJmtl4m;cGpIrJoeFu-*@`J`ylfPALi@$N*&;8N@vpr68%7ep$MFt>C zGpQ9|(58IyR7huK-2($JgTxU1lMval$p#)FE6_H|NARXq>OywM`Ky>~dN@e^gyeYF ze6P3bwR>(ZKDaS&vkKLlu$VI=WT^jWHq7d4Nxo%x!`tgwSTNh^6Xcrx3q;`fzgBT+ zDc5mJVoCoguXN4Oy8*5sx3Pz@c4f0AmIG&JF}j^bMQ}Tk-#sLpXCvtkCQ?G}Mr| zuAs1DnxWg*fWUT;`^&m}pTJh1r_cSaiE1;c(HahAWP^fSRI~f@i>cb!aZ%+>BHG1o zxChC6i|QXOmTv1qamPrJdax#1;lq6`>ac%*3O)jdpdsoFy>3$>dN^!jx85=#?(C{m zGouazJyi(3!AyBG|HiPp4y#y<@cVa_s*PM4HzTMmpUX`zGrz2ur4_PzW z>+=p5G#*fMx&B9a%HRs7D|Wn14Ac(%JWe2ang?O)nZYb7|bpxSfM*qz*~c;3=X_)+)L`<@`^+(ISlMIl$15!<&!U7PPdFmZv9C z)?!*>eSG|1H$ECY8m;{GV2uub4_37D_3#)E0~jYl|8c<%Oy42d{OxqO=LOPt^dHtu zO2yu@pdeVORas*I9Ojr(QU&zRqEPEMs0YbN)qdLSs#$oPCzB3((@v$~D;{QDb>`Ip zH#0B0X7rH-!d^YpHbyNb`&`%FXYYOHtbMP0{g#Af z8GjqTlONyvIPoY}-&6v`x93d1;(m2zHF4c@2hfp_f@~A}68is=zQ`7e%gIGe_v9az zZ09nx^Y;uu_zpm6glAQ`zHG;&3!w9d%N=D(9;{gu9$0K;9L5aIxlSYU@wT^7o3U)+ zq+#0*$0ZG_^$$a(tGFs*M0s)n@m296yikI2>~;tPsRp79NHa zkk}nfg2Qdq{Oo=kI}4oYB24R*x_lli#+Ir>kgXSQtIA;?0G9}ASTXvaV@RJlP{&|* zfb8#1cg*Z&e1&l<_Al_1#;kuEt6-dYBthpFK;ZG!kB}64YUSi4vpHOVEfy6b+2kI< zUC0{EDoVHW{d|oqAS`kG0nx#dN6)`kUOHzr_2IKrpJog9o^U4tHChuXJ;u21Xsj30 z+@mgS{iqu~Z!*HX(3WNJvW;an%@#Y>)tvhchIlO(0E)syQpNujO#VVBkeVg&lb$Ye z01Hweo)Q$VV)|jgmc<`Ye#q<3A7T>qTv+B!!3|CBfPH<}*%A7R_3 z3hg65&tm+JxEDYB2UNR?uymCfq#^ikusSic=-joO_DU&FDmWoS~ zMu;LOJS7y2dDPN&dDU3+sE48-UO8?Qc8A8>(m3&a%NviUM=c`tu0$qtg8&!d3x zy*z9`T}mDl(O3IB(^N(Llc0h+qKC~ELhzNxds~Obo9Z<1?fo>nS6X>TyE-<?L^@=uUu^M=?PRHJdkj057@%mNXj%-eie)&8n?p!CaZd6=}3Q+ zyXL^FBXFTr5Zsj5dbH|fa|yj^jL7dWM3Bq;@PgZ91f+k9km3R53y#Ysu?HEhqISEW z(QUk?r(}sXft0KYd^H!7iOV?pU{}ecez{XcevyHEoBdS=^e~X&n zcZ~;>bVcZQQ*Vqe>ykRaAN>$W1#b#a9}E-A^5CcA9mb!XTN?I^SP_}+)|@36Y|>;e zmL_NcDMPCc=PJfW*y{|z5KvrvP?no{k@G&nGL>XGnhiMdvd;S>iNpaf4)t$^k(pS@%2dPx#xH$*Q+im)zsZkck-{Ov@JEJLR2g}C z&CO&R=9|&{@c+vKIDT}z1;rD+e)a;}c_?8-nOTIz_`A$8e9ZQ=Xa$%zD~BQZ&qKLk z3<0ie4M1WIRWgf-2(9iQvJK78lxAUt#NC4ZuLn=>ykFj`n|I`)tiFfgr-W&$$^0&k z(dHPbcWgFsI+()v)4<57FPla^Ie!-lw;OaMeZ}fP$k~xgLOySD2@1@=wmB?6k{QH1 zvmXWsA=htjTRawd8dD0cqiA{P6&M)X0*JC!iL_X6C@1iFHsg2_sostVOfYaNVqm3J zB?Q~5eVruIa-bJ3P|v3>a5#4wzV_*(D{N%&+MYi#)@wF+Qnp)!*PX)wdBPtjMjpyK zjxWa~Yq(m8&1P`UvzI9qN>}SXhk)F%rd(-&Z{VG-n>HM*-+;& zTH-|grcFdWq-5nr@B`C9W>w)@kp8XT9oAby&tj~>{t0=SHO&iDl(12HIU?V%Zd7z^ zo`q*|wF|U?sj*ea0h7D_{~4O(=`q*9f$eFk$Z592bYri^hBi|G;IX8BK&kSehh3k+ zX#<^PkD9~?3mViRbS+DFA3-^YP5W6g^b?Qxgt2@ySpW_)$bhS`)h;DS>T$?Xl43^; zAJGGgpy;{1lFlN)DSqd8xNr&+$D|ytdZeGv;Txq4%x z1c0$sC+_BeO3z$u6nsMsC(&pDM_EB-He+hkA2xCF8Hs&{l&C*E$DcKUvBJR$&tE-J zK2w*YTBvx1WX7IE49A5%K@wzYDP|1MipoH=^=X%Fh;JvP{9@G)oe}UOy8SlC5v1EGxzGa!xlX8p3Ki98*4mCQLkKLhFoASLK()2iOymh^eFNb*$);!*7 z)jZrv>Mz8u*f(6pG#zUK(do_BCs3jNr@+4FNB(@2{)AYQWC5U6iucBK5wF&4Ah8(Lv)DN z73ZML2nK?bFD2%-UMLpxgRhM29)h(^=F{*g!fef~+95((3yLWG6_Y(^7j;$f!+uM} zJuU}{PvM@|?fTl9&R`>(VN0F3V!*Iv@ox4hNo4gcCu%3LVK=iV-}2i%gQt&Yc2s(o zI}+l$x0G$t7;kDPrdVm`~yH z%D1Ce3G7hLbhBu{4`iLR!7_z@4Plf?`CsFN{I{Zf=IC@H%+|+`JiD#vVD4tbT8tB6 zHpi(3BIrLT*ai$*L^<*b>;9WcA6O{<+YL(E$b`;@4ivHe)XG4FqEj=oy0SQ|R`Zd6 zd7(5RL%S7`h#&f}+*)){%NmwhyUneaQgiX&|gM90XX^)?Bu6TD7z=O8@*aZCnqqYi69Ea*NNje+|I8`SL=ehyB|Ewsey<+ah?LuXhxV7NiqOXA z9jmRjI+{6@IIi9eov-^IwUJPGP&$Vx6~uV90}5P2*lJb*>B?O^($cN>`Sc`Zj^q-; zGTyQpapG~A{cU%BeC+IxF{d7I$iw-oWkfYv>|)vU8rnb6`A5A4{hE`86Ei!sT?Sb{ zFCrx%*vLDc+_UnXITV{7bB~yu*-=rloBc zk(XWd6?MlDa;(QX4}+#~T`kAIn=k5ID8myuf=w@h_F)4Z$uNHp z<mPV0~xtH%C` ziZ)hkllZ)@ov}=8r@c-ZC^s6)M&iY+7%Zjx{2w1G3cj^;gTyHO-XWRYZu2q)1nVXP zdOEtqVxD=ib1z=x0dQP$YQ23v+EKJ$sIN zM*^9yA$~*+18m{moJ3&=lrH5_t)hDYyd_+A-zMC};(}133SKQh_zwqg1m!|5;b4%{pXIpT}^c<5$*(0;dh9uD4G& zu%)X(g|0-F7Wf?UdLv_(iLWd-aVJ_97TqHT!Cw77<4AS4KO?5ypY}CWYy<$#wNc zc0n6P=##`eIW23U2xbsAHMeJgChsO60ZA`%1VRUUNO&?K?3yU)pwzuBLKGn8f=)Oj z<;}}5->={b15~-vgcW=CPw`yzn5ZI%CdIP|`;->z$0>wy;K>+|?uG40wK+p#hf=&q zq_cl-M`U6CUD%TjLgwy!Li!@(9nZ$-Bq+PKM)uuE_9BFH9*+%z5ZX1)H$RhA`9E4M zqwrZMZLAdBJ8DOvlUB=kHKTHj1}g2;Ga|;6VvUmDatJ0{w?LpB_( zNBySh^o^l`V7}jP=OKCr2)jdSzd9e0ks*)uVg$Qy*l}{06#8|?V z+h7d*OvEFPi*`aFfkxoo5#@rk$%yfN6iXpPN;9r>4!S&Gv(MK2G837 z#x@Fp7rSe(GV{lxXdhEjC{elnFgn04vN?Y~AqjA!T$}pr{Av*FNWaf!o$AJ5quv$y znr1A@c(-obUa_6+L(E`6yBh0HXvzrslCm86sf07y8x?oHJyMCP zDUi?;F*eIM+XYQ4H=sQLxIJ0JUCt5L47B8fo(sq%CQ?!L%*}rl=h7IrO79nGq+gMM zobPtV5K^?0Kh^~=pRlYv4gocQj(>~QtjQaMW}0#6k$P@cH&BTvgyChU=O^HiWQ@v3 zMQ_emp%|nQWmL_?lXW%jKw8|Y%Ry8a(63+7wd4o^T*Bc3%rr!vh+Ld`lzKD5`otR% zhSH__5dvzvVF5trb4dgYEtczRWgir~(QDy`6SghYS$h2A{AFJbkYkCf97Zsyc#O4=eg)t=s>(jnvU&#Y)G#&4AlLNPn(kuk2da)1=hy;5y0THZ4EmlzL|bqMJ%R3OB7DUv8OcT z83bOF&4WVAj~ww5W0_@%5O3*<3A{<&^aFpa>nIg%=TLJf5hfb{6`;D>IOF>eu@5?3 zzQ|UwCzcSuFZXqdqcDWzO%bW(j8xac5w8dJAaE%0ug{9GVvaBNns908ycwd8M&;_e z5Ol!`fx84cZ+4Tg3zNwkslFBxZY-P5b0w;t0g+M4Ed=(f`Y-JCa=|2ni}(>+ zzf@`3?D7cugFHf7SiHY6jX-c2W`7|RQ~znR)9&ew$xpLJ~e1p~D?S57V_jr*}k`Gbkf?WXKu*=C{PL?9&?u%9Tg zZ0y}vy6LlXp)8F|JN{Tm^GlC;K-;6K2mreN-8E%wcOf;P?iP(EAmXvzh?wu(&|>1* zd+*2#Y>B8ke~g7E!oW3c{l@IFSh+Vn!Wcf!9QU^2N~0|xL=Pc#u!D&zGgKPcNfbAJ zu!2%%;rir2k-VOaW%V^CT1+4huS0V^cExw?QCz_8avLP$jkryOad$)|in@IvYh|Ju zb&%O8&>*70-64POAdX&~HT?W7QETJt3mRe>S_CI8bHiVh8;(Gi@QJhD1FD3Jw@4MA zj}Vy&In#K|@S378ol&s8lk(P(ic#&>ABGi_3HZMYXa^SS!4^q&qC;h$eo6$!F=_q} zNelFKnQQ5?`V>2m>(q;?Z_Wa@B|Y&|6B3C?6T0uKzx38d#Zc@&g1P<{Zs?V;ap?_v zz?_q)sbvZ9+o-W>nq6ug`pCS^!T5zcp9idO%jK$nPfvy5|LHx){ig$AL2E)=yF~|Kgh!wD$u;&4fug+i(McPi^kU zUStdem}H{CmG|+V8 z0kzFysGf_++B%QQK|DwbkK=>zkx9dnY;GlH3$r!%Ta}uFys>xq+45@`Iwl#dc z{3VqI15`(|_WT~W?v^2{j2^DyiGmp6A1-5iqXVkHgP%E{^QT;)dyyaU0~EU@PsR8u z@bx^P?RIK0QYQoK~#favncYD*FYk2h@LoWq_D)J1AO)zZ-I14Q-g}BMo zJf6ZmV9(k=8hu`@cTfD?{bt|t`9bvaW3bD`wdc%Vg0cp*lq;_2S@`thwWsB#>ez`Z z2sVSetO+@4*cQP{vTIHh_$|Sbg0={!fRU}_D}9$_J^Drh4Y=`f!U9D-0lP;dK&F-5 zi^|l&my|D-KA~9tE!ZM5oTw9QwJ?^Fwk);l(33LeGLB{Q1(u3&NQxhC36RKC-LtGD6b)EMYX4+PHADWFtfb*5lteta~H@}a6m9(mO)mKk|#Xx95SbrevK5YZ*%?A_*Kl^VC15)`8_4qZy7yk7bfpmK@h09R=H z8uXd^x?nmQ+D%~VZIMx*4j4@8b=JbU0R87#gheo8_{Fc2^0rMMtF~529Wz-&@4} zrVRE1cGqTuF77sS7?0Gea4DwqonZ0)bdlk7pHn9Mn^~ae{RE=)hz=bK%bYCO9f2?rW7< z_W!5A^kux1U8|NS+MKpkF;?q{MiJdY56D9xKrbnew+0X-*VATbW{&-Wy&?OlW>4%{ zw0Wp%=XBvfaIfK(h))dU=*=SF^HR#J6ZyVsj^YmZd&>$7apuQu1aP4-pubV!GKc; zz>;}`oY;;-?Q(yD+r<|C^#dyCj&lm-vr9Cql*f7vfw)aDy-W#R=R+OZacjtN3H;Id zwgdNEjnVM}w;J`HEP?go8|gyc=|?$C!QB_*33V_4J`2gV&!!k5-T8G4sf;nV7Z6b@ zk&gc<+xmV%MuHvpdlpgc`cpwMq`9`EURRLonvCOxeTi$j-3VT_2-1w&6mqdOVH_-T zNZ0-76TdNLtNyT~=(L~t2WUtQ5cICi^4YCyJtReC5!ONW&;tTv7jjNf#0iQhA5Ics zdK04I8_N}%CkmE{%;hv3CrCHvCc)=1y@dvSu|K)&ty+r0Kn%7tpo_Hz@MXEAry{|t z-V&;7XWv6$`?L!78xtH)*}+d3x>s-beG?tmHQ>G{l7}&IJeS3KZ<4VJNzvlDK~c(` zla%OD;H(T$5~#6?)ad{#5rK~JGz-UIeMpOK_yy}NTvq!05fC|r-{K`b>cZBzVB^O*b}T3Jev}wa$oPM&*xFi z@s4@Skb>u8dl5_Cd!!w1M8ShKynX1SVa^No&e{2kk1$C+`h`d3U)|y|*LSuVZ-b2z zjG}(h7;XMr&azLPzRE^>rw7|E2!(X=;}y6|NNV@Ggx%w32CkEhyfV9fTiXTJ(uyOV z#;geERSd?kU|P@Zl4nlH$B6VIF<*v~)vX^6o4WMQC*Q{;t?c1_j$kIu!HiusU84Zy zUvS8FtLkzX_nejU{Drt+dP+dfeWwvtXmk?KmG5vCQQl_4O^&rK7D_ec&qzrHEz=;r zblML^tyC15X@@fDAyV|u3u6ADE(GBUf?Ub4(w{{APOX;arFL$7aHCq8XY%-g!J_EF zLc-vhTDaK#aMu+4Rxbl~3d4J(Qr}Y~*GF-G`}T#|vF8U>^#CN}*9$xy*GgeZgQg(| z8E#Yr{fS<4n$yw@W$OmM?W~xlf;_0Qe;Jr4KbKI-OzJT1X)y%~Z?L4eH|j2AZOZN9 zTc366FdE(uDKSYf6E=cJzxC$~Fe7E{PH>JnndlofEgr#OPOQ5MjI2Akvum?Z9?7*;f{Va{hH)8M6I|JMpt#P8 zSJmIQbL0#=O-&pX4&nD!%cfZi53{XoSFX!*uvJ2-!_kwI#UtYI>)(x?{W`%# zWMkcSz9lmOX-f0CbQ=#mo1d2sV*%hH5qG(#gp$@dh+K?A5W9>+%fNUAwM?>$yK6o} z^{cN_4{j3jYm8yn^7lP`suGIz)qDmwCP4(1>(QS8`^OIv_V9B3r$yTgw^gLQ6sJ4_ zLDXY>|K(p4x8V)7Z-gxcrrLaJeX=M--W+K)n#vIiLSxtQ2Q4ojJjrtjZs&x=Z$&}$ipP6jbGBp6En}bR2L#ZgKkjM2 z6pvknId1Ao$CGVawBXg;-%8T?JOMwXzPq?v)(DPQ`alymF^FS zsCA=8SLP(0qjUMj2n}(~3HuG@Zp&VBE712*jY!UE59}_1cl5!`Mr>%P&CEphcV%H^ z?rkt+7{AQReoqU*bj82qgml47yp!)Te?m@qiE1yC)Y{Mrtq#}hi0Qy7nxrxzIeY2p);^F^P?)y*H- zh&UXhY#eO+^#YglT!!V1pdv0+pH=EUbDhack*WHSGl82_uN?vL-PHf}Tv7`=4~jRF zfS;@NcRU=ro(>JuVpO_-52RcTJ}*^;pyQ>)X)HA>dEF<97%-29c?2Lh{h^^5W`IY`|XxfaOSs7yX&A3uO7Fd@5 z5WmzxTpb>2R*a5qAtGCD-l%IPg>Jfv;Gs~+oIGx64sm_};~#8U^Gl+@&~I_>?wQ0x z@eU?dm@-)DHvwq`ZX8h79}_8=8xw{-7_z?$UxYD(sltIOGoB!iWPo#JS!mRjeQAiBAWvM4r=Th=XC8-%hCC?) z6@fi&YA)VKF}_THG2X5=Ht>s0P$2sSQ4{zN(cg?iLhkXDbnrEy1hlGLyGrcI@}5Q0 zbvZKIw{W(qL6=Z984I@&Zw$P*iPjx&D?OQ@s*AA}$(D({fLjUz} z8vc4uVLvDMn?mgBOBpG-=0N?b8*leFj`Vn8I=Cm^{CjK^G!b#261~#066M(#!f|I; zD*F0K?4WN8lQaEAoky$;oY@1C1Hc2yV7v=|MiXkRugmv^gw*vQ{BZNi#9mA(=mZ6q z7+TIRBq?Z%UlYJYkqSffrr|1*e*cPt@~kIu6zTY1K%qs5=G%Xmt&0A|Vb0yQ&QwIC z@GD|VoQAy{3K?k^IqH*pIKgPvIdpn3Ip`c74mI+pGq8M{{DR(hXBV3LZVuW`e6@!~cR%LK|5<`e5aq`Hk<=?{NvRRpuAh$D1>FCL z9*7~275EU4!^a~x(nPeAr)1TOzR)q$ZlbcqP6Z{nM&B|m3{-0G8xaKB?oJ|?!*~vc zknGRw`cbHPCoMF7R1ttJ-iPO7;7@Jgur){PO>o^@ptg}>>aD3hOYy&^rb|7r!8Cph zUTg#&tZ(YpN-IM<Ghk8UF<~8G29CP?^rGnWnc5q!|DTyg(7r%j zh=*AWyK{iie5%jA>Ej|Fc*zk2z%7DkF+mGBifj-Qn(UJd)JsF~RBp21<&qw1X8C|! zp>1}+4Muy-*u7co$Bz<=UVP+1!-DCr*tK49uuOfy?+##nPDkM)g11*eNHmkfHr30K zpEVG&7k&_sqs&0X&kEv4Gu^}PkRv7FmNWK6E+i-lU^qafsMn*7-LD`tD9d%E(*}v7 zZJk%2_cg7#>q47xlh~MCcK0KX3M2c=o$-H&=V161(QFH1L&veT+&J?cCy6^hYp*_P zfyDE@??SnJ=IcYK>xfYLgY&xx>}rPv4SbQm7*_D?e4Up#_t%J6tk=yml{7_D{k<8C z`n6LnjFz)MApiI|?OZ6_KgHElzA0FVAPq{WXp)6=u%Kd&{@LI{mTd>sG@$TICa!Ms zX%cw6IY~T`T8Uh1y`yrE)nX)l%hRAtag)u)7!#BQ>5e13q4E_-&cUB^78fF5>N7Wx zqvSmJ$Tp>Hm1xQ};7ZR=ZPxm(Of0b!CU1 z&Ey1#mfb!oiz3zj(17Nhr;@+>TjqBM_2=6Wh6@8lsI($(i1>t>Gi-)H7h{122~&XK zzP0iT;iAOiqSs$2Mqq}NBS-x>+gFWIMm54B|KLQ~4jzL$9>IuQLYvUjn(Je3)h5rt zHNB`0k6y!R$&#^RlP(vu=23sPm_h}Eh5g2qGa$yO{h>A}`Bq`6wuiik%DT2qGY_&h znP4QTetJxn@Q$b)YRem0aUa`he$ZN)Rs+ezZ33vLSjNWJFzj?G<)P0AMf~O&0kLap z1D6kl5_AoJao}TZW40%pr7^KDx||QI1Lt~Uz+y{Q`D^D(-^j=Z;#bVdOMECUd_F-~ zKIcU=&NoTm`5+!C=r{b=;<@pgpM~w()xtg_2EDFKhP%-M*cpr2e(=oY;*Y$P(a$l2 zkq|G_8Gl^0rCzw5fMx)x81D9-z+*qpQ*cptTpF~`&E!N?C&&PWPN-*2CM<2S)5{bM zf8~7;y}{n4b77N>BR71DVKF1JrThH@wZze7wsMcGTIR*0rMT-~LQRCqdjZ`6{g!tl zJw*G3iv4T$3lO;{Nq{Ca{Y-*)GB?9FPMB^z0e+)VH!FfP_4Ix#x^C#-b;v!f-lBG~ z^GA(D?cbVc8u3{ZlY*fUdfBzl<*d|T%ohO@B%Nr4DV=JG)@=JIg)vLY1-~MZp_mUU z)mwXXf^qSBf8_4WJp&6daIYbqQ%tx!Ys_Ss)XTw{I1!j$@U1%W2 zxP#`1qOx)T1oEzdZNrXaKdC73{e7MHTXAeaz7)xZhRf#1tE6VVnrAeh9;sxFcztAV zBWdl_o3DajXz|Y-`zyL%>A2&Isw&UK(=bVX!}v_iZ=3Bpc7BpQpN*j~WL9itcL?kq zL3pafdX{2Xg){}NTEv$vu8pHtF1Ab7Qcl~ZmOXmg9yM)qrh)NdNOn)_Bj4D48r$UW z0J}V~vALbL5s>ZkEPRC$r3-;mK3HzSfep$R)K&Gx(q)H-Mkag2k)HK1I7GQQEIs+g zQ!rOyu4qXQwp);MSaav<-pKpgt|s4$ic3*V?$pVZXNm|)R?$r*}_kVk*xeEa$8 zPivHXK#Kb})yC(s-!ol_agIj!_F@dSLj0zPcQP^OHsX`%?;;4C+atME?cO~YX@90e z*6st(r*Aoet}SlsvfRJ@F)S~I$L_!ZH^{!it+CW}>wdg?j|d3(RZx^2*$+0t_^j>8 zKwkW^qN#nU=1S)Y2O(NH+0x^%iZ({>Z)j z>Ni}$K8n{18gfKG14N;*M1oJ~pI4)(WYalsJ-GTDeLu-!%}@p=CeEr%^#_e2frvby znSwwN9$3m*itzat(;)=>L2q~=U#%dCB{&L~L7X%?165dXAfEyYv=EoeY^=bmrYUJi zuDlL?x;@I=!wv5hT@TXr8+0|(OtgRefoit|9?WzS1nH3b`XMBShOgZ5dGE7D%%0j# zo)G%9-<8en%h+zKc|Ie|tcHT9Z|NM(`BiKrJp-M%JpDB@eELa&W~3iBHdLK??|kJt zh@|dfa&6FhX6|fb0L&~e<=dH9!~K;zCfMXRZJrmg2MB|ahU@GNXw{Zr5)NGbQ&QHg zU~J-|>b9d0wf!C5-^L{|EP8;~n&ZP+(#iE5X&%Rwj7$k2&1L_@(0W}iW-689yM&vn zI_)VVD0Nu7yV{TCoOoL%^#{^iaLzfFHC|j`_MsU2M&#c`@@Zr@LF8F|@_6&f%&6rb ziPrY6bZGCdp4Yh&g9Md&*JUg}()cALu5ENsK+&cA%j368X6r!RjskQEG5@DD9K!tfny5mc(^`!#ivOQ-Mypl`*! z3|6@-{d3rbNsF@q{Ed;AgS{fZM6URlV!J?wMy#k}(E8;Lt$ab6OSjIwX%1FB0a5c5 zdH0lHCh5(}z?k0&OXC>u3$uwZD^`=|L#6YI`KYN!>JG#C6ctLj!vr#f56&VCSI!on zX?eW&aAS*^54Ooctlt3Xkww?@c@;^+$gIn@_vJbogsM;GU_0S527txqXMZ@y%m*MV zv8x_1KY5=ZmkmWcwgaN;WhHhP%WVm#?Ix&xmtj%-h_1WFASM+vEsL5``kvf2rRJSpqK!~-E-{24PN(mXPLCJrF&GC zu0X3`2_MwJN^3l5kVZ~`NYu-8Wz@{I0rK#f-q`2f^qh2GU1TnGZJ4&ddquv>1cU(* zhbI7Z+Pd_VXJvRy4^j5P$>B!(WGmZCl3|qpb_LzW3-_8u-o!ehZ1bhKtc;Gh#lQ!w z&jrPTl9rV|>QTVvsp!np;aE!v?3=M~QIn?*PRlE4-k)`Pj`o0rBn-6D?v*yO+_x-t z{xJrzV4z9woT-#EtZ9(aQ+vbl2?a3R_El6{z@fqYNy$P%-dT0 zz{=tmeN8@9cAFXyQP?H*wfW148*aAT*^Tqs%U^TZjkhYRCMh0-^u6kXgnHC@i z5Hd*2{L9heGquWSW0x4T2!~71CvNR7iM zcoeFxckU_sZ{N{}QP8OUoq&>+S5XS4^GKB^(iO|Lg0KUxAVus!Gw z{q|djYFjvud3KPz!diT=Twectf%^Q}?RgmAx}6=TMl`w{)hgke~Pb=I*<}CY=*THRW9NaTtZKOSP&) zPG~^@rWVuQgFm;MNf4?!)&xBM7gx7MYq7@RA={A5W7iJzmXiGKIN{Tzls8ifym+cF zexoC_r}qmLJ{6d8zak`^&i?Ur;BIb~_efLRy}a|g*2PEf(y!Cr(aZhUK$VBl9({9g z52;&@0a2sH-@k>>103n&wXgWYhx$g-6mghGGN^1ZFCcuhh_yIFHfW7{j`(21#w0m7 zvyJYyNSA@D>`Z=L#2Lnc0U*C#x@FvK661$D!b?yQ(G*qsfqk*jA>7)EZGN_X>y&y1 z2%@NK%J%E?`e+0%pGFwXlue&tPw2R~hk`^FOOy`DFu;_$O-}zM3;Ba~R(ro18g@1lD z7pIC^aLV6Z$;m;;&Uli*&LNdE2gcoXv-I?5Sesp2qu*}n$7L7#$ki*H)jai1a}MWd zT`?Jo9MuwXs_S>7IU-P=PyvPQ*e~C#o~jOIpw~<~j2}n*13a~exPsn{o~Kf4ZSwkz znmZm#JDV1IcZFS9_Ci%o_@xdPl6H1Uoo&Z?t{$Bo+VbZ%Qx8Dh=o6m(O~|u=i0{V0 ziT5!+QnpnTKO4quQwBuuYspdkyY0%+aqBvZ6Gb#Zzt{d>|cP9Y1aMAr){1?i^f^PH(uJD2W{+c2*1rt#qN|Y z0}svYY{di%kKSLEb`4k4K0H~{4~bmW+6_>$x>=mb( zLc?f4$IC>yGlRuGyy{b=xju_Brx28w=@z+H3ODbTF5yOh1>RgQ_GHUfI-{bF z5gfB9ozX3tw3BCG`?k}o*ZOt*ouP9Z2@)=;loEeKM=s9%VQqT?ySw}Ijx@cStxx0k ztV{Vt3;V~c^3*k9;7`QcE*-IrOn62M-1wnyx9Rk8>)o=-BP-I!DUBoS;mp#K1bMha zYpLi{j9=2^*N7q8j=`sXh&G;_FY0*3+`y(zchUx~J7K~!U7wIln}>Sdn{p%hSG~Z~ z4We|(4n+18av?G9>R_~;*pVlH^PI?Lm9!I4LX%vQ(3-QE^-TZ$gC0f6O!wQ?V5{pa~JheHkTSEri*HSiFX-{Dr`jn^J{` zHn7X>cC)L%(_X&}m^;cH<7a258V2^>$vLl#r}yD&mfS>^JkMR*q* zMSi$2Al5oh82DxM7QfOV&q7P6!MR;&NJy!%whj3J2-@{%%zh&Ej?*PEQ>%@O#w_5H zG1ZT<)_zOgI6?aPTp%%_)Z$&71&VPl%aIvfeXYur3rg zhpK%19rAMeq<8ow?r`l17fi|6{>eLL;gawo&Q{qrlgyf}oV=X*;%AO?wE9AybZENn zGH%#Pfpmij2dz^#p$?wiZcxTfo>X;j zn7FlICD$oiJ=zBp&yQF)CiMakyUe&9O!dd5R*+#(D7xfFm##gJl4Q!^( zxvB{rLJ+MUjyoP;&?Ym1ZTrb1+2>g=4$2dlQHvD+hmGokewG>6d4^_maeY{H`ku@_ zIc#gP7qY}{e3qZMOat<1@V>QX0i19C521!?%PTAs0Mmy z$B^(mg{`r>oLFgjPqy6N?;R!zU(W;J=Tpy#qu0p($#7CQHj{0*Tm`dkDZJeADev2y zv$dtcuOzRuWJAATs4ODWGAxV$+P1LI=mQcGi;it~Yu?7zzk+OrFyXyr9K4qTJRC8M znZveM+yV%{Qia#X`_zn&H{>P0c8=L}obiSiC0sE20`~dQ`=Ja33lLiXKa0*sgjeen z4`do5i|BpA5CxJ#V8!0hQ8VX(dKnUgl^?1CMaP6af+ZkPsU`zyhGM`yGLH$)%8}W zS=57o;yJi(vgJ{8)B|GbjiLUw=*1D`f@vxsF7>KBp0qk4BWIvGae{u+b~E+|yYcT3 z#9wvN2VhRZPOQ`gSgf2CEXnw=Yg0Ns@pM+%7)GPEfQaG&>`xq>2GWfy2eo5opYv(P zX_?*or?#*W(~ixei{hczslo@}k$wF(420(8 zUYz+CAKcA2Ea<5}%RQeHL|$2U{xN}*BFq$yAgW*9)E(J7oOSjF;TTBD*V>R^Hfzme zIp->@HWaZdB@9~^=qM{6U-j_9X= z_fSe*EuUz7DR;6>6@5eo;bF!-ceB~v9v*?3eDPPdg20d68jT`Z!L|2Na-q!Fm0+^* z_TKLV_3%pNM`*^FdZHf1xbrO_Tz~YCxJfU_W{P#WH~1Ajm<6=BP#$s`Oq+x*;tzp~eWpEX9n%a@^+!Uq<>hxF7vgffF`)UPc?!Y)eT$wW`` zY#a8Qw}#dC4AMtMOaGD(7vrd@HmYt|iclNnBKWE!XdJfhvGxuL>`fJHx0|pbHJokh zeV-=#dr@UW{P8|UP7YIPOl;1T#2a!~d^zU3!XoZ-Piu?Ge5}&4U>2;%wCHanK1BhI z$*=}s9I(kKo;n0V2(F637A{__MNBA|5Js+&k5`>Qs6Rs?#TEwt{b6xv4Dao-21%EY zSII*^|I0Bk${3*VXbdi8vP$#zz*C1Kw19q=UCGs}E)+RZDSxR;z=+4yVB-UoF_*j4S z$2bHHyKNA4ZvnCsP?lkaI%t{G%SC&a{Qvry{o~+Cz!;#q;@;ZSzIs@r(%$Usz(?^T z+@uJ}UlbbbCnTxL>MM$hKU}?4k&M6Bs6zgrs(DS{ET#=P!!#(pP>w(U)<}$KH0=u= z2QBx9`P_I$_T2Jhdt2RdX-e#?J>WD9Z{gGsP!}~)bmq^DNkZM;8Ot=_A&0${#b$vgP+W4Vw4d$3?bR&S62TzcUqL3jbD1Dz=YyyExRi2kqLvPXuj)$4; z+1B4nviRa=HB%DxnIT*ec!I7@;mNdWP(@imqUcf7=p9QFa&3=;?@9c0jcJMt8lAGM zE)V3A*XA;`k(nC_{7`5IlYuj@ECu?wjG#E-KSPSX!gBGu4}-3IpUDxK08HPp{f}o; zvB+OI5mMoxj67x-c%TFA(=!Fs@jXfCKZ%dntx!Y%B|=5k|Ch$Nk}7oUcn8AhuAKXq zw8W>Q{rLiu)K!}ZUm@>OCeMos?5~O3e05BbNN;zb#A1%p_2G3qIwLI_<%6qoh!dyy z)<364%+zwj?Wgbgcz+UqZegtxS5w;gg}F^&`-HhaI^M1$=Mk9gNdRI5D&-x-@_G( zQz9O2%aq97i8COF>EG9|NZ+CDR^Vh0K5xY|){!q;pgv0%R3VJRd|srL$>D3Xfc<*m zfh5^v9HJd(cK6GfXZKg;&eXD3x$!g0oYsp}foO*>rM(2layN+7DP14rzK^1j&?qj; zHF!Xc<4pdG>GNL-&PvI%1|C81A{g>5kWDm#qIqsD+-i$ZQe4vq8^|nA>!KUDpcUTH6kvt)SSpbFe7y)CiTcrJLk`nsh6)1nE`>w>dfGv*4-;E&x1aH zQF4i%7nV7>LGsmGCV|{IKLfz7^k9So=x=!AyTU);_n{-46YeAg5A=tZLL=`}CMB;0 zx#(O(rWw5JRm2eg%-3vwb&9oRbXSJA2ft!@IrYgSO8qX(l-E9+6xKgyqEG!9xzwCM z5j4|OF4LmJ!TD`K#^T%x%_~IPjpzxhh=wDFkDRHGOcJ$rn8kijjP;5ESFC^0Jqh71|02BWvvZ2saEzYIzK5|D3?jAP#PO_8d=8KYYMkSrY4 zj!EbExhwN`JXRXIbOi$2;CNS7+6tacQ~{~nH!~QUKojKp{q5vfTZx2MB(1JS&xchk z(&|v8Yud&=W)aJaPGls4jVeU^uvJFzs+Of1$gDE1yGt8uD1pJUNwTZiRo*!VQ&J^v zng1CZj^I-r4J^BMZRvPyQkf3eQC*kCYrJwqQ^#QmKC$dFh9``>v3~^FYn?<0()W)3!Ik2l`u+1US1}p> zjot_V&GpIZuA#qrIHVF|&!ci=wEj!VoUaXuz!Z}pF(|CYM~fB>R;oJ}z#f**fIL&4 zLMs;1$or}wz0O9^h{mI*B`~Pb?m z4-DpL(Ur2}CczZ$#xvu{_1hjgRX7N|vm6azmKqvL-CJzqxCT-+BqaqP?-La!)BC5j z$r>w1>Mva=()bQ#bi^N?0H0=PL9qzgW!8LOs{OG(=Z8l@p{^B#L&DU8j*kTE$E1J$ zl_)BJn>trSSdrkque`uZYo{?_NVt{8ig(`K8n(EN*L^Saeep&X<{{?mMcjE(7 z8-T=Q7615?1`6yghT^g7U`=Z1mu~bx2G8B$+mqpkJAzw$2YIb;{~ib6!efxL*+VSW z;C-3ZkUjF4?S(w21D;G6NBuKuhQQnRma8=MTRf2+$Wv=3SCBfw{h!v}Ef}l2Y89%T zZ+|k@h#qctf}Da&9%>&izCi~*%Y3F@C~Pv`UFpm}U{RN{U4o99o5`;w*S&QqFdr@* zm);*g^8Jkf<(7MB?$YzdKZ!2~x)@9I`(3opIwyBDXNh=-v5c?|NZ;UE-^@9WgUYbV z4TCuTJQcFv2H-J4^f^H;mGisP+}NqJF{e>ZyB}mw0B1pyqp?OJ1OPo!TV6o?xd8O$ z4~H+4!H=M3eb0SY0}U)zT<_C_5V*FeDBCT4`nf48>g>qeZZ3CFF>hyibYdA*pY3%L zJ2xV^S6-7qPkswG7CgwYlO1SBXp!Qt8Q*;HA(f+zl<#-zWT@K*e_+_ z%s(ZRO@<2{!eoC0FFd-@_jGXZ&UH<09cy#le_FG&PdM6`2hYg2akm+trPwdDtl;-tl=Wu(wA?gJ8LA6Yk&lRIX1H^DePcya zQ>k3uOxjl*o1f`QqRb;)CM7YmQBM#HH*n_Xnd=h}bNgYLR}OOSTT-s4ZZu2&N)Ufq zT;8-7b39nU563-x2z%hqtHK!QD790uVX7+@Z|;nKFrsc^^6XWRB;PJw?87#KpHeJ* zsgl@c`}YB!1s4~dZ_GnuZJC;05uZ;uO$=Idr{Krb{9v?ZnouTbTsKUMK`{h#y;cse!79KY(LEMRH;iN+Rv>h&M+lv z4NaJ#_1UR8y-$^? ztVbri1I2{x^WDn^_?gYwT9m%<`o|YzQ(q*`D74y$Q##1T)0Bv_WaS=zHXK*2(|~9i zZ2jMt@PPv+H{Su{3KP|_mU2W`8Y$g*fCLAP1sCeJ`1#Xo@Doq@x7t-nCL3B8Y<jp$fT0 zXO$AR1dfG&POeFF_mU|I-A=+Uo0l+K(LPzL=kD-d?F({dje3toE)DRyq7J$_8`PRa zd@Bkh)BX+hb&-3;-7pUeelpa;$}U5otSq>uMn&^WV=8YjpO+l!IJw{az2oOnk=QhW zBCd6S<)scmYO}Zf2TK&p{f^ng$137E`Zl;zR6sa^jRdj z4icuOevj`>_)z9We4L*=KM>wTsdnV^dJBkv)DL4()$}TSbdg5wmM>~ED**>}?!xAZ z(YOz{8bY$RCp%Cnzf=MPEwF(OQlN4o$nv!LWiX+g9pJ0#3%9(B!}dCAr!>%F|D|Mn@4uhF=_W>}#2e z*Wq-f7j&i01^@gv-p6eY{^0p87~HENQA*ff&i?Af?Jp&fv%y2QS1LD-K*J!V)3+q< z(iZNTH8VlzH<1MLpNrCJj=w#+tNTF8NA~g-Zm8%Q7 zPmiYfsRV7vneL;&LrX4-4@YnzM=AWV+0^jGJWA>619S% z5*~UfjMSbjF6t9DHucH`zraCg>p}Xt(4*h4(EmAsyauw!T=r6;NxV$<3s8(B6ci%% zvn=lHQHidzjo6N2HS)j2BngA1hf^Xt7U$9vDHD4>{pYckx0z)PjWrJ*yFBOR4v0Z) zdcIp_7j;S*3#4O#f!DS1Z)6b)7ks8dJc^gm>|e6FZSUPb67iewpB36eP(iVt}-r3k( zt0JcWf^}I&1#EwqoUQX)254_WnlAx1FmM)Bcx>#z|{w3}?VNWL>Xb$xM33_!GyRKbA#}o!83u$k`>Pb*& z@5$gOV(D9r3-auD)DB{kR?Vd|FZa1z9jG}9?0Sl{;DTAamq*^ZmI6Iq^?dLQgy0ZO z`$1%<#A|}KqyU-z?&}x-0Zz-lm+Lukw_SzABq7qRZ?TM$u6QS?SUlVf+P^RM`tly% zm9gKx*WYEN1sKuC{guKuRTXEtY@9kpUi;WUR2BhA6|Tlr%)2=x!By5kq%o4w0*0sG zumbyVE-qeeVPkw~jF;p2nEF&?+n3;|zY#){@_o?nh3Pg zHCYm7z;M^_3liwpXM#LP($D4?>bm=) zLPT%6mBb>D{&fr(ad3eTDncnMNmTW^imv~%cYMx+9bZ(&?NzJQ0D}UsJ>Okr^p@0- z$>rW+6Zo+Q+>d$YO5sy;b5Ro8lt4bP;(hG#M98Ua9kt!tA@mWMM^C51B(nYd_-K-I zx8F755?)?>ah6g}#h9@7zCbn>!6nO5;xDjah~h9+OQL2=koyWn(f?fDSGzC_wy|V9L8NJCSWJ1^}+%BH=g&a za6Ii76n+n&viB~E4FdiP@W@7MJ3rUj_&2k~YfoO0C^{IA6<$xg6Z1g$84&F2?R7)Q zPWQV^Zodet8sFvt0oRTM%^5G#WapapUF9~)w&sl+m|_BNSjn>M)CZg+B+aJ?)N(K> zouK(xGQT8{+aPVr@P$@Xfs0YjDwWVi|C7;DZ&ts5Lf&!P00*pYaL#S~!P>M+$iZ`2 z|12J)oSuIWaa1rxie$CByCfrEuq?HM?yzN|$^x#)4Zzd4CF`IOqe zsSJ>=EEW;(kf?G~l$TtN5GxbGzRwti7@H@o&BZ8T#{4+vJ;F~uCA8^_JCpj0-e67E zDedYUngLR-LYC*$B=&C4mHKQ-&gDwepyXLZ-=_p>A@Vw(o6I__h~BsDkTWGm1g7=Q z2C}VpTHM2sCwxtp_ZkWr+o;3c=k}4Ixb#0R^}Quz2fjs(%Ps9Zok>pdsPmV4B4g($ z?EV;9ao#%xJED(Bdy^1IK_In&?wAL9^c^eX=wY^3wzzks;EG8wsjl3t#xU5JmNf1W z%K5RC;GYa~hSgqCBm|RGS{vYGDik5|p^adRDNo*8f4Ng;>F6?rQ+iJuoGD4SPu$WR zD*GU_H;|Cu)cwbE8$)B>oe{&nrd5nCuZBjK9yUGI+bWKLa{3{(*RGwTcPI z-c3fY!X29p;|w-%S1i8#Vbyfu*1M;Tf>pg(*j?1Jo%A@?E!DF}>JXmM;6h#_S}8`h z9&@D14+asB2^fG0Ekts!<{J;PVdd z#)NU9vZ|n48@!Ea<892J@rcAHH2XdpZtqR{04jj`!Xw;QiTMZtC7qjdj4{uyZRL?~ zKek$8uO@e~>#9eP=X$=u-m~%$`i_MF9SrM-f|`Sx3)o4i3*T{jlV{Tyv&=Zo3?o;j zJr9FZhEtjZbXTHl%86Z+QW4?of%~EKyf4bANJ*j?o>c@?$P7Oth3r2@rN0GbB~v}3 zmpBC)uMohR7if!Pi-G zlB?jnk4!PQXz{pt2T&_dJx88Txw*pJ7n|m4e3!c25B5x$`ixVU6$%f6#7<-f{y}c3 z%3eB<8ufxMRfYSzEoBcG5i7R@8wsONl`lIiS5W-QZ2aK_ul_d%PPnUw4#>&j?5j3S z@VoTS>JJ<2(w-)VH=ZlX(^<&mQhmlbQEic8t1(D56)#+K%J}+?szucx@?#&hJRh}< z%jM92CwJeHhga!=u_KxJr^_*gATeMjcWS3fWj^QyQG$Mduv$e)ht77C5R>`xEei@8 z8~2|~E8hO&Cv*`a+HLg7U@YR7+KpMv%6e1Lj2O-aDzeoJjSSVI3*hDC<2WlUhlc}Q+ZUV=lk}^D9t^J=pAV)E zU9(dvS~*e|i7t(q zkmTK%P%8du*uZQsfm0;AV-j_c15?%?0w&03betJQ{LCuEAyw2i4Ax)Nt*z8C|mGMV3W^`}S#Ox)XGjBU`uj z-@&Th{4D9hBcp0K)=Z36J;a5d|CrZ#V1g24^cx5i|SvwirSQFU>X z>#ZY?1zO>mP`vvf4ap7M$_r^wn^c>3A4i(7Fseu$u%jkQcb;w8$e|?;O(SEE}zjZWwJEm@gB(wQI8L zhB;WL0HrzFxjzsm32Y@~s6WbF>{Rt6x9NDc2X#9I@Ks4vm%@p7Z@f7RLLS=JL>xdg z3u{8NM-%l&9Mta{XdrE`u#SpLGgoIdYRbV@2Fm-NJ#cgLb;mF^&w)Mf(rlwKHQWiQ zUv?_ant)*IS+pRvs+jNCzdwxN4yriS{w%zpAY0qH>PVbvxt+lrj(T2dFvp*=b8iFO zS%q3@r{LH}f7tV%< ze3Fa0m$p}LwBxrW*5ldwQ&-giva(eFnW?5PIGfndc*}@MX&)1GOZ*p_Z10kc}B8J2Xn>6 z+szy#%N#^T%o&MqxQhBZUnF(T_be+6G-i5jE0($kroAUArLy?fmN1hLZG1&gvzo#7 z>y`dVAugRVf#EGbkL2Q~Mvm`{8KEXa&}_BRB~_Pl30&_7YWSXu`Bl3#CiO6&J|F(+ zg*PIvczjr`Zp@G`5UG-BM{;6ay(H_FFTHtVXsa)ru_X5>8oo(7F$@Mg>wEOqMF);8 zaG)4ZeS)mAEj7!TDTq+LyS?$6zBb>tx@mI*>^E?#qGWuwXUw7{VXo$0+daXNjBf7o zZ4``+h{XH5K93Gwq|G}!%PXAM=2Ha!xb;dtTIFU?isAI2wOgp(i)z?1c$sg(Yu7L* zIWFuh;8APabh&d0?^UdBh;3ZeKSRN%cVAaCcD#B?pHZ$>G*p@VT5>kq_5qC1`bCk} ziq1`s9BV8^Db>Nbbi1KDyr(I*9=@M%;MV>%T!av}FCW_=ebqIF-i0&`V>er8z&!Sc zR2m%<+)<#!R@(tO-wgs%XlwS}J2Hz06z8W|Ay-UOp-G>(1=rPSlb0AqTgbFW9`sjH z>&B;4>n3`ItfM;9%E_AhohPtm*luKvbRY!rQ5l>G{gsAhhW$Phk0^P{g?M^i zU40TTK_B6glI|c@hKVwaQErB)&V9psl8V#ErZ>G_B?61ghH(ua01OnE(Joz}@XK3z z4?l6aT_n5H?n-ec67>dD_%Hi}$aEBuiMcwiyat{|y(!2cXZgI%1LC(Qj)%-&VU9-Q zmsPVA!HX-1*qzuFhUeJ!!}gl+B`E>(dx~a?3!TJDqwrapAZ5i zlT4U6{t#vN`9x%ACVC*j`X1BWJao2o9Z0t}xx<&-og8w^j4C)V z9HMI*c{^|wq8zbo6VPpVczsofm|*wx>yG`#HPVE&^uLVXULppychCBU57@bIY+CJ+ zNp#7!z8`ctd)MiV6;)T$_}Ep}HK5=5D$?G8n$UUlC3*8eWs28YO3dLc?2tV1ffNg#^u&A|pW)ml zV!6t^9@sZ?bE{!hKH+%Z_2AI^K)=*-<6G<~8JD!&W(UhH*SCyH^b;9D@toN3D)EHY!$DL-Eqwx)=$Y!B7xO-clIH{hr%jYq@Qroh zF(^;xi3*$@NW3guPOM1X@(PVzzH5L%`maMESOB)03B>NGvI*{wF2!}f=3E-8qh&iscL$D%)5=Rb^UKGl%e0Bg%5IQ2=? z1b;y%(Q!o2o9D-Yp zOd?^ME)))_Q9+K#;KgS2gc2U0hT{hRpD4Rv%2^Z5M^{)fu91Vfcqm04ev9iNs` zSiu*oC7xzd9pqf<9o8IclBc>bADa_sgb&irb?kk#K0@)yj|V6JqSX&0uFwjvPto*} zn>4NGhq!#d!XAkjY|NV?X@Cd!ja|>OZ9b z%zi9asfv5Y!|`kIMm*;^oaE!jQ7xA!}DIQiYzlS zH=`duN;I7Q;AzoEIhRL`x39ZVag@(DpH_^y1UHKNedJ5BAH`g{jsFvRov~l$eya*N zTg*W3B3AbE>DS{^mShe0@VCz)$3&dt!_j~HaG%}WSbo5}RPnfS|%^ z#IcC`_oyFL2LiWUa1X?Zwk|A>q^~GL-**+`J__W;J~=&DM4BMU_Vu8?Zf;~qs>rG+ zAqFp*^eWzMRevSb2GI9&%vFO9YuqZ`d0{q0wVc1|0+~FAtj!IB87D{be=fD59Z^L>%=;Vs0L&HU9 zrskrChp4VoLSaS~3v9M(H^9%m*6V>~QMX#@^Mv@3;nWqg_Ux3fTfPN%km9WJ{ZTg{;^R~)FyN4hB719)sBMoxjBFI`2ORh zQ88HVO%Z+np?0v)J)9OdEN8-KBxq=io*HLqPhh z#~f<){?=aH@E~?lzvx^o zLYziSdOcb%?1y(-6arS9jgPjG%8BpdL%AN<a~zFg&+Y~5#8o0K7Ot=|k(-Rvy^ zaSxDw;#M-{iCD_G41bN{2;}8W^S<1}pvEV`eW_~&{m5926>IgQ09}na(dtWch2JED z@O^Rf26Krm_p_CTo$7SWF>_6igt7>)dwDUN|FA&acp-1)k9t8 z4CI*)9YQhICosk#+}9%ZiLonIcPwnmI?gz8rkin5{kL}FmRhYvQQh@dxW@kn@TRLc zt=F1QUspi>IV!`F;fY8xZG66y*Iq>7=_s#~%#G(}*ZkBbk1SL19LUfEwmZ-Z^9gzAYuSeLFYFSCqm}+(2MAX zdOcg{r)Ax65(<9mFi-wyDi;6m^3u|QGIvyKvj%tsFCZsWP{)|7yD>Ha={kNE$_MH2 z6E18EL1qMsQ{b>pFg1aGG1@m7+TU*UZ}ryEH_`G(PpcTi!Px|#!5kWKA)@9+HA7-2 zG+IEj{UZuJaDp51idI}H&z->{C4n@i>7&$A(p9-9SqS7O=AL3hSd}jipEck3y;QI8 zM+wWifPE3L8*I||I_&+=k!K2}g(7A%?#!lmaF*Jtetv2-VL=*-qD?J{?m+vmoK^+| zQOWh|-=Lq0IF0D9qLN)P#zlc_B(1?nkS%NcbN7=f$vA++p!x1U7sM8%B{I8%~^1+ z9f1}%W_Uu><^?zoX#$}2d&Bp%-sm*|9;)Nnf<00B<;N7|+|$=z<}a}j$$zOQnrh!Y zlik}xDC^|wRP(C+EcUbMcaN=!xPGyaGf8aq5Vw4*)=-E8?_;hO- zT&1c_ZpP=1j>aiI`M4(J8GAjC*x_|R99C_y3Q!%3k$4Vx28iI>HEfd(RH@-z{jQ_l zrnHUS?63RXecfG03 zwq~VF;P0|f-kN&*aVdTAlY__Wh})RIp(g+a0&XenY3gjqohzpL&cU}xVEu!{lY>fZ znD)yZ=OHx-LM@Sv&2EmFH?TWYVjtg5yWUUtE_&I{n>8~3X$5uM6@ah_G$NwtbKY}! zEt?yGauWJoswH8{->U?{UI5vko`pKXoGR%tQx$$&KCBlDIiVVO5ral;CB}Wyn_wHM zD!8~Qd3QLUYW)GnFio!FIXb(kWXX0w8n^UE5=b9z`k;A~$2`QF-LsH2ts%L}WYFom zV6!AwMc#+-iE_&SB{Kl$`!jvQR`4#c%DiwiW|CBdsQ`&?#Dv>v7{}J zD+-9ae}~h*k1WVS3{?aO9E6tJgx{CE@0_#IQh4=)k4>CmI<`4YtujcpfF@Sw4Y2qH zVLz#WV8Sv+0u( z@QfLT^6ffEnb>{4Rj~QwDDNUmTH{7Ve5$np7;XPeGU{VwV@km5zC=z!w!36>eXCC* z8#C;Cbqi-pqO}V2Z|W23Y4gpYSR1Nr(n=;vsZi=ZRBnv1jX&7F!v5m^Qi|S{Ciq4X zFZ_*77t^6?I6UWIagLEqj(IA@M?{R$KBn0zAD}X@R=??Uhd;JQr3tnt zNcc8zyt<19H+(st74S7RfV6gnEd|kOx=P<5ojQ$UI=+|-ysy^tKHp+}1}=W`s)JPX zC!M(zs;_^pG(@08d~ADcafOK)Rtk9uh1Rxjc_lGl7oLb$6GPB%nLX>ZY7Be-s~mT6 zs1$g~`q9An*psu)$RG+Hm=*SwuxjzJ0Y~ELckB+jX;2yjF zRDdCQmwW@jdW@y2`F46`&3s#!oy9_q{RcDRE2DkBklUMaC+-MjDx(DozpTM#To6f zsX&9(ON?Ws&)1yh)S8wvivP}OgYcq@z$y%!?wuFTasZCGy7I!MW8fB`Elc2o@nnJX zhB#gsUOR0liDobV0TWvOIz4(C^OA0@p!!Wo#y_!mbV-sg3bUxcw;#sKy0)II5-f*k z4*2>;Hgl1_ab8-Am&nBGOBwOB-YymYdL6?0GK}aW>830_cn1PZ5-}{PBMN+&F6YOi z2Ninaw^gqX)*6~82&)4u) zjHvACk$5@%lJ7a52aCrY!4aiZ&uGysmA(5p+Vk)_3{E5&GA}=a9S|sxnABIaRul?{+Ni$>%Rnt&w_~}Ua?%|F&^j9 zN{qB1`<&($zbf-MZA_c^I4MD71kYs%-d<#K5?Zhc6jLD3145Q~j>muTl$enfmXm9! zC*&R$ClKpFr-NdoOAe^Q_I_wTY13%3Xr?QjNXV^}@KI_=aj&J3+*wn)QXPONW5@55z z?shy0(=eL=E258UrQSHim6n`Gjcg7&_)JtXXuQ1o5vc3v5dCT%*s zS{7C3{e?`@_)K=gwCFM6oWO&l!5LL~mrQ)BGi;Rn2QVvSizvE#K23B)Py5>OAWI4Q zUSAv5J)p`11ML`zo6uaHTxVC;>Ux1%t&3fKBr5-_AVbmQVo*YsP%y0 z&JA6jaTSjqVyMJsk(^rA-O3sMWY=hj@hJrxWsZcp18MH%Z@ww>)c6?hZXg<&&%i$* zXHIZfrXfqcK=Rph?tJ53@{025lz9#ZYb>5}$fSKt&a4=0^A(xB4jKEH~Y$wg_6C>*oL-`55xGr-Mhi5>}tTD-C2gRg4dz zfSGe2)q7MPpMCmQ$JpPF*W<6F3>zl%npU1)Ayf}M^EI|NMum4hTd#W^VHKUcQtp3R z74;$;TYfgCUN+|I%{bz4)f?Q1_Sy;<%Rd(;zG!R^zhL^dYNhgA@KI7s%yDelY4GIV z?d5jA%ll)O8n++Qq+y(#rNO`u!RP9JY3|XZYr|Ik;rEGzafdb02q!~_6tzu0<6Ww% ziySKGCSupcykXbfp=`zEVJz$SU`sl|X%S+@uXed#?xoSjZPumkV;;0TS|+h+)9WAwh$Qq^TfnuEP>KBjO+e6HqdytTS|hZ*>o^z{7kNHt)&`APc5ZbQNz{&c3gW`N&8tu>6RlWfm|BglkD+@EP65U*1Cbli2J!>PemZ;rI|3j0> zdBJkZJ8-ya4b$+6l9yW0sKhY$1mC_ldZ}LYck=@U^|IZ6vlCRTa+QRz!N4$lLVjjW zkq_+*-9P_^yp?v6L*J)=I9?v}bX8}zqZQwu`d#QFIsIZ|G?_D*t#yV!Erst&z?>GY zf@N)(N9gBH?utw76vR@4T25P#S`n0xNLF)K2siWBwuzGf_&LdXz4leEGD|#1KuAws zlq;|e^~==k;2GC2b<`C9t%G44{8O^idt?ur(nOi7gYQ_7nukO%JT1j*!e)7b#T~ch z^e?x2t0|MFm!29J3~bC>Bj+!c-2@S2!AIl~%r-bT*Ljb{^fR0uvkq>OhFH|)F+QVg zjjJDH?JAxRD!ioXOKb*W&a`K&so7ZNS&~}3Y?9GxQu^;=B0UIB94MC|R*A$6-NxYb6!-vCf2IUIY}$LZ zgOZn{AI|1dT^smLyvGp)^Ams@S{E;4CR`R`6gWryQ;~Frmx1NauB)Dl3JdD+uLAgK&u1L z>08zG2P?IZP;6sOe!0EHQ)~Ij6g=&AYaiswb9WQGf1?Xd=_jNR$M= z1*;h@?ze~oVX3P(=Vi#m^&SZQMe5vFSaqiV2}!oVw5k1oFtgp%1fCN}K*E z9-jT#tDp2iG5_nI=WF8qBxP$ija#qVNR=+x!%a+l;~URjkI#FYs0dTOw8j0H_yE*P zv(%r!N)5cfWTdgG&vPI6-{bwzopa0#qx!F*z3*Wh`ekfc=QM`g;|;IDh8bk4q``Bw zrJ*_!Lr`h>lwj!CjuRRffG-S@vG-SoKQ}xsW@f_*+xeIljh z!J^c%-7E8pJL5Qqh)01b&TaHjq2!#K|4y_MmDTbPcLrWdGxQL&z?KJQ)}ER46qfxf zJ$wp2Z-HwAdj2PD&aG)4A>Ui#f@GN+Pyu(U9mwBYiFC0fVyY7B(QW|rtPZIxbnIW0 zZz4FI4YEdP>;HsdQ*-hxt#>b|ltLt)vDa!=-xtsolK8>bZxA0PkH8&FbA07`9kBY> zA=zV?79^4jbjJF$2HmRU!`FX3DD`cf%;g7s^(wx`I^X`!GxB@5gEf%msQQAj#uLOp zMm!u8xdZp#vmW+b=-AB@VGP`d*0BdjBrU{JiA1u+p0vEUUD!EevLXLTY$}E|UOIdV z2(VgKjQmA1V4W(jyj*K+IOykEc~Qd@E>U^e={XCWAZ?MY$GfGHGuRI3p6t1TNBHS1Zm7*$TwHnmtH4KiQDkuU=+m=%*i+pg@LvjH%Z{XQM1`F{@we>%sqW7d+uxd^DY9%o0RH|-C$6w`2Gt76UXj8s+N3fCP z%jqL$$^B4-Xr@%su!ven2yl-;>$vr<)FbArv>@roF_5NsUIBlDPmQ#|H(josV%(UXHUgQty+2fL{#{N&Fk3>l_nY!0rgaLzsSuKHhT+9j; z0wbg*RI0uEHx`eV_3`kPnScBTd;%8+C8m%^tVpZ%Dtu{^)HncN7Cet_KrHCd!2qU~(`8|wT#2gcxSUG<|+mgE>DvBKv%9|H? z%~DC#hge`%g%RQ@)f~i1Ff6KCXup1hUcf(w9|lGkqvuDyIAD(&v)mm7bX zCjFB@DOW0dXbo}^CZVz>^627K9uRiQuwR8dus@rDl&Re?8nkl91NjZpb&qLs{rc!> zg-iJwMEiWccd*4Z8Y5vl|k;-umcSJwI)1SAR6rh|V~GzQ*^eg#pLKMtS$ z=3BL;{6k@x_&R;fAkK*oPVxdkVw#`KO(3SBSDE7TM>oK(ie>5@jsCagNZ-%QEtNmfH$(uoBPa=G4JNygawfpyPOx zE@$UB6TApS;Eu?gv+4@?wT+VMDrV~{~(JxC$%#( zpehQK0)Mm7g}Bz%xsP6jP}@qwVwK3lOT`_LBisZiS^%H(f#T%iKhvEzFcJNXwIv?E zyB8d3=lqQu;s*X6c)a^r^uz!1HuXe&uJwKW&vhjnsVyd)d%jB(aZ8fwY=Rah9|IMe zUf@Ry$A5SI?YsD61KR~XkgPejf`=+xeq?)o7q#K^8~2N)akc`!44UJkuT{E{%A74B-BPtpQfqjXZ%puB?`bmveuh?_bCNzCM<2NDv|Mt~s+M%NQFCj;$ zQ=l5KM$u;^5wEJg+PBixYx#O2QZS*PddT%MwF)Sb=Fm=Rd4n87a-MxJ0vj)hnX#rEFWX zPI-68S^Emad#LCfN8|iB5zjEh1C(}U{vLnK)@eEG$eeg>51+kZsikI$aO=WK=cN#f zdN3qC5BUxQGL!Wks049>yFJACpAuPp%U9NIuTIjG^d}m_;+b#ysE}P)&`H# zFo?1hu2t*5w9uQ18UC)|hLz}r>nkC|d5pqRo*=q<^Y&ZYDv*6^z|!?Y{h$B1E*6q< ziQ1}$Ec~+nOw38;_?KHG{l1&GkeT?nRZ`V8<4;FitKt32=5Yl*Xsg3`ub}wc{Jd|e z@LS`9gr;vX!AxVcO&ymm>#6l=#CkvswC%HJ+ArS2uxS-#4v;0f!hS6JTFaVgDdN{z>JO8? zV|}&jUe@>eYE{fDzab#`UV`COx|dUmIUEpUQ0Y<(_4?d+$;aykH9;Q1 zDWPG;#k=qn*vjH76flh)utuz;j{y{5Ho~oXFO2c7k;6KLR^`gygjm}O!^|_T|Dvr* zrUtcD6IzF&9~WVJ`ablj2KzTa{*y&TKxy&~kTHDo_MJPjfu(<|QX(v>nw8k`rkhI$ z?tJ}j=z?D}~pfkuhNO1AHd(tm&gKT+C&V>r?`Y<=s+b&6V-a6 zJMJKX!%RK?_0#Bgj6w}vkBFL+j_P`Hx(jaEoQuSBZ@Ydp$5Y5J{`_0Tvqe{X;`^RW zD!7?x*)7H1@K9=!j;npZBppvcaESlC+x<>8m4%1Cj{Nx)cih$je~sHi-H;5hJ8Inz zFEy2qGmv6H8MF3krfvc!D(y9vMe9*s2S@hxc1Xu^9sA2kM`VH!Z< z7L0$KYwrIZtA%go&U>U|&?w1?DYb5uP_ZzIsuIcHY19as2z7haz`iT+-;IXu;%S}M z@B`y$?TeUInHk=dh`?Xp?S=>fF3{j^tL-GX4jwDrILJh=1A>24YC= zkiLJw6cQ6dZ}^8W_ZOARn%YOck*ap?kHs@3^!#c0dg|6o@wZm4z7daCy32TPWpwlr zL=Zp+8IF@V_dJG20E^0{1)^U3T8Jv5vds$k-B=I*rEZE~c)tp3Opp5_mi~u3ZOgG7 z$r04<-sgWwXensdB2zg%TleH&mA`i|a;Z^bQHyMBH8f?MmZ!tSZj|$N=Cvqv$-WCpaQHub_}sHEd@Y@^KYZ=qtoWJ7 zKP8}k0=75bG=Uqh8Q4y7f|bTMHE1Kpb(`wFcCpEONi*ijZ{D-2GyQQWF|tZgCn0MK z^WAv66BIsS{IZiuk*P@SYgnKWUxjUP+~;^mxP~IhO2Ft*>Vr`RGuxl-77qv%a*51YLSNNz%MlPDn#hes0f$V%&(` z_9FK`=?Q2?80Dzy-(taG@^D<{Wc{mNM+w5R2ZRDLQ)a3RHDtk8%8L~G72Fwgs3c8%5Lmr8%vgC zAB=SvgIUbXdB*4adtT4;@AK#L&%Ea4+|GT@J?Fgd>wR7C1qwiF>YYirh6dY3T^2iA z4YS2akAc{ZTL{X>2Ty6?6ZP%4b@!^Oxc14^$`e(cce@*b@`= zT(cP&M!i_Qn6RG{L-5U4Texyy>J{#xa~F~_!CGNdkXhoeg2c`x_wBuTfgqc)m^Rd6 z$w(~x%?Ev;Q}Cm3lZk};r)%aS``ik~AMbp0=^zHv&H16F2d^c$og3N9T<6^{J~~>k zL#+2P4Xr+h{g?MKCcnNaBfl9aL0B5^zWQ`Iws-YG4#;#?>6ONX_mVdy-?4n8}&+~Q@aNAHhq#Xc}t%e?G_{T8JyBcjJKQg>!j`9X|Ql%%8^^RS+HQju_~gDZ%0 z`Es((ZRZouWmnTZ%ORUqlg2kCJUuQ&RLUgHpyB$EvaIAYV>+I~;X_MpwI12L*irQY z0hbIN8RQ{I{y209&c+>gc+GaS>Bi3lxSqMhVzT8(1lJ9A?T1$L-OY_-;plSxf3&Ra z#)iW#(LSUJGotkMgh#Ucr(_*{$4-K8i=w%hAG`m^XSKsWZgVEoTL0^b&L z#wC?{lxDM40>(49vlFkH!KK5bBzHTw3wBZ9yeMTIi(PmPk_50T3cRo$^4lwcEOB8RNv{0 zL38~)qI|(%pEiX)L((oWss=o{YS~n!-ZuXO!Hh-E~O>L~oRP>AO3%!C@$Um|j zQF>RyH>K8Z5>!7alnv4#hw;rD7*$eA^C@1&#jcdodV%gGTV^^H9$I#|=G9PjNT2us zXS@6?rlC_TP~~zv*y2I+X#}MZe^W;~GJvB#>?Z^Z+4A9Sv5%)%pP6z{LT=2B(Sb#* zdJrR)$7xl~Yhl|ljbKS+nb8NdZ#+~}tRS)L(M-jbGh=Um?F_u;aBfCkji!EMCgr4Q zBSd}YNxVjnJ53k9WlKZT)P1J98Ny(hA)Py-V-X&VL0wqI#-FJ|?+4e?-YC zb$~7b?g254dyg2#%fw|?4r?=bV)8_}Gnw?&rYJ^vvsvc2l=h1UckvIvOe#rV^d8-? z-#(YEZ7`qf%v;>N_uH0LyX=#9zRSzJ@4^Ja>DhAmj8Ssjy9h(J?d1v`-diij=IdXZ zbp0(|C|IV%18<>g<`lCTyWqh>*6Xb-Cb~pO1!vI^iftPcvQzM&#)ax!|9TD?(R{c% zI?({%83?)o=(&u_pMXSZJD4zPM|_E$R%iE1F1l~Lc^b=UjZ>8k@!M^s7Bt*OVg$O# z790x4S3=6>c7HYl4np!!{DNpbl0xDcOu76)DJD)aF}fcfAQIj>?G3(NExyr;^j3!5 zpU%QUVa$-Px?6YKujM~?R(`Ag+m^?_tm<$6aTN{!fQPl%A=#Y6xiIe-sT2~bnbC*- zSgzM(2s|XGTU<8h1Bp3Q2Fj#ognj$unE!X4+%ZCR-Wk??eNyGxk#XR2c+`2cy53&0Wt$OA*`7D3Q6|z1se_W4qb{Lye0(4i= z)A?=>L8Bj2(|6R)9mFl6n!iu>C&Nna{t9a6e{Z=;27BR~o-gtk+&t-?clT`=C>SC{ z6bRm$^7o&oM>SFxH{yPU;F^OBWk4sgLWg+#rgC4ym`&xJe2e!ZU9Yz{ot|5af&v$L-CVJCSNeR~DpRc99eQHYwa5vL}2} ziW~Wcm!N0&tM}B9Xp=M+34(Z4(Yh<68Mkg(zzkmEz<%QyzKADz%{R%24V&{o_N+7- zhmSa)NVYuD#V(IIxA}9)f!awBw+i~!JlaGlOPN74gxq?swmG)qpDWo>LN(EbNzuM= zJGl>2pmtT9nzUC%V}G1aQ3?fu8hYlz*EY{g!IJ=Bo8KfDswbqwIVC=XSQcoq`O{a_P>1x5wOaq^fl0zP3%u1TYW# z(!SIF?%Kwqf}xMBd;vrl-3xj+@6~iEPzZInRId8SG4~}%Vy)xZ%??U0r(st2HS^jp z9FKE8kN>gM42?9q&G(-D-hAT&r0pT4M2VS&GRC1DJF6OB-O(a53ki zVKrI*r+OYgzn_MXR}a~<63mm#InOV~F^mYsL6MK{GocddjJ zjL9rjZnd(KnOnfw*xMn~k;k%Wabbob*5jjQq%AD&kXiAndfUY04pKb7KG1fqjX8F6 z*qK$q{zrkfy(=E`lJmj~CRN|a*PyDi+@)#ydk& z$OWctROd_cjwWB4IjM^iqpZst9t)jQw3+0WFx-6W^F)>Ce4h<+0sajg!|p+sbWsN# z3sur>X&wD@J8?{R@{;mF5(E48b<6>i=V%Q~bBuz5xPv=IVjuc7_JX;t?Z=|nAd2zY z{&B*gAGvishx{~$FtDBkYT*H9Ct?7M-Z!(lhdkt7^|^j=N^3U_fTW26aLVsK=?Axt zUkA4jpKv#s*fKb!Qf^`2P+L(~al=wOrVEhi68u^z#2)rh#Q32CN&LHXPXqa&`fS{w zv$s+EdC6P;#}+;aMj4C=N))<|Z!)E@hZv`&M7(UjD#w?CY~Tp1i>TEiLNRkf?3o=2 zcer=meCXTyp=4@1l+na?`kQ&I;#tOq1FE~vFtln0Kh}qzq@VG+b`H)h$g#v$RPDXX zl{X6PK`8EnO%y90!cP~wXa$?Q-UjpBw;d;AF1NFK!$!BXrI=S5r zp-W-X^Nf6d{?&YLYGQYJz#Nf|Z2k{{EwkVDbKCMV=C+)90YVkCQ#`_D7ZvM?tX9EzM6Q5-BdYr?u$E2ON8 zS3U0uIxaOl)@hXn}FF>ObBakj{0=JX>xTPn}3sh>=8+*F^OsyHr4KbB};{-(fRH@WE3Np$0qI7j`2Gq zld@4`_m#Bqk+jkc^{qSHh7%tIElmB2Ij&o~c(YtRcpP4P4h`r7Dz+KtBWrIWxhB4i zJzg2Yeb^9b?+1VbYWBNqRb;>vriXp9OFCw?|p&;A%^ z4u#Ep{nhmhFOLa^bCprpj79tk1{j@0F0q%M-k#X>8TFhypI(Qzr~c-wi}2&3CLbd4 z>cc-K81+KZ)oFV_v9xO2e~V9y*j-2nq8+PJdt{rP|!e%crrqFCK3#+3q^hbO70bBRPBJ`4fbq9ay ze}XvRKS@W1uM%hf1laCyDMnBbC@QFj$3%$Z5yX4!^d|iro9}b5yy^lAXtP^xBx=Fa zemH<-n1hrpryxx3q^EtqRk+m+N}i8q&2?0|?P9%Jb)_@@&W<-Zs{FE*XUyy4vPB%J zln$N-$g?Dre{=Ok9zWX3$7vt|=!cg=Cw|p15_Qbf{pV=P=s@@$Zw^#K^^ZW>89wRb zTGh?PT%W6RZe}P2FzHJ7Te-lyG4{*`gK0Nj$4X_?hMYNZAW6~GZ??ja&V@B*0CChL zAgGxLBO-vETR}f}uy=9pSbW5n+2(mA8<#uHw%M0NrU(z2(1f{@ViEIxAXh^mHNz%W z42pT@Yok)JN)a17lZG5$8p zI-B0|w|pUYpVSlhj*IvN zV80TG(OXAFjJ}weJM6kxf6;12$;kn&tN3$@HGRqJI@Kg(4Jbl3{&|6CvM;UNo-P4Q z2BQ6{=zpt*N^ku>OOMTfg7GNAWECiUhtUNDu!(?iBTDzGr>uZ$(JE@y=d<-s^!~#c zM5Yfs=fE(tYnFs%=Z|@$e&T~E(vgUM5APb-l&lIgKl%4RxK5qxRL4qJzI59MgI>Yi zT-J_PKAg14RH8Pbyst>g^;vTSFTw`e_Mu4uxuJ_#qJw;pcxbqi%PdLiSdwTB@*Vv1 zmQ@q>FU|n;TeOI3b>r)wD5q{rlatg5-jUEeX1tER;Z>FPMn1&o+oF(cs>t32@b-S~ zT_XgMqmMf}nk_bw)ng*HC8 zb?wimD$)H)dX`xwgRjBXnc4N}S`qXJy`%b;5izB(eI0%9){nVLv-54355U%RT zL13qqZvw{yGr%LA5 zEc&GE9G}0q8+n;iDp4SqELqNN!0%Mr8TIv7r$uD#5T`-2L$id`j7O8Frmr94$BpCa zV8-MOEsmaKuV5??=|QS*S(P&xnC#rbali@2L|Zt`m{^Bh;t(Ru{0?BS6b7>!qawN8 zVJ|foFY8+>0`J#-cIEc`cJ-F^2QW)DxfA%{a;)eA-pM5)SP)^;TmBcbWVVW3^63rv z*!??bM+@y0Fb6ba8z9s~Y=gRE2dIpC%)s+{4vYQr1zykRJNvTFi{aL~5cW`qiQ zobOUzEwFyf`;Jwpz5QtM$99k0ozkAaXw{Rx0A zq-m;Y%fJ~&r$Zb!b5h`KFM9Xx zOTb@v+i*T{rS}OBgs;+RqWGbGBDC_FB-=OYumkXFCCS> zMhMZ$l0PuEo?7{(Iv?hKW@iNDNgn*$G_Yw_=ryU!*^*rk~*$%+oET;yoPrc=_; z@I_fH52brTACUr~H3U4_^JIzbj{g?(OL5b}H6A*4wf7{*+gVhx(0hzw6FLkM^bBFl ze6}6K$Og;o@PUhla$h?~hevo{FI^IR)3}sp@`v&|=2t9#-^E4qx1L@lQKpC11?Of> zA)_(G>zO_KSh_paonCGOIQjm&LWDMxlW<-r{4YF8B$7k6**o7zkfY>h;qE!foL_SX zjavabg&6fTuK`!oR;ifP>>;BraAqHcgPzmasM>us2d*EzptBf4M=#OePJXL+0K1&| z0VPi%Q&Z4zvt_a^*Z>*6!&;u6(Pdk-vqt&G^DaIf*=FWw*UKTTYHVfmvTRjSY9V#NL1i}HYk%=>KcJgq@RvrbnI5Kf9$Ka?wo|y`M7~{J zwS>xWvgBQSH@Cs#^{3_ye#Enrg#CE* z5+<-2Rg?5Z&0Wn64ZVtoS(_5$w`$YTa3AOak?e(8tOC)-v)sbSi)awb555Ay_v~Um zd&52PdWVsJ4_29TcEhw&);n2ekw>^iV*Ubzg#N>=#1HyGO+fu}?7g12xp1JOhtT!@Fb_KLvLIvsI8fFx2IIJsCivmTnWF;h3!% zV&l4h_R{1D6_@2gpIu|<2k>)i!(9jURcph8{0P`}ug*QE2`zEx4UH+#<{Q9%SC!U9 zfzv9U%~(+vcxGae^=cm~>7a94cUD?uul52DN6GlZYtO#1P&Sp+(P z-sz*6!Q7~G7svp|?sgN|9}mC!$N4L_IK_mX-ka{%cl?mpbV;_#FD zi#I&|(9l`VLBd1tRDAyT&l?143YI5(Vi{i;-n>GdD>`?pg2XRiff1}eslAU)ZcZh7 zDZ{+m<}Q%f`kaJ@Ys}rVS{UeRs@orQW(SR?H>10 zcsRoy9Sj#D4q@o>KyKCc!m2!Qd8Na4n(zTt8L~SF)a@HRAajiNYc1;ViJ-_)Vl}*h z9<2I{kG+6yNdyEd8H->G0kA0>kOO@peCKi(%58m}8b`hu&Mb)y+q_JR?gwg`H*XVz zW?^AO_wDmfKua+0=S@uA*SaIVKeAl9{L<_5yx=yL^`j$i>uRb3NV2(;T)l@FrYwAL zCbx~@|=7i zu!Q_;;-GZDp2@mufo0ydS_EH_3UA&C({Cn4PXK(8xIsqKfWr}8s%#vrx$3;a<(o!3 zo1>46s()8WdiZL#FS2&rhQo|ZBfR=wr{mUHGyT*@CC`j*{F}aBY}ervfo|`1Au$RDAU^e4DIi3_w}CMy!K+az_rV+uB=d<-IceK zG3xAkucsb#D&6rp3F&g~MGz8c8Zc;$Ts8f>!@n7TIRBH;Ah@FtXM?3#QM(;XY7T5b z*gIWtSVZmqE)v2m!xzW;xs$XCIdlE#3IR>h7w67&b5yag0r(_Vyf$AhYfw7+0wYg~ zR!R73Mp&fpruI`~br+iO5f(Jv6kw4+urk<5oavND$BQ?UFwso4AHt1CnBBSgWbxAA9HSz%6wDJl0o;&EVLa2$4iR;*N223iTj(SmbI9z^S4tUzKHDg||zCK!{X8a(^>ho%i zgxg`!Q10BK*PCDx6ycec<)mXm{QP>Z}-hawpOems6{) zPbIuk{dQJ_d3bTiSWqcA(xW3@Jz*91ko=X_52tiADu&%2gzddyEt}{%)Z`nhK;>Hu zo+i#Mp2xr)DQAK1{pXCjde0r-!I|eR-aEs7mtQ(2zIu*RLj6*hl(u$ToU)cz1JQ`i z7ddn2sr!+0X=?4t1=dgDNtZ4h23Nox`FkgJ1Xx+wrcHKNKzdihMlmr6%G2rJ-J+Wc zUL4%ujvK;3i0_L0UvFf_mrbu62ko`Kb@hQwohd=f_qUdI|GFZd%W4`fIkv*Cp9ERf z1m$ObS>p{ZgIK12| zeV%+je1m8nqNzi|@(yPwvezP3`P}Qh(wv1AqS$?~F|pTpwRh^oj2SjX3UW4IC&%xa z%sYjR#a+(44g5Il&a9%hQ_MRCMm1IRnuTFFnK}n@4Di(Y6iW|LNF5 zB;#1C_0DNw#>YkT*XUU)xadZR$@#PVPgeg0htOL=&%rhJ>QeRE8z}P04b*F%kq_tl zNciMB_PDOelhbGlG@;L>GZ+KPSjWt;kmt7a4M|1a>}KLz;ZC`x&DRD-e_Pcejx>sc z;Y>ao&fuWqmIZRi-^TUZeHYsXyI<`{9B-)OkWx-KxEVFPC-13Xf0|ew$u-Er&9%;3 z)&67rcC-GHnA^`r)%4MtkP-G5zBspI%+b`>;Fw)av-1L9O+45pHlrRlYj}j$X89d^ z99%YcaGTghcHbHMfz7S(*=8you8B@aN7HIZT8t>VHXasO2BUPI`(}B529RD8SbxNE zgf^{BKPH-#qe>5-m0ukFbn(6~i&g2bqXK`%%Fiw2)kaTs5$!tcty+Ebp*lMsnu-|? zCg!hPZlZDRAE#%{ZjJ6`&x24qWaSxv_E&kPJEyhAqR9N`g-(fAy{Y@ib8}QTG|R@g z-&k7secRb6Fy5sVxnJG^@iWm+*+>21Iabj1TR@%BX;bTUKz(6OZYA#kMrBVogu4A4 z-dF;O$Fd3eY#$$C#@eSMQ_Lw~1DAJoW^!K*cO!|4J|KoMKvA~wq%rso1;y%JM3rr^ zDZ;@-2Zfpuf+{iSPw_Ca@dtP@hANQD zj)`T?nptbWT_C1;kz>?d+Sj^l{WDe^F1$YFNgZdkf&GeMoBJG*irigp5j^R`UbdednWicD58`B@BEE}|Y01X&jW^rzWP`fhkp{dS78 zaB#P>Wv_h0dwrGmpQlbD&UaBBpRWJnQ(Ij~_~#x%Zo5oum1o46QyFzW(;4--4Fbe3 z_cKIkSmzr#W36K7q6|B{emwQRDUNQqfjo$EhXC#68)Z(lGUQee(@E0@QFo(r z@j}(zmrUfUXRED3+wKSFN)o`SCsH%bq;G`}`}daJPyTeRd~|3JG2eCFaDI`vXo+RAhaWX3JSSics9uYEg0p zq&uE^7kn<$_?^2SWzq!Q}|3D=Ia%$lYU8GyZI zmRfULvbBHRel(F$WY-!I-hwt4q+U2)Iipkl3^~{vSzIzwVzemSH)9Xk80L5V9$7=u zfo5TY3--gZd9;0Lzb1~BrH7-hydUl?H=iExFJ(7<75BTzMFL!1I##d8%z6!g~dOE!id-?0`dD8^Y%DC+dmNz@LYS>P{Ou zC>>Z6O3EzSyS0*z>F*_Y)sWw4?AC|Sv1G4vr_t~gcE8W5(_G2AhiD!_e#~X@1LpAf za5U@eq8WK}`~OH5HD+UCtFX^3(t3Fi_G%}uq6T`y{U9M^^Ak*a%QUO-@`?*A_uF5> zuQ$SF`H=1}YO5Vw=~L0*fJMEHJw`mk-Ky@ociRgWD9OYZ(5&N{0S<$%|hsC_LzcA)Bjb7(F)2f>5WSYrr`M!<2&E1d+-2 zItn`qL+{P2Mx-KW0bT84&pWs#hKtl>1BT@-YyW`vicJp9k0i3E2YR}@299Qs-R7;L zrYRWBP8Ifvyp4rvB5Bp>8)%X{Z_R_It|B!Vd)1r=jh>tH(gj|{NoB@KtE<6aH?FV+ zQ-1W3*0VsCcsW?~Wvcj`VM=r|WooW@Z@R&1n!bn{C`jOPl-w74pI|e4l;jrDQgOFs%nc`w%|JC!jjvd|ljQDc`P1 z8=gfvl0G2&$KEJx>0yB)231l^CnN2sDE#Jc+?G0YxM{yozQHTv_g5TWkXNAmkN&&E zip@9AYq?$M$P6<$_Hf|XZOPQr36uiiE6p&_ZJl+k-6){J>O}xb4#g-8YIT1b%&AUw ze9eE)GZPTmpoN^Y{A@M<86yQmAhF(M=O&zUUfL0fje>^SzYrmoFT8zlh z|4kZ{>Canj-50-Fcn=gjC&a<<)nYcN(o8+8b1cay)}72KD=_El8r2oy*Gz+o0_6n! z&8}Ep>vf-@lEnQY_|LhV%N!x=;?Ms%t3p?PVcj=Yvhysmc_a>t3{VvjW$=C0r_<%X z;N23|5&Z)+_cDI6P7<0CIu!v~rlM#|SBQ_vA6guod@I8U@m7J(VS!a)`Hn%z50&V| z@nasFnr`C9huK0!hF*(h_r#jyT5GBIAlHGVs(&-xC>Wz=Do!|Z7rl4CHc7Kfb;VCA zRK+0s^w9U8UkxWZD=>?;gntCd4R;!vf(EGyAV-6x%wAkNg7VaLA}M6wrRpqS1#Z=FPpj#tRb**<9Jx5h<#CN-)?53~ z2Q~*bPV43IxD#Yuq8gX)wRxUhLN?ECEGegv6S6xkvC?u1UwK2M_ZGYT#fuK=jbYfO`|#g4dKQZ6+&=Gf zSi+5j)W3{X&bu88XBKtLRW1jp2Bwgo6 zbS=XJ6x~2heI1<&*FitnukUm2b944nt=t;Cy5%m)In+n$&e;bmkuB8UTV{}v`-p2D zwF|LAB3g!`1akN8X(X^2;@TtqUH)!ZDZ5bS>A8Z<=1o4bRL;O?c0QjelXkZ2oC^K<_58kj2-se(P zC(kkIF%^}$3NgR9^H5?sXK;OE*)<|n;Z`j&S+~yi>7_27`_%&D~0{stKlhb4EHmHAt zjgyLfZzax}?QJUQqY?f)JsfCEszq=Mcya^DdtrV}I*btL`}b^Im>Aw3p2S}7bR@xN zEqjOMHx=<*;Xl^f7Z%Q)J3^J=kuJtBH^LVn26z+Dd2>z&OjpGdP^FIa>P>`qNrMxp z-s3Ui*9j33tVWOZtil(E+Q&xW?AjAv>hg7fewM)Pgb+juI^$cnP1lk6xjl{wq$gB; z9gpVIZjt>}Nf%9LO4YKsxyQRVG|GN2XCj2YOpdkdAt130<@dnyr~=Wdk|i+V_k=>< zWkd5n``zPD4Up(q2Ov-V!s>+wQ);NZ(T?0icv} z-QxwdxE#~=o)fY20P;-h69v|FSwzSG4)-(6#?lPm!(Ca<+9cat;SK97o-02A2AYKK zuue@PS9A!^T06Zw_8`F&NN#t$DUJcM7Tcd@zqYJ>#}OyS@-KHmB<0fA?Ig_D^Lvfs z8;JvMadZ6)k+Q>d9906?hZL;xqJ{51F3CCW`0VdZ=oNP-hSiyF_lu+`p`jQ3*0F*o ze-C!ipg>@mA%G}z3f}c|W4h7e%(-e0*_)&;y%E0hcXPXnWAB{@^2L|B3XVp^n=mmS z#&)+VNQARS!eDV#Avh=UMJCCUh(C%VA(Onw`6PLfFqmD%=O|tpC}raadOzc}a=$Sh7yLDG?Mb#|u1;hL{2oA{j{5fy)Q_INEfdj;GI!@N zA@9-9Dm3&T7BW5ubZ?UgUQF{#%iC1ORvXIK{W{rlx;-u*`GTS1&oND!xesP2A>?{b z<6QY-y$dENxoZI{(@k^gUX57)R9^yHBItLFfGmF;@R6;YI(Y+{*%e<%E#gR6`2qRs zy)zW3Y<2%!Wqg*iu=h{f-0m0q42m51%Zh&Bj=(o+J8NI*%ee6JRZRjQTcib4(VrN) z(YC+Gp}CxuZx%g$QH~86N4cTRCCcvs&+=+T2D+;e0ZL&X(+5aiOTbE3dpeTVhJFJf z?}B9=Z^(g#up*}#TaP#m{s`91-+f(g7{oq4ik;k{D10dCKG?jTc&nqCIUyiRJ@Sui zA}u;bibnugV>Ny$HZdBR@}&aA=vCXv55iT@&= zaZ53h{FmuihE^mNpTISA?OEqoJ@uo-$oS6+(ep`caR5Ww)V5-^{CM)v`}^NhFChK8 z7TzjGR0c+-x=YCf$2s^vqK?WBblBT4MI`E6a~SQ&>Ch7)5^I-$!NG&D@@)80cxCYM zr|?!tv)3yaNrMSC*TeCjbjiAB6v5;jIjH)jw3iq2V_SCTE<{AZjPoP?V@ubIAbsE6 zC244n)tbfRjmnl_iJiMymOa$l)K}?3HV|%w!h9^$tUR}+*&CTJ{GNY}+^wCiY zKXXs(V)aY1z+9=pG6N${&d<$pe9r z;FkD@za%ltkZCZnN>(~ql)Op2Nq>@Lm3%K+&uEy*pv<%mf0<*5A>hWb3iAzNQrkO* z^e!!4FPWxh>st;N9u%9cz6i~Rp&m07#ysb$Zp9k^<_JqL_AVrZU*-GS?9GuUtFgS{1*OrOD_4nChgv9&`P%XC? zULQ`_yRQ!YSr@V!rq^?8H8BKs|KiZ4Gpx8P@EIQkZ1;{H**vdi0UxksB+vMT5PFjq z)DlVeRKmmHY--^UBQ&5sFgFE{#Q%Rk0{80%4i3_g&6H^jB&Br4iYy4841~XY&HmA= zVmqY8fvSL{8H7fLG@EICo!v3o(>grTHXMJJ11VrQ-~6AyhqLs1*W{)oEO<;GiDh;W zRW|}V-cvoZsLzh+sQI*9nA*sS&??a}MsTaD_Obs(dc<(!6W|uv z8F0<4Gh{I?Rn}pbRkM8$!#MSAhWexl+IFMBA4eTBf(261`lxH zmuLC`S?oenKaiyiEbE#MGBSwEKYVIBV@q9!LIpCSuj)EQS$dB!{gpv(F&QwhKf@#h ze3{@{IxjxL9MY=o11j&c#e;F(Y#gOmJ#0M-m9LUNgZR}Pl^k;C^)!y2$BwB|{Z)?z zV(_~{x0We3&EDd-klWr{BAoh8L)PMVK1e2}wN@+FjGQ<%;br*J=D1`o;WUl6CW*=u zr^#31`2xwe}u6n&p$xYH@;6Ut(9U%v74g8u$Cd|7TLZ z|DR2|c)!uXQ9Y+nlIzuU!!NOoASV5}9%6M|xtwr7C2Hu10x^!=O3{nX()gI@KgWSP z96J5N9ZT5oMhAAK{p!72Luds~P zk$sgkQ2WmY^NFh4hW##Up&xo<7p7+Z8};~yzk&I3yZ09EzfoFt^*M#RuC;+d|BVt? z`R{~|4GsT|;{9-RLb0-$`~Qt%|L=tVU#Df;XmwOGq_ybYZy*&G9h-UI2OmSC_#@3i z9COSh@_v{>L?V+IVVh5odohQrFRAP;h8L<_mDqzVV3x1_TjHufVHYRV43z*wEc^Rv z`0$n8MPs>Y%e4U*%k_IkotKk-93(RTT=P=U@QCz%Qf_4V@kjcADD?yLgd2!U?_Uptg^Vls|I}>nte_WHRh?B|TqaiCpB<;)lG+vkXSZzvEj=6}HIh zzQeDZ6A(g2gj-83g?ITOy_Xth^O*l)BAz~1=@FIw^zgd-S-6?a-)VS2G_H{N%G!*+ zp`+V;N*&ns97e&7xE(tBh;LZCbgq+oRA>Q@9^X!B>^OREg}k=PRigEc6IdkFlhzOa zys1=@LvidF8hQwPGgkbPyH+?gdp!1RPx&aHbH$P^UqHsm&AofDCy>i*q<-dS=6|mB zSkQg*L}a7i#;du$Xg&iX?6#VU#|!LUsA4fF`=y~SU2n9Jh^vbrSl|EwqKNPg59YN% zK@{tvlIGW&4;>zHo*OCV$-sM&v&m3fw8|qft-C~{tQ6a8Z%pFVVkU12n^k;!T~t#7 zU)eZ+({;>!5QGJ^|ZtpcSM~4(q?HJ#A z!@wxd@48Hy2fKiKyqKgio_&9fW%)O1fJ=1)j%?v%O~_^T%3=f6qnvrw0X zA8rJ_Ck9K&Y~M=eoA~kPo5g%h)|eIR18+vzdQau6?T`KD7MdT+ zduOlyRPfbKmVlfnzh-Z(CpYD036~@{8TiY&4lvQzJfnSGOgTmzSQH_B3%dHd;=N73 zR$B(*wkmFrDhgeERY(+d+<-YuUtqdWW|X#1>A;eHY?q`}XaO8>H-?<(ShAqR%Id&Gj9$9%p%dO6M$1a6Eh3>NyvCrkWM9O@Ur zJFj7~Y9V^(KAZPsuC#GsdQy^Na7=a<|0(GG;V8G{jWhlG7oFQ`##VF{iE|IWkS;y6 z${ufDY`KHy4l)J{h)T$Mud{?o>1(eFt=Ena1-fo`vGo0Bf~aTI z6UBJj2fj1GugoD{6iPSeK zpLU8vWl+rCq>Ir@J8qF$JA3D)DsQN%suyni>;$P95Hih{wwd(ns;X$a^ddw11k>XJ zeCquLikDWGZJ=B4y9Q02#FVUFa-LT;dLk8%N%_*>TUyLFYyZNl=*Nk+?0!B}{y17Q z33?CY77zEo&X4T*C#H0_sWhiNGw2{c(Ge9A6ES2IiQ<;sbjP7;uS}=lorM$$|Gm!o zfVXGMzi~4AwHFjOSZn$O>AwxBUJAGH6LMMW8!YgF;oR388OMT$E zzt!UX&%6n(A+8fE47a<*J*)jU$KKhT91?TLQ3{<*x0*(L?zVaMe6!WGJiD8L`r>L| z!}h*2E>H*VpK7bjt%f#!dt>n8iAXo7^jekc<5cEEy=6I+^^iPA^^~w&Q&SW{;4QT| zr<-n7OSdro`Ee`i&On>@!-wU9Ilu1yqq0t|dZ z0ewJ0>-+9uglK>CYmq!_jthreph;Fn+fdfPorI86Ovvj^oQ$w_&3g)FO$yP5>Z1)i zorH_(5nJxMf%OwMA&=C?yXmftyn;L=mky1wblF4k6H=ibc(!ws(YML(Bknl9$#=-` z?#ro-e#rZ{ciM<^Ruk>uCwY+Zu+7UF?Yl@Xn+vK^wTdpFyyH?5tQU-86=uwG!ylLHUDGBM3IRqIAa zu>3kBcb+3(azCV?*IBs-E%lT41g_?ZpBaCvlnYjClTlgxW<_q!zrvFiwVL$h z!Ncgg9d~Xq9H8B0HL)HO`kjgCEKxAEdRTL7=P`= zy#Kovzzp@attTVUnTwH|ect45W7PS|xY+TID0i-t??#@CKp?^9ejRgYqx>VPD7NL=dLHvz^jW%~V<#viq0&K5oo7Ekua6 zdX_;P&WKLmeZdD`p~pvy6?n@qvLv4BoqHoY`}rbk*i$ubjaufySDq^wtoQkfFa8AXw?!`eJN+$9)B*7k7!9Lud{`otvx; zr00Q}?w0H-1C!t4QrLUqQQR^GIU+s>%ztx{Tgm^q(LMY@?XvUXTMlDHeWZfQI}P&- zx#`^s5P_+nOCEm|E)NL$WjI2wU0RypV!x7%*f*1x-~BTI`ZO%>I-6?m`FAvm1V)3l(!=j%D%0y>s8XigB-tErAJTfj$waZlXdL+=6p=5Dm zjO!jy^1+Q(-o^1SMdOICC6LW-6=5=Cd5^X9WfLP~oxa z()gO4I70kYCB3^E%Nc`@J?HsYd9`~t*}B+F9kQQgImN@Vfl_s{vdnj)VkT>aL$d?%aSF0*ikM?k6W6Sb?A@@f`K)lYK9Y@ zPKs@bzn8)5IPXc`>Y9hgUh7e}x#`Uw2<9n?gIby%tX>O0QJ20vCG*4#f@!2OIAcN? zut)2?#*=^I@JYs>a;(8rR5Uw;fklWT;eFR~h>^#ka&X;@86HNVRB$jhmPksry`m{<{-+UqukUWWWnY z8we1^RuZX0^gXf0xx*f!RcvSP+ul}Kdbmpu-=x)gq7mPli={2dXCJ*M#NtISGz^O} zneXu9Ak*i1QEO)o%CIbaW?Nn*?9K@^0yhphm+USegeam5DIpQ4_;uOm-~y>&J`kDe z6FK^&?X6$miK*C(@+Hs8SvP#;FZVUCOEuRZJp&{p_*2ie`{4ocF>ufe2vA10yXI3Y zNls8f0r_c$;W#xc^&hpXdz%s~E7JykF#_!aHg$a4)Vcg=x}^|@v;LLK#4hUk=7e2X zl>9k!k{D-UbbIZ2&59N?+JKh(=L%Dd#=rixZ{^n?)%w^Ib#Hp^HKYIM=*xd=#A230 z8;sj-_n+UgG-m0TRC{!2iNJGMbOwuE{DksKyDTqy*Y^f%gb>$TD1BY#e>~@zh6PLsIIxuurqtv@=zkLl z_{nhaB5ilWzg>*go}1{=^EZ$OCDtY!OKupyNU}3<`zGTU`febBospRs*NR?(F<4o?)*Q`pb|>kleTJD$d!w+Z># z@{5k=oaro`XKQY6cDn_r3*lfvA{t>3R~Sn)azvgYopKry<@fN=oINz5kperuSwfyK za+n9n4gWQ59B)0N-~75FR$Nr^*f(quCsEXMkC($E(;5L&5zC(`dx%YP*>SjmqKZnI*Bq_p(xXGL@E}Iom?Qc1-d)qr*6fv|?3!Yt*cN z_-+N9+fir7g_EDMt67s`5&KDBQB>?RL(SF{R(Gh!oE4xCarxr7Z5mzU*Ew?VxPl`> zti8HHZ~fAHU`)$A zA3)2AOw){EM-rmDG&4&bN6Uf*g@}9muEt zfm|#`jI^Wmf!lwFwxBjom(_Detc6836w8AN@bsymfqW zk^HIUHwkXPsRC0}Y&)6P!ojQQ&cSxbdQhit6#_aaSd1zNb|?tjv)Bo=UWx{jAJGc> z=f<#97Yg{TB>0Inw*#cZP8cndc8}{St%Il5`kp{z85~!N?tEic9J|Fc@?d!%LQ@cX ziFxp9ZGviJBJ=y#Hra4i5iC|(xC=@4*tFtIhl={ zXCIs-Qw{n5Hm59U9MR90F?o%OTctWCAU;E)bKh%xt=VB~>p$+S2a`90oseZm8j-q* z3*{#k{qBhxIR*nE^N0V%Xxmkag}Av3(tylrFVDkuMeuX$ltkf>`2jLS=Sh59JYW)8 zgFeN_wtK^SKBtfe0gs-79}dpOMY-&|+cqyN>c% z7)%Xzoh>^rZTDJ~hZp%s2746;=g&Xr8wncs&$^J4a_F8jNUGnmmy>^g9s97O zKx`Y_m9w=mV9Bwwuze;b32ID~vo|-vRf8DU)b(6{Dt|iHwr-Y)t4;#-ED9i5RC!aT zY44&P^6FqxGz@6om4pgc^BMFIZdFnDUb!-~J#(e^fA?dNitS{lmAZ5N11IPK90o6i zo);U^0>egy62G(@l6|rkw(hj|?DGDdp*VHlgY#u=ek2{`gH<5xx z$h9=$j^$w$2~bAl^pos=_Z}s^dKR(hoL~w3&9AE$%mhoe(TErv+cTj&d5VbjL2@wV zo$eZsvx`I9(!y%!t)+c%*5e*(mA%?8|C2j)cz;DY)`6@eVie7CR~tn1UZ&v1 z^i!cEII!JKE)5|V2;8dhVQMVqV9=J#bjyB#aJcGuBB11PqO}&o%Ep4UjSzJ5di{Z4 zwm)2x|3|L@NcQ2zx3D(%E&Sn`Bp$sMQ}OT(zJyBby+ei0YYy*nzBbG=hJCx)A*CKe1cTU#gd5=&eiyN3Ov^`)CreO|dQo`mKsSUmEzZ2O0XqHjJl(C6B z+{?h#RZV-6Z(IrSLUa8tzj^zNBR9BfV_dx$tdN#P1o>CLtV^N)vss)%_v$@H?VC(@ z`_@1QJ!B*?^58O#1-u&r@7xcI_INEup4lb84}z>gvo!oJeBEO##r!o4^IsD3Jfrfw z&S!c3tnvaHAzRwM5pxaD?x-f2E+OWugpe~wO8A=Y!j%FTa!Vr$uTvFk{J6 zn64lrvfw4AkNp4W?Ubj==9r|iH ze2AD*W5;!awTgQnkqKU!YUCb@>Nc>`>P{>pjqmzAq&X9l`?wI9+e2OOltTYWKlGW# zA8wZ~CDe>BxbEh=A+__vxyA$M%$1TMJx|Jy-LqWWD@h9SQ|^3|iiabj(-o>V@e2U)I@2ja{CVbKmKe`)OYJ&douEqyFxv84<~a*4jE3<)A-1 zV=v4y&=;r^+NofhH@90aW21ijqwFZYhq2hR`@D_TH;ZA5Pb*Fj7bw=v_|(BVdh4xR zOlC-pLr>Hc(zJ$O|Fi6rdtf%v{qW^s254eo{p@GY(7Y4<|=Fr z?p~(>11q*YsAB$a?CJsK>66xfxOYm=yL96|py&<^534If=4jEQKB?xKvs`gh6{SfB z^^>&BZ`LL5)Ie5+QKI**4MsL3$nHIwPGX2r0xS`j^t>m)**~=#P>r%fYPX*deQw9o zOA$rqkG1?(C~;bi4hK+=+it)9IPbv?!+&Qhk%nSDJzc9PX?yTwhrSgI7`&&TTEACVc}i=$J+|SZ`oDO zi9NW)_7HmhDd(O3yh_Y9cJkr49ydx*{OuC;I5LWZol`*n$Gt4W-cJ5@%l`hpYH3YC*S-8_Y(IxaI#Aq7%@tFaISazBRp4ouLug( zaV4rzFfM<@!Ar0!oNv1Iy6gDtV|)gyK^#_%O=+qF4-&%kbJII*80?(Utbo@d3J?>0 zdtCF%GZ3*RF5ca{MM{vpH+u;G@0>_EA#?N}lm~Pvq;{n+WJf3PBV>1-qW9=x@k^4$ zml5tz$0R@I$dsUXMbP1kXe@&duScX#_7*@3?dKCuVS{V3FQQI{a)8W*wI|4se_HM- zB9)jV;6#$NS!WPKm34CkXrl8%uJ62HeQ*H^N?TLbEZ4{RG*_E zj2F&~>%CH^qX`I`4&zO}ih29o?l6TyLQ-WGWTd)~>k-LizqUN;z{(&7->?-}32QI;Q4Z5S-m(S#e zO)C0`GtxVq6H#48oEY zJhTs40|vRvi~T8&hJ$lZ2b>4xy8{62{4Fmg|1K?*A|yOVzoom_vP1YUb(dCM;ci%i z4p@c&VTOru&_j^fhMwx>UK`F0GCe)xADsK^@xx99$?k5NP z0g{b~yOgK^ zRC!8j&s0e7cF5fFusz9vRLbz;PAd-@?uEI5P6bfcwzbkud!JSOyO{o^fQi8v`nFSu zP%P>>+jQnUYmW9g#3*g8I$Gh>%_*nHhusFK+ubUI#-?&K-e9K=>q}g2Xo^PsiWk-Y zCkkVh&|opc(S|EUpMV$s(@x*JgDh@rOM#!lf=)gCRo3j<*L$?1pc-L&NrvR+`cwL1 zk*Ny4Sb=fO{Tu*K>UDT%EV{(N>At)bZHTg^3>4y$OOYW{t1=;5NiX>RwQSK+9k_6R zy{qDJTVceu^iRP% zU%<#TzGV6uEqCQ0WvGa0O7rW^`dX0Co-3aV#q7!&AYCC@9SysG#; z=6XHvbub7U6N65{g6QkL3K=JAyHWr1GA5GMl;h)xeuZJ)@$-^G>e}QFYXh-HCiD66 z_aeHIV=PK^$gyiqhBtyIePI|7e>MsB0W>7=-ThWEL|{|gU{HJpNFjbwN+4x#%_u)C zop}2m<x6&Yolc{PhiRZy z_W(PBxh|*Sp%uv1{ht)4Z(Rfdj5ONfkaJmX8xXg$J-O?vPw;^L1Z())5%&v3)+|`^ zEWT6RUBUdr3IzVh7f-r-sI@Hy)Uq*rc;yHdL<>Ny+zp=#6$<>Qa zZ%FrVR_XAa(Pvh3Lk>Dzf$Zw$-k>SHrQz`X`=#D;_bXtt62dQ@NK@?Jg1gn4LEIO9 zeZhd&yVLCg^2jaB2z~{c_SkiY3>rtMQtU_Pb6^&Ypa^nrwA>d6zxCaJ(1=w~o1^+1 z-BRc=>B+suN9gpr8^DB?2UL`T9=;f~Lv8|Y0NG>g4%gcX5$AC{fQ~Sj^4;F|lmQr* zpQg^e1iR=Wy@CvKlaA&86E0n^2(gcym8)U1*8aSQymZpmhjUar+ltgHY&0PabBk>{ z5NpE~o4kwOU>_{QZP0K#+FnVNTYVuSH2g;l+YvVY$vt5T@~Sap;rm8q14!~w5VQiG zw-(0NY(jcqXN8*Lp+gU_5mv(~Q|>oFO6?y=)1)vBr&TlZqxxvI{y3nk9wut*x)Lyj3?VhXsSQ#I7?bWS3=xl zZV(AjO}Sxj)#CcfRSmn-3C8LlOvo!x$~fd-uii)-OEo`7fWYfpNRT<}Jmz)??cW%; z-|sdB^Ua9Y)||SSu4yr<@F6c?srwk_+X7b85&mmOaj5$0YU%Zltg6bdTW-Dn7{MaQ z>k%bLry*uDDvHz^>FQ<6@(Ai59zx16Vn+y#X1+n|^VCJPJBi6p^IJ=2NHMM1G{ZY^Xt<( zz^Vi&`do(vuera&nYR8V7G6*yPTxe`7o!6^6w3en*7J*01F7!#^M}lACGJbL$<%jU z8+dsX@Wr3+%e59#S1~kXZ32bWyK>NaaVhTBzm+g6y8OYgn6cME|H7>LX%Z7;-belo zRO??3{bTanO|6n+oo!KqXsKv3o(SfB-QZq;^wx`@Q1dI3bRF4M_ZjPYBN{GAPRdgz zZr+3k=5~|LxF^HYagj#Q_o<*`jZC}kdY<7Wi0z1szpFW-uHPtNPsx-#)hNz~lfCZ* zt3tx+CECnzj36(D%r@!+=D_WU%_+3Dr&TaAzE?~onZ%tA>#9~Wo@!{(?T?@jTBy_Z zvC6#Wdt1Ql4TXQ?gOg99z_;D>;`3l##hDvHVH5BmKMk$MwUNWJcgrik4fC9dhTaUP zI<%tDonbi3#(7ihjSfXuv(e&_&^gD>sLJ+hZ+@}bKc5{uwk>yqPC<8+=W~cMRmPq) zHk`}PNLsw}oP7SFjxm8fYJ{$-_XgtS&i}WJ&{I#3V7qdeV-+Oyn&?ripSNeuDt?Oi z#Yf73RNISz7tRGyNWwV8ot_Nh7SM!ZZuj053JT4k&q@IETxuQvVSfG%rKe{EzT6g> zEV?5kDh{J{WVuT%1B5P2PH=~ubd}77P%FWP@z<_$4qzg9MOo3Eh9UP8GskzbOC=;o zf}tbBye-D*Q3sS6Zi_a zSZw>#c()Z*e4zs*<g8-qB} z3F;E&rCfPs`B)7k&yX~iOSU_my4koq!PRmqtz`r=Q>_%c~o3%5YhOXrdDa=WU?N73E z^+Uz^eIgY>QkbxMPDAV7gLpV0f3=wxvYCxfy!7*|hJ=X0!Wk4VSXq8x9rPsd zzZ$pYv&9NZj|q!xrLEEuC-S_JXRAhsDhB1*_9b8qk*v%r*u|A`CsFbkq@dct!5_$K zqrRW*Xfxufxzt#-e|!P@R^atNZx#o*i$Pf!L$qNig{;MW2=#dYVgC?D~b?lAjm}@Y>k|dWMH5*4y^`qh!>tNOm}_ zzz9|M!7}SXaD=7nP4mxv6uHCG&Wwk*ob5A$2GOnt!qyRAhBiM{c$$KF=o8Q<@h{<* zM|E-QGg3M5sps|a{pdf>r7UBhu8B5wXZ7DYW>*n62)&Dl7F5pU@6bgiPaU0M}HAhTM~GluCz2ZxLJO1 z+l@^cgBG2m=W5bqFNhlw7+pfM#Hf^h=|fJPp7SL~0@g+)s%L^d`-d z)He=OW?>RAi?Iu{cM|KzDUb0YF6Dg!M}B&{GRc+YPE5NX$*VKGg!HL)@+Dh9OA$7; ze`>NO(r08@x)xy^(r51SlRT#l>7x^?%kwyez*t^S`&Ly(yi4`M>p2Q1d|t zq)r@g?O8QYg6KW`(z+Gie4-$bzfSE?$WLTS{P^EFbgZ`j+Rsh=jrCqSe=oZ+q_V!c z_E=ABUkO+ESX4jnc0P5${I`a}Teo9whWi;s{{FUG*^-;otD{x1aXT9Fc>Dkw#g&2>j<#_V{&_5zT}XDS25jMTeZwZ1mO za)w22=fNlzLZ3;(d#7!7G43N7V@Z$IopSC4ozwqJ8y0{@(pWcmqbpK@zSsl~I1i`& zd=Eofut+~;jxWu_ffvMj1T5;S{a=YM4-89lWb^U7g&nBQl!W za#a08_D)Ji>z2%AnN{4(DFj(V^@u)$Zy0ZL;Zzao@+mM#p>I{F11Gd-1KLI5{Sp+F zT#MS=Sp{p$WEu%}r^xnHb;G3}@~qG8quD1@6o=2So4RNi2?&1@HM;{Q(xZ0X6X+BP zvFvRwcs_ZpPHS-NN)i^LM(c_L5sMAx#Ph3i@=oEL3d^}|9~O*q|K5Y1@9>p|YuP(a zZ^y&^Mkx^vp}TEH+~Bg%0=0XhkA8&Plg7Azs%~-2pGs84s{3yi*1+sTHiKFC0n3jC z#Vwj8=Utu8y}!ROnT&u2MAJtnIGm5H&4{Wcv8LzSR8GYMa5AY70gM$edBA_A?FWL0 z%aM__y!DdjjYf-O7ohe62Y>trzvhgw3Z%{H8Hia&VxEZhNyVB z{m~XFf1Jv6Kn98WCI4fUkbr+l!6~x=_7n#SCY|a~@VpRr^*J!-Ut4Fc;S5gxKtdJC z7*y=u7*hR6b^o*L9nbL)0>tUQ)Nk|)1idyU4)i_~>M2gineL%^`ExmXZ9cDLg(jo1FLUT8u~la@f~JdWu1Fhv zIpGz_s}+>d9zGwm^!ZlyQ(V=(3P`Ra#wE=S{@%Ax8k&5`;y0O_P4~V%$zm9MX3#$6 zuETPGpaQ1#qQJE~-TcrYGghl$h|3BFNznlA?>4=rlo-IRd^ZP3t-#68L{_smHpF%v zVauq}=O09*|H&QQrZBh5&DZpNHtM#;D4gmB6L?{B2!3|W$^lr89l8P6i(J*B9%oOp zlNLH>#`h|@<=exZu(iy3kfM!`cI0(ndpR=NM@i-=mZJx66wU5=3a%8)SRWG=Iw@Uo z2^tB)U{9;vTo(W1`IX%oWiz?id+eE4_0^ohvVOY^`B^v~ThOwuGxwwFL0ivyL*ybD zOP2v$V6U-z7y(gL^~P{u5U=tN;!J3X>>WH69l5z5H;V^~ zS!MQ|y4LfiETY^k;S@cC>^6)f6j9ZPPkqb4`{b8cn`7wP!(v2j(+f{q|E)XCKD7dQ zuH$!nlUq`|rKayEVs5B7+;`#|2w0)c@CG}{J`$l_1EF#c-um%~Yq1D_@+*JmFkSo2 z!eDXWoENVv)eW9ZRA}I)zyEzZQYPCCO0S%4t4C1SEkmgab}vf*i6UG!=Pw_jixf@# zdjsu^PC<&gnEQxb=I^9d-z#cuOO+cMgkzY<5cPHP*~PziFp?(}^V;06iNw6>2a7$o zo{jxf-TZs$+=hqD?s03p(;6ky0NVtn+`v(raIeF|M9S9_fu58W5^;A)`${B>(@lRc z-o6xg3XAQ@tbehkH6d`;U*%9)X)!==_MRadpRyf| z{BgD$Y^K8w@lYN->_L3VTKN=G?U+qM%L_PY1n?gCmG`@AZ%MS(4;|*+K*eYH7FdbE<7k zP{6v3jD;et$Pgwq+*5ZVe)rY3Ooq5xq6*E;QZXfaQ{dc*nE;nbZC&fr*}t_t%k$JO z(jaz94xx^!3UbpnaGAr5-fX#PZD~+*{K_ zG2}@<4|Qp>IJEBnm4$f;nVFdYWhNR6@!29F0VYpqRu= zyDC;PI1{&>Kx%vt6u|ZGK&ZXdJrIS(PFf5Wv1={l@hlakMDCr4?fsBF|GI`5p6>_D zXs0jM+@3R?JjlP>dUKMSA!a*$&~)b*>QPqaUZr03OKws&bvqGaxJtPpDo=3{_)UTt zKPS8}-n!=UWYZp`NXcE#y&4g+?3h5?oNn+ULb>j(@?^km4zNc{!zlXUE3l(pQPCji zXeN3exFOHFBT+&2!}orF9yhdH>bN4J)+6xaA++c0?-E6I!#P`n(tls5?`736;)UAR zX254Bs?J&9(j~T-9gl^^b%S;;p4kL`~C~Kn|PtwQE6yMi7&b2u=x8$_Lph zPDvW-RYHxKHqTc(fP=uhf)cnu`Yx0tLe6>({@)LDi^^O|@t&0j_}>7oM|a8M2-6VT zmNIxSd2U+ia0IWj2XGx;f)SXiu6c3uKLmSCO#l7OzXP3nv_<{$bzSBhtykdACW!FX zrL}z1$3QL9y1P?~I{1^FU7pHXoZ^`O`F4^>l+s@nw*r}WYFCY+%yfkx)p?-oj)A+h zN*ldr6noh)2@A0y6;K>3`~_C*o=~Xhp>WWsu|k)74+4J+gQB?>sS|U3^By%lcU}#E zX!S~{m1^7~Dz9U01DXN@)N&5hc=+Poa_A$}MbC>1F*9Ag`uM`dk8CFFx)Sr*#puNJD7jyN3P#E3C3t=x@S#GVlxwdz<>3Jdv`X|366Hxu!YE#F;qL}6eHAdRo605?V3v%^t#iK%%uZl>kpTt_7{VV%gvD{3^ zv~`@f;T~;OnBh(Ef-0@}TzoGu5PVzjTD^6Lr^UX=L^!enkrEa!=p%lM{d@0{nfkd8 z#~{Fi?EZ#~^j%B6ZWXY3xmqu`?PaM~B^zM=cVg{vZ^^?=g@#uW!oiV@j(TGJY&5}M z*{82_{?q@yPL>KTMF**NL|`jV8Nhu1ffK}pNNoOGvzut+{5gYpmyWyf&`G_Ho8_%* z4QisZweJPIpmrqxLjoC7C1G+9SidCSg$@7pApyJ4k6oJ1byW5ie`yO`0s@Bbe!@GD zi5cRiCYJ6tccNS^VIFVx)FDI;>9^;iTz7I4lgX7CZk^i?C1UTMGrC|P`N}~U_1G!X zbPcY~U}af6l!&}Zf&El-#X*UcwUzTHBydK&|7uj+q?Q1s*-~I8htW|-gdajo2VFW-kr`#WY1jYs{sQ-Ixd#g>YwrdYO@-T}#x~+b@HjYiXMc^JDCJ)~TA)$f|+t!@AnV*mg%WB*7YwIigeIGyR zP31G#d7}?a=_L@e9Y`*@+}lc!C-^RWhO^8hYM(TIxZ1-(%p6Nxt)hgi^8mB!-73Sq z#u^@NUuWZv1JQ&PfFLOYzsv@xhqAjTgj%M7&wB(uZ=gm`!T&z*$q`-*(zyS|AlT0d z&XKo&IRH5N2}n9QOE5LEsu`)rtc)K%oIq-{^LOPX-70?X@LgkBaoXI@oPjyVB#5zN z9G~?aNt(M#%yMZ{A0k0$_UrAOwnq^rmJbt$;s%;jHtgQ@ZY%@$`hwf2?75*F;_!Bx ztswo0%HDnoY`BKlq22;W?Mw#Y(lxO0tW+mrR2Cgpu{H)+;@Bn@#f=f#$aL z*hJYdt)Is4W&d?5*R=U3dLnzx3jarR>`8w!uUjes)6u)@R9FP9*@%fSE6P*tRFtiq z%PV=v$qP2?K3ATU{dMZL$}e)ywwi$gaL?_wz~hI+@>`ySMzo`BAqp%Ch945?ZiUtP z$(^2>Tyo{~iCg7J-M_$!x^v(z#;g{32M=wo_fMtt8{b3J=2ijR4%B<_d0^@B`c%%j%;kVhZha9vf+me4l%h%>hH|z_FTf(=lbv1mhSk1vrAFCS#buoEYl7uxJ z-=3LGgClKaKbBVbhej_TokK9ij{kDotIGbK&9j*_@Y+?wM%$Zd=Wo)KchE zDAr~U#HL#Ug_Lp1!wHm2U?ZU_>oC=Y7D&wblbySTd~$4CI#&VNT?S4NM6;uWik58& zlizrxF?n>0(6mc%8L*R>0=_|Op-+x+v(^|zVnnnV0M zb=}8DY+Ky~olIcPS0{2#s@l%);RfW(|0!nLbFbtB4X3|! z-`ntQleyGa^n`#ml(-nY8;VK({UY!wDyaF$8wQVKpp!g`hzxQ5E1w{Fgt$~c@U~~y zuEOFt`VgEO{@?kVXP*Jwq^HUDN2c?Q04q8p2!Rb$#{uZB4!l?vuy%tu1i%B2XJGib z2`587)1cUe4-Z$FM~z}Uu2~JJ?^kUH=A2wQ{};?TI{plNJ6lh!-MDTmRsE^R6Uo*y zdt#!Bfm~1S-wN@QCTuC{cVi`>b`r~1tkeuF(goUgpYnTuZ@6!9Iq@mNrh1SSHm2eX z(<)oK;#RAkl3!r()Dl~%7WzzVT)CvoRxiM++o@?}Or)-|Wwz3qx7zDDX%I@Od}Bfg zs|-x!b1AWYo~~^PuX_-8JXLM|3|JLtUd){a`pJ#2`$uO;8hN``6k~7+cCIsa{{KLc zuLihrYKVRRo$W)!R; zlUalf0-Pv6hWbNq0hMwI{FLJUB*&!eZ_~Rt_KTWzKbGY;1I;~5W3bDr(^dNSV2Woh z-zVMs_tD6e%omR(Er!sT> zmn7SER+e^xeM zYY!g?d(jTnLK|C@)p{TXYTy>`$CD;>TBx>PoR& z!#4ntt7}jt?jv&{9-vOnY{FTJ+o=El&|9Pc)k37 z1RDH?o@z{3H{ZGWqVhYusg+F~^=!)MPld{w@P1CU_nQ?hFPWXPA9oy^aVU9|=^+0S866A1y-#ON z7B|4;V9hv_P3|MDioOud$7>VN&BGe_VVIc|7x1brBYX6b`LEHt4?I3r3g{CBWRDJ1 zaK0r^5oIR5@+RZuJDhCd66FFXThXxELPB!+mG^bV8iJ+oMDKOs>UsiWO;8VDnF7no?se3?>InJvhQ#vD0YLM}C76#p zRrlA}JI(^Q?$%FDA>|5-Z#6@q;X%uz82G=n)%EMllv<3;&VS?%m%Wv~H zVOS!_UQs`TXbkYbR$e*lQM9RAS-g$a3T))NS4wUw+S8u2F5@DM@xm(9rfo@s2x5z6 z=hz9n*03X>zhWv6l42p5SEl2eQXX>L;*YSleu(qG-%jQAf?G$BF#BirG7OOXYtScn*Pze zI!5-5tA_il*+q;(h8HfOlgv`q)OV}OuI=J0yo+}_IADi^J6$tC&8(HR_^sogumqPD zn_JAK_iFh9mUFO|AA6>9zPX{uGPPR{yTk+Un~ftgedU4U-#B_~FnffEVa_qUbtPBm zv;6*vO#_be%Oiv-i2$+q_2s=k0in4qi)KIq!*?k}h3=&fcDq&JiNy2n929o`0Fc~q zdesXqaLnjg#IenGf#oc0tEyOC-%f59ukfEJ35=hkrfH=kJ^N-FM#E@K_cll*rO4kP zP|7REgnvv6A4LvpXB#vbK+M-xmlUkLYX3AeqH?>z$7yI^8dQGR7i5z&rT^r44!rGt z%w$~55Xz`zcycUeff{VoAM;EeP+)HwI1xtvt~?WuuMD=n&VAD0Zn9^oysGlf&&8oa z;^lU!ZeSH<>LFhV$gyBm25cC7gq^*#jj+aewzT(7+1FBqIUgij=Spq) zX(TL!NC>lr(T6Rz{>;BiPa_g01dp|IYLndkdHPL|NTg*LCLxYP3?5W#sqsNS-bd}} z#vHz7FyS8A(jU}7%4D-em497)8{_ohA^Zm7IrAvDIa|`&d5!f9egnxp=09Qb9G|Y) zQ=;arJHCSv0yBq)!*QWH2^G_TJljxw2>;_4y>a`GjaI_L!ny|jR&*TZJmyb`R3e7&*w()+cfx@(<#pX}5V8Ah?db$9&YCPnupQJPR;dwu z$HCAr#01s*-7F06Kv@u5IfwOxBnA34p;K;_3IImg`Ruzo3)9yB=epT3pmj2l9E*1Z zgU-XdWHW(nhhtnY36o4-*+5ta>}kQL2)B-(z*%*)Mg}_5YP_GQSE^n$cynJXPc-l5 zz0h`1@F0r)cM!|!KnF{1Asve8N`YKX0w4LBUIRu<*yU$$UvXL4tuQzP1g?FJ_X9p(Sk8{D909XW` zgndy*2s6zkPC#w}NqQ%GdmG@P&%xF7TJQOHGY6y1OvJ@fYpkz`GKJaD%wibXr_Y87 zLEhN~~gBL`ZwNu0qlq|aw6={ z9$wyUI(%StOut=Em>b76y=(4R>{TSu>(+sek}wrG1438(jxjJMGdtF>s16C;T$UT4n{k9&Liy)!J5MDlHFVna=kH(dn zL6S(H;p_mX{F#xSn1EJ!z6Ay=<-{>`XT|Mt%t z?kD{+s{UJpiF0G?7UcYr30n@o^cPX{v`_OD{#_~E`ns|VOuxreG+e~xQjL@7a| zWE)qGZ2ceC-oHmQLMYFDAN{ON-o&QE9mIXWIEa0{81(qELZl47bM-kww-g;Lo@RXd z23^p=?6Y*ATHGbl#g3IvU@A&?;tJ2M4tdSB^ud!0$Hj9Fl$48A4&0l9e|4T$&40f#YMs;LH0yT%>xY3< z^VOSMU+umK$!6Sc9Mvf;WDJuE%QEAaTCs{Pj&csr-OSN3V*M+lW3Syu^~e*l;bXL0NWm~kkt`hWd>GQL zz&b|uS}53AnazBKP}abm0b4@hNd{SA^FbHclLn6P&+5f*G-AKbxxY`uI{=uQGH^`I zlDvZYkMvSw`hrbLBi28Cs8(@-^5+0f{0=0qy5eA#{(Hhu=d_@oqka#~W31d=Rkp4{ zw486Ge2i|tN;69f3K$!WRvYI40bMdbrsZKi`2cnj?i-xAP_S5UUa>b7T9mgi&biCR zIGfq#lynjYK=92uNB`H7tx zWR8f3>0fz$=6x1Id(k5#4fqNy0sp8anl&;>1@LmyN;SDPGtg6-2ZGpSCK+%5;h~^_ z4w+<&Lr<+?CWta*a8y=akINk`?DC@F?3?B#jb~t5_M!MLrA*@$@J|RcI zrE=jbA)k1ACfB~AJUgk2qC+krClF2z+%9NE7I_~3s{Vnnx z)db?`)(i8Lwcf9VIcPaQJ|lern!qcDIbX0Z!~5AUUHyQmsz4j3=mq+lX7!KOsHoKg zDhJJ}7(CH_U?QsGyyMi!ev1=qO}R5fi}!>k@?2Q zn8XsiG<3p>98tWV;m$a*zsgv1rgDJP+?irYs5k+BK&vQ2UO^m59R2|-0cW>%;5)!P z@P330ud4QPYHPw!K>b_``R>Dj5>c!0dG})d{+RXR|2VD}=U=6}z`Ry>dG8C#erj(> zn!IoO?auSi+rv{Y{9zhmIJCl^UTHIiA;)h$&|Xy)Rd0SBl3E}j^^QjM4pHkZI%lz_ zMK;6oC$@w3joL`B@3GEZk}{YpqH|cMU#Eh^KI4p22G_ zx2Kna5su`_7WacbkzxqqleI!&c`RN=d@mU;&v{o~6*36l#n9B5Ql2gD06Rb*-`3m= z;EJ&vgk|mSg#fGE8fa=STX?(va$C*3hO{2$fn*mNOwr(0t@f(s4%Ou7>h$we9=I-C zrxzNzE4c1%wOUdpQ(o8ND!wOCYR9IAoq!=!{84O!rp>VYYU@2J0fsbHcFubG+BJ*j z;#P)V&j+lP*t-qltI1EB7r)M;y(dB{TE9V>T)I2iUdBUh7JoqplJM^QF^2$pPLz^x zeiGp}OUuU6NG`60M;-T298^D}w3h7c@LbR*KF{*^z)6G2M;&CvD{p*2El2QyIgouZ zPGzQ-AFYXnxP%aUaMmH4wg{=LGvo65V`sJu_kLEG$)Bm0`+WUkM8&|UQf+d-uGaHX zy6=U6iQ5Yn8f6+~#I}TiU9eN_ww;!T=`(3nIHf#szhy5U-q_qs91>M4;d8f{mVdBi z@!HN%)2>KxF^^9%PqP#sYB{@sxEDy-Rdq*MTo0Vm2rVbpO_~jSb`}^xGn^~6{n|qI zoL@Yf%nE5QBvU@7iu@5+c1Qc7w`US46O$ulQNoxM#^ZHW9wLmXYN-Cvw||wrHbAb-D;cip+&MC-6j1}%(ySRZ0@da-o`|k_C@h~Y!KQ6g3NPJJyLJdq@ zw7J}ZwfNdY*<#oRyC*9%Ch8UfbQYOOU+3?3lm;wQUY9p8-udNrZI8K;!a4DBt~4;< zWuP)K9wi`J8cj815NDcJBinkMDLDu~BSF;xEc2;7igw0Z*a!ELCHL4q`VMPsgA9BW z(>A7x)DrTS01%QK2)cPRHo2QS(N?rdwbxJB?p> z2$*3yrNa?K!wvFOnl#{nI4NnDJsE&rpYZ8dI3-Fb`kbC4&}Lib87I!ZZz2}2D)FQj ze7Xqc0_u;Ytt%~2pBdZ8DndNt9TPYjz?BOAI*rAgT-04+SxrA-nq$5cvP0>mwKE$O zgEu@*XWYPmSl(${GG>r z*@foP{Hc}iaZ∈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 literal 0 HcmV?d00001 diff --git a/assets/dmg-background.tiff b/assets/dmg-background.tiff new file mode 100644 index 0000000000000000000000000000000000000000..1a41e571f23b7c19f8a9cde30b39c1197c050bad GIT binary patch literal 51034 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 IkRLz(1%R%GsQ>@~ literal 0 HcmV?d00001 diff --git a/assets/knotchy-icon.svg b/assets/knotchy-icon.svg new file mode 100644 index 0000000..e8e21d6 --- /dev/null +++ b/assets/knotchy-icon.svg @@ -0,0 +1 @@ + diff --git a/assets/knotchy-mark.svg b/assets/knotchy-mark.svg new file mode 100644 index 0000000..d479352 --- /dev/null +++ b/assets/knotchy-mark.svg @@ -0,0 +1 @@ + diff --git a/extensions/chrome/README.md b/extensions/chrome/README.md new file mode 100644 index 0000000..650bcb2 --- /dev/null +++ b/extensions/chrome/README.md @@ -0,0 +1,30 @@ +# Open in Knotchy (Chrome extension) + +Adds **Open in Knotchy** to the right-click menu of links in Chrome (and other Chromium +browsers such as Edge, Brave and Arc). + +Chrome gives the macOS Services menu only a link's words, never its address, so Services +can't open Chrome links in Knotchy. This extension hands the address to the Knotchy app +through its `knotchy://open?url=…` URL scheme instead. It only passes on `http` and +`https` links, and it needs no permission beyond adding the menu item. + +## Install + +1. Open `chrome://extensions` and turn on **Developer mode**. +2. Click **Load unpacked** and choose this folder. +3. Right-click any link and choose **Open in Knotchy**. The first time, Chrome asks + whether to open Knotchy; allow it. + +The Knotchy app must be installed. + +## Publishing + +[STORE.md](STORE.md) has everything the Chrome Web Store asks for: the package, listing text, graphics and privacy +answers. Once the store assigns the item ID, set `ChromeExtension.storeURL` in the app and link the listing here. + +## Files + +- `manifest.json`: Manifest V3, with the `contextMenus` permission only +- `background.js`: creates the menu item and opens the `knotchy://` link +- `icons/`: generated by `scripts/make-icons.sh` at the repository root +- `store/`: the store's promo images (also generated) and framed screenshots; not part of the package diff --git a/extensions/chrome/STORE.md b/extensions/chrome/STORE.md new file mode 100644 index 0000000..7683bed --- /dev/null +++ b/extensions/chrome/STORE.md @@ -0,0 +1,76 @@ +# Chrome Web Store listing + +Everything the developer dashboard asks for, in the order it asks. + +## Package + +`scripts/package-extension.sh` at the repository root builds `build/open-in-knotchy-.zip`. It holds only +`manifest.json`, `background.js` and `icons/`. Upload that zip. + +## Store listing + +**Title** (from the manifest): Open in Knotchy + +**Summary** (from the manifest): Adds "Open in Knotchy" to the right-click menu of links: opens them in Knotchy's +disposable, isolated browser. + +**Description:** + +> Right-click any link and choose Open in Knotchy to open it in Knotchy's disposable browser instead of Chrome. +> +> Knotchy is a free, open-source Mac app. Each link opens in its own throwaway Linux virtual machine, shown in a +> panel under your MacBook's notch. The page can reach the internet but not your Mac or your local network, and the +> whole machine is destroyed when you're done. It's meant for links you don't trust: unexpected email links, +> shortened URLs, and sites you'd rather not open in your own browser. +> +> This extension does one thing: it passes the address of the link you right-click to the Knotchy app on your Mac. +> It passes only http and https links, can't see the pages you visit, and sends nothing anywhere else. +> +> Requires the Knotchy app, on a Mac with Apple silicon and macOS 26 or later. The first time, Chrome asks whether to +> open Knotchy; allow it. + +**Category:** Privacy & Security. **Language:** English. + +**Graphics:** + +| Asset | File | +|---|---| +| Store icon, 128 × 128 | `icons/icon-128.png` | +| Screenshots, 1280 × 800 (at least one) | `store/screenshot-*.png`, made as below | +| Small promo tile, 440 × 280 | `store/promo-440x280.png` | +| Marquee, 1400 × 560 (optional) | `store/marquee-1400x560.png` | + +`scripts/make-icons.sh` regenerates the promo images with the icons. + +**Screenshots** must be real captures: + +1. With the extension loaded in Chrome, press ⌘⇧5, set a 5-second timer and capture the screen. During the countdown, + right-click a link so the menu shows **Open in Knotchy**. +2. Open a link that way and capture the Knotchy panel under the notch with ⌘⇧4. +3. Fit each capture to the store's frame: + `swift scripts/icons/frame.swift ~/Desktop/capture.png extensions/chrome/store/screenshot-1.png` + +**Homepage and support URLs:** the GitHub repository and its issues page, once the repository is public. + +## Privacy practices + +**Single purpose:** Open a link in the Knotchy Mac app from Chrome's right-click menu. + +**Permission justification, `contextMenus`:** Adds the Open in Knotchy item to the right-click menu of links, and +nothing else. + +**Host permissions:** none requested. + +**Remote code:** No. All code ships in the package. + +**Data usage:** tick none of the data types. The extension collects nothing: the clicked link goes only to the +Knotchy app on the same Mac, through its `knotchy://` URL scheme, and never to a server. Tick the three +certifications (no selling or transferring data, no use unrelated to the single purpose, no use for creditworthiness). + +**Privacy policy:** not required, since no user data is collected. If the dashboard asks anyway, link a page that +says: "Open in Knotchy collects no data. It passes the link you choose to the Knotchy app on your Mac and sends +nothing to any server." + +## Distribution + +Public, free, all regions. diff --git a/extensions/chrome/background.js b/extensions/chrome/background.js new file mode 100644 index 0000000..d027fc0 --- /dev/null +++ b/extensions/chrome/background.js @@ -0,0 +1,13 @@ +// Chrome gives the Services menu a link's text, never its address, hence this extension. + +chrome.runtime.onInstalled.addListener(() => { + chrome.contextMenus.create({ id: "open-in-knotchy", title: "Open in Knotchy", contexts: ["link"] }); +}); + +chrome.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId !== "open-in-knotchy" || !info.linkUrl || !tab || tab.id === undefined) return; + if (!/^https?:\/\//i.test(info.linkUrl)) return; + const target = "knotchy://open?url=" + encodeURIComponent(info.linkUrl); + // Hands the URL to the app without leaving the page. + chrome.tabs.update(tab.id, { url: target }); +}); diff --git a/extensions/chrome/icons/icon-128.png b/extensions/chrome/icons/icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..653cb8864a2ecb93710047c7ab1073d61cb74088 GIT binary patch literal 10908 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 HEW`dE-vmX2 literal 0 HcmV?d00001 diff --git a/extensions/chrome/icons/icon-16.png b/extensions/chrome/icons/icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..07c558cf8e7807aabdc07e9d652d28e58664ef25 GIT binary patch literal 866 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 qG4n=W`;LqLSA*c_C&rDaz0xDe{ zji&?zHv$O=h$P5~fM33KzdZ!L2z{Sws}WR>uUsW$=(iU0eUNaFg!exuk#0s<8RT@6*^K*IeNs~Ry~ zAI_<(JSAy%pJ10aQsuv#Mb&Ss^yRU|$z8P961H9r*YYLa=YLWPD*<7g_WDWPF4}<) zPv;$doi-gFm!}0*qwKx>(x{}$WJ`+{|3lxa3dmMsa$<7w=_FHz#rJ8Ot5vqvW82Up zQjQwsi;NJNLN<6qyNKb=4Mt~$}VY_v!L?>=pH^6wXH_Hl*l*T;b= zwd##h=nwAE9FeJM!10<)2leW}_NxZqriaSFapE+&LbOOp&8*f8_x8p0vCaSgkpE?! z%x%YcVn*O6zk**2`6hkV;yPcXp_v;%9vyK_JWBdljyjs)_?LF0_bz$TmnI}vnsRTm ztI)b9#EW2_Loz}KlNS0#gw^p1-c;J7nO+W2Ue&>+gAr*?5$vvmc@GUk zdw=tnQJ;@4fCUFisV|Ai8y}!t^qQkKKJ)OeS~bD4$*AC$W`Z|q>4nbXiWXw)PiH_0YvIfKd26{!Gc#tV>JBk#a ztg+;kEP>^v+>-6IEL)>;D#>k86kqV{;GK1glEZXjQ-^C!6Oe2-&&IQ$iwUi@4s&ls z^u9!4o*4TqWq=W*!e%cyF4t$A*r#4uYh|2jBl{ZbaJ4vuXwxJwjJ+tCcyCl}>ExJ2 zZVEI7A4zVR4oC*Cn+c-WL~iOKCmMqHXHH6supTy&=e9$&5T(D+qcuvn)B{EL6a&>{ zh}B=mUTasEp@EBe-}kPL+BkCQ4K5fv8F7gWG5uzZDcD{&_)}bybgct+hn@8uQ13M7 zG}$)w)o+%m@zahS-ABQ^{xS;y7Yb4A+GU=xqm`Zk^Y~dHr?D^~o zB2eP*cYgy_+vV4OTj-ygHlHC#3EU+&xee2rr|5t+KWy%pXS2qPdkU^a9_Q#`^!ucu zQVWr$t_ifjD{3$720HgJz#N?R-63CrQ-x*AJQencxtqO-x^k>%*^iTiw(W#@x<#c>-S%6>mTOK+C2LlqjE-#3nI!m zCcD)$Q;*c6NsBdr>x6AO&NzXOyw~n%oR`}4%f@p}-1UN0OmNDNSPmmYIIn+LQj)2xv{P-{K0o%#Sxr z>OMop?}x68_F+Ahtlu9}ZQxYR&UToYeXzS@>EP6^6|K>SsB+#Sa&F~(1vFagXluM= z%xgukg;{{nBB+ro?ne`?4&=mpRh_wj8kb9j9#yu|*;E5}+oj99N#$~qAGEhxxYRpl z4qu!;{2q0Y3I4$PLHld~^Bfz2ZbQ$Cl(n$Rz1H2eoPq$`Xnm^#Hu^um^Y4|1>gS$| zV@ER1(+X${I0Iqmd()6BjVL8>y4YL8Ho2!e)W4UNV0AE5`tlfNsU)+%;80}u4>_! zDUZgQn4XICL%)6>vV{pveKWAF#oQ;8i(2SI4+dq0;On8yQQ(EW{T89Z zZVm%Bum!Lw-D-q=<1e=6Mc|N6a%j~b>GJ-_|NZIR3R;4^XlL>#4QI`6`!wJ29Q#j2 z%YfA?1*A^gjq2x9%r)#j?aX$J<7r$~+p8G=WMHoj^l?2ZRBs^H15xTxMo;e| zA|qp9UY(46;?s%!M%am7$Gp2ZJ~~~-IbK$gD9PpT3=OnVm5^OkSi8D^3N`r!^Q}HH z@oFj9hmN(f&Bz@HT&+HHp#nb>cMLzYRlz^-lxJM4TvG*aw|;fTB)(zy0#LG;n=CyF zBl&=@_w>BT944%6$C&|ss{2uc2iAyNgA(hBp3@wRgzt$@FTWwShNZAu!>%6w@n`=! zg2D~%X}woyhSle`ES{e4wPFPiY1DC9&_8$GoSxgd z;3VtuWY_WH8F{Dp2}1m0#R`5Q$JGZbQnN|#0|j|FoG>GppROCgK0j88jOatpdVP_! z$I8VMaM1#_T5iS(dydUGSwZuI;~53|qG12<46!?8YCk-in_vR4ya!b=noM2iVc4+C zBzWlPZk5wp+&H=G!D=>)Pjz%=lec&cF=}lWr#_`o7q=Ygd3wBH;T$J^x7Hah;bT0oK2X)7=^4MAIUcL} z)F!GC>N;+Rtv{gM+w!S-VAtQ&iOn@OlXs$7yUHMZt78po!uensNCNT9eyd_ta2nL} zbg1}dfNjAH^2Nv7F4I;Z&FzTq<4K4op0P{DIID>7br;4Ajw);YUfbCrvZ+!S67?Q zF|kg5DTWYS;A6tm{c?Kj6?q@bJ}Ervdn88QK@BI0ia%||29Ma0ZBlRktv!OLe#LG3 zo+3G(#xv|TLB&w1SD;6N2#U*fhL0W5r_LGLm0;`ErPizE91s-6_EezwDLXwcLs9{s zgLtr~No}0lTnFqM6=i}E_l2kev?8!}yM$^$DnBa}9O;oR0@4dd7&iS;n= zTlFZeJU4r;TJYmw&)GL5;cW(KD;v)w)msAQ?Jh%ePpg7Xx$rxJTntAqE9d4j+z`5H zly!nO=mZ>2@TJCGrB_?bKiTphAxctQDwq{@PTMZ1l(4!A$WcW>SgJgO?iPz2DhY#B z{OYi=5rg4|Q4&_$9?uP*=A24r->$jcYsvkg?zhUbtO%fy)Nw*Y&9GH$68BNh)M$<^ z77>#1g?rn*WvPA-%#_z}&!r|xS$8}6=kmeH)N-Ermyxaqq;vhNWWVmgHf1}gBam$S zU9co0)m+*>0Be#SzPM9Q5dmb{2h!kbAmq8o=R2o~OzVtP2_uyscuZait^{9c+?>IH4)G*IVQvAusB{0l<(!unD3ZsudWjOJqI%p@=Z(x9DHa@co#CKoW zZj8!7uhOziFKJy63v^s&dLm?V#=$3+v&O;fNm6qqoX?Y~P9HG33uBE^sxXEBIW@`4 z!hP;Bg&L+wtuv8)EcvOG1!d)YR!_6RM+;j6KjHa_NaNxn+a!00L=zZ94}AQyZvJ+h z^Hd1NDY??T-TKE-QK&>|_lXw%Q*WJ%(GL8j+XmUGkfYnDnzhOkh)e@2D)=o+E0+Yv zq)f%h#L;$fn2=z^1wB6D_BcO`giu~Wo0k4BnG27xY!+v4aq`(G?aSkM!YxgF7U#Mu z%fXX0DYUjys%EYp-q^8wbdUy&E3oO@8S~Vx1>d+E%vAb4i9_rrk2p<$Xcfllx8&ry zoLBYcO+oj>vJ%(e9?x)+*Imm+hQ@vH0#V}3>i0JNAXP3Emz8vIK0okgZKp|BaRs!; zuijE`=q}$#c=Ea*9y=J#e(p(14vaw2g~sDe{8<5?Yl7D*>CA z+0CJhxE)01+(myNXg01HvIfCq&>(2`obTM60!8yU4KZC0X9V%y*0;$~?fw?ChS}j? z)86ukfNacc#K&qY%OsjRf7LJQ!fLnVZFE9)s?vDps`ag@@!Lj^b$$#pKUXL5db`QN z{pHE>DL;OgZCw!8!DCN>@;8jd(2<o1J=TBYJFWX%hJZ46#|9`B}1EAp3_|B9tOZ4@KPL!5y!K&6rJt zUz^U+!lHMKt0gK=wkzwoapV)9t?kWs#w}dcF?1h|nmTI#lzjUWd%^oZG8m@70W%D` zov_KZ25dnqcNWN-LcfsIz3H$e#Iwf7Gw{6uF}%25zZ0<2#W6?fWNA=W5h zDhrHuBky(W%4ms_tsMLU9?opnDtG5(%4KAdCGq-W^k%WbeOXx@$pg)&pQD4L6 zt>(%G%|wjup3JoE7uH0IbFdNNbF34v)o%dP+;0yQ4%xB@Rt&}dz}12Kvp>*Uc{E7| zO?pC{$~S_fs(7C>6)ccf{xeQqEUJI#rC;{w_58#aD2B&TZ$y}s{nlfaR$HSraZxEt z26h41eYRu5|JZ4Jt){fqwzw?aCgalYxqCHHo#>$ErZwEK#QU$#i`R&@1k+_anSWB< zy1_~L^2U;JYs7hL8dSb4CM0xLYI{BW%&;wh*?$!|%teKwN^k?4!aamALyqp$xzAXW z)NNJ-1uIaJ*za`SiX|=?(on3owHs{$j8|{V0LBkfgp(=`E!za`*L}LJ8AE8OkniP) z9glS1cn@Iy9m2r^hsE~YT$Ik^Yjg+3ychn(tDDodVWsnC(>82clk9Ku9kC~2I`xRr zS6iclHNZ67V`qMz|7U5gLY%bYnaTExIk`pmu@okm2C6{vKUFltv>_Mn-)6|%A0K3v z_sRBv8cFuK^VW1Rr!U?Bd5YRL-@L&rM=}NHRe9pCW`~t8MzEr`;`PS_NPw@~mqrd9 z*sWQpLVFK7bEWq2+k9GcSgKG{EJmJ(zMf(=Y-2PE%2J9)s0zRF)jYcSC*FF8a0*qz z%7iU#YHL+a%Mjr^VE=$a%LBtWMYPWZr9BaIIS*&&maDzT4H+XntprCVv)}Nj1gD51 zMl%HCDK34x%AfSw(2D&1hRa*)L3g4v|8dlg*gY=d1m@-Isj;7^NEIzZHI$R0sK=}Z z?a#jgp!gr$C7d2zqBv`^J9y!_FV(Ugg-vw=%x~mE z+OP|}?mfi|OWjTfYwHVBUHWPO@8s~*wGYlKg&3vF)KkZ?XK=$(-x*)m2PIId(m%kZ zPD{SHnVQFa&n`DpTHakorSvQR~-5bE1CKjj&ve26l`s9LCnh$Cu?a!W4=+Z5};^(*PUFO?Qi^S#lMu*f5Mh5 zdj;bOB4*G&JVklaBMe6daF?^d&|mTY+*JpC9R2<$0>V2G%;AK6e<2mn4?oWW|7b0- z-yA4|YF0ja+!fe6nmW8Cd6+tyKjZJ_zp0j6j%r$W?j?1c&wvkXrNTLK^WTkceh&S2 zFVt&g3Q&JHi3X0#|4N=sU~w<_&pm9(?%L4*O7?fq`hVq-mq+cl zavwu~B5YsVRpBdLFuVRC3mLv&`Z+*j0 z;2o+3KewgIIJxbWknWRs6{j9~*VOYLt{vvr0cY%s?GgW|!ER=vaV2%b73(&WT;0-H zViVdZ81kLZ>eAC0%TNCN_@>vIsZxmCd+O)k>@nFnQ0_bQQG-?tgjUVJUre&qvOwco z$HfVd8}_fMtPT^IgV#nee#$PUbx<{Tdn_{Y%Xf!vsxED}K2S^o=E}*Ny+nK|W!&m~g5JsKm3r{|~zW@}c19u2i{#*bu^-F4fXaRROX)6K7# zVxuOC2frwb&#yZ%laR*Aa+Od}-^dIogbn~hJogxe8qpZ*w0%(pXFDjE6brfZWqnw} zfqQv8`0c*Qt1prG&?N;2@ks5ss@X@5Tg@iZEw6ZFy`@MLL*90Zl6<$vU@e81Q!NSZ zMDF>r399#PZP~nwDP#m_7MoEXgS2ZQsGdR<@&<4qWJCkp<~55)=-Bg|y_ziY_1>Zc zsoNdxe0MuUNtA}s6BjS+9@r5Yl2?zq=TUOQ;UV^nk(K}}_SE+_9V(500(h;Y3-o2J z+bC_?bhj~6{025Ew0JemL&-hM9_mP=TV5}bM@l|xvMi-$*M z255H2x5lJ#shTmqWrypVX7##987ku8ZQ|RknCvo;I-vCl?B=_ddt1T zH7;peV_ReN_|zL~vF0XdWe=!kh+>>_T){KqeX%^j$2(+brmOJ3z)>SK{c{>{hcaY{bHfe$eY8^kC$&sxyKgE zq;J59Gx!RT8d{1o06ElH4d0jTk$|=e5QEl02m<>RVm&&*86ha`u*eI0D>%a7S=Nhg z6&O@|1XLtFS?MpBzN2Gd;cmIwI?)^r+o-kY`&N3V@@HN2RAddG4wy8x`X$}sTe;T_ z*pNizUH>dB!J2!pTXp~+0?2`+euOzp$y-dc3f{`$?*-h*hXA=A~< zz31|_7xk-}U|xUkB+bV$9hm-XUmz63Jr*SRm_Q8r2X@>cdq=7~WE;-k6>)8#GK*Q# zL20JNFH=cJ)lzOBrLA zQiAXKu>vmiYG8Rqi}vX?@i_TYBjFEq^_6(4hKP9K_*S1zmy#-W(!{9J;>c35OGh0p zp4eo<(u)~a&)l+Bjk(w{NH1;N;+P;V5wLvodDTQq66(26d=Y7=uBW3`&eWF^6b?0j zxr}y2ZHcX?!L-#|L-(fp$mBULxloq27Ea51b+e=)W(NU1)nQW5u4=&NjGjH7ez|W6 z!I-&J*I8O{`c2M!FSr2FRlZytKz*`Wx4z`VX_JUlnr3TrAeVa)E;}D?b`Ef!USEdM z-v8O$Qvp|kRrOS#cEMscS-)g$JTXgowwHjXQ=NjTtAu}^kvvf!`1IU(dSOs@=GV(s zY5#*vB(MK78^_7ff>u+Zc(&H0}Q+#esQJTnPW0*+puzMO9{?+Kkm~hRh0^ts0 zb~`MclNLk9Pi|}Elt1MHI1q>my(l1YS3B~H!+5ht%ki8<0bMfsmZ=f9z1D_I@dJCn z_>{p}ZagNYS#cA|ArMmW#de^NkNQ1v8RDCXsiD3(MZ1xE(%UtyX5AJ$wy-YwNZ}DZ zxOOYfhQYZEhNasguXrF5#)(CTT-)oX+IhU60P8yFWbe?#+Y~(w@=h&G{Pt&S{D0pA z+~}I)%PH>Zh~Rl#=nzM;Bs&G$*!?Dh#+$1yHK3xg2{feqn`tz;IFalx+$=@Ua>UcI zdo~L7f;jERq@~;s&Wrcdi!~L;UuZ)x96cPBwDSbux4p0G*E0d%07n7Iu$tFzAnHVO zSz^(K%{}Ofq}&Qxbslx^Fd^3t0hqHuv3Hl+7<8HMmr{ebFbSA#kv27W4c4V`Kh>Ok zP7oK{7TvGV14nXDp;Xi3$S#Q#HzIHSVWlegNY{%?Zo~X=X35+OacFejzti(|@FJ%+ z`ppH|oktNEi;mlRD8WMWn~2fd*@kbxgxycMVA()2hC(Qt=<-`M|62q^rS482oL{tv z4@Q(&rh*f-r`TW{0fmimM;$$+Dqq!Gu;--t^cV!pjs?$D0zNB3Xlj6~FX z`12pyE)8S1I;UaBX+26fbLOerOO3@h7!B(`pP2O)EA3yF`J7vYJ3aB2kiB04^xo?q z*K@w46ZGGbu;E=3C)YQ>Zt$nY19X2&lmHav0;LO@9B76BsI01-4qgBsdJ(+3by;F! zOuf|lfbvY?s)J{T8zO8oTR3Gb`$;>b+P3C7Huz38tRbO1sa!PSs+YD+hiX6jsh`7& z{0mal4n>H&_|lUOmII7&y$%=ObLM!%*+R|(eoiX!4r;>Wzv0N~86x8@P`iuN<+_R; zwG@m^-W*0rUW2K`o1_;0mh80!+;3#$5sK}sNx>4+*mrqrxVY0^iwH$-7!f_)eFFsG zms<^A@OsmRIkU@`Z}D+15<1tP^?0I03fVTHWP3@rxQQr zEETEp>bmi2Vu@h8M0Np8&ea&87W!rgo5o8}c+KVe2&X`w`QCuyDW(|3hJ8e~Sh}vn zI|=J}UcXnY`>2@l6a`(a8C^DcAqbk|9~EXJ*^YzglC0>{<=q|K7A2`( znQN3_=Yl;XC(?YBLR-09-!Q9A4D=d05+V|mcHs&(Cnr9)>QC%>TEO8lr}E`63}N^Z z-M?OE`m5J}&Mz)*aJ^u;x|8`!+L);5FO?GSGO?VU1Zesa=7cicEU9~Ikxjv2+XnHB z56sJ2lG>`98<6}QgIkEdU`y(QXWtKzpOF^dT3njRh+4dL9AI3jM5*A@)hcEP4A>gC zmx++4$*(i02=~(dku>2~apmPVrfsWR6A6G21MK|4m`GeTue+unZd;HQqZ+mB_sX?j zyRwz1H~40Xjwubqdce97cXv9Pl&=Rv@3ydLbTs9tG;kx75I;4_+82@aDkZ zF#Vf-t@W~BJc~E}TUXpEaKlFv7ZP^)MyNbB;UjWW%aQBH2FLBCDY<3LP&#Ur0+9CHPbzy5NJ7 z??XdG+Q+mKy2uD8r78TkxjjI0kPhx3v|cXRk8VRpU6@C>QthR2so;dDg5O;HnW#vt zGN4yRr12Kk9^nSx9V;JgZJ2>^)mu8hkIwdJvX!3dVjfv2#qeZE*c+Pu?b)RHEjsW| z?y7}hDfA`F;rrbVMO_!oHkhju&%JfI(xbtj@h|~MdLqUd@PXFiLKwZUs4U(|l1DVw zLC-ePNkSRm9(F-ZB7y9=1T_RCvj5?=_{-+It}CBD=eXb^Zcz1;ihH7Zb-+LM3M zAlbGz^ zq{m~JMl0W-KQlv&e(=QtC&6B;UJ` zz7h6w>R@=c-rLc2!xsHbw>UlR!{X&%=2Ok<#RhKVNX=& zX3O)HJQWGp1~0Cj3*rsW-k8-H*+6B3>|>p+0D?TUbTE8p=3$w|vU@J?H8NZ%!Xvi?wMQ zkM_jzZb5z>pWDi?IsHNGv;Wx`U_zB1lw(KQs7hrP@(ZxZ_aeS2TlAeP%)pzc>b!`y zWPVh!WF&OJttFr$AG_cf*U_nWt$H`aXj?wUD<%%6*GB_JKj7mBR|4~hd#-3^a1=}T z=?^oO7U@;=Eq>FkI?w%uaWhnJg_s&Y;Me_4>IdC^X@C zIP#Ic^1gH8n*nA!uY&Alp=$B{X;7~g9k5tL0(PAy8RShI*Z@^~1cc;(K$~5KV zthF=ze>&M7@h}=KgqsGlThHD>UpodRqU)I@prhVvp53V!PX_8^qRjFY-7F)8rY6TI zZe!o9VD_Pl`5T;twD^Y6&yA9W!myW?-kxOh9BhL^$GDSpj^A3Mu^(+W2zxG-;11+a8#R0IKp;<;_|(h{WM3 zLkQdb&$~yHq0KLChyPRF$-wG42(lG(RB|}r`FL9#a?&dH`ALU589rv%m+duc^;^2l zew*Wu4WIXKY+7DPYIDX?6Zj-I4J8Cjl7^yHTszH`oN~X9dOWXjUHXV*diUxe4mo&( z8`oxq>h+a)Di!VMv3nP#&BX;%bSH=_G$044ynT@4)mLJVY8SETnu+fTr{zczR4Qf_ z9TVbaoofzbT&SfQl1w8tHH~ih^1?9F0+!k+lhR6H`ie7=5D9*^KGn$5Oj@8DFooa9 zQC;hj)%83o_Fu##5}KUZ@g^WzBY2=H`7LV+1A?h4A%AT-tY)KNJ!u4TEJwP|B8ubW zy{2b&Kr~8m4W3ATxgH%J+HNouDX$%Jzv4(EH@~SZ_A+Tj=KA7<)=omeG)Ukk3;kn( z+6o6eiOfE2g8BR~>i_xY(U1-}Q*e&>U;`$zbDn#af;%oR-85$D!{c8`brm-3k}>3} z55fitN6Ji8aeK&8*Nvd$z_)n6!W$(oIGcXR7D0-5^t0I|p`BXxZh^upUEm7`MF7Ys zFK*My`vRy160cXKbQ*j@Esg3m1L9a1fYLmVn7k!UC#+Vtf*Y~*1!I0vOJG0;Ub#0jahp?Z5uR38)r-Lsth@3Pp27?`4mtP$bO!8gHP^`r zeziHfs}_!!_Wf6C^?jO$ROt&nh$gADyXOM?lerhEDIA<&ZYxc9ZzKrCSb7m9HlESsStAV`+ ziQA}qCf#aT(q>V1j5QPvEj<4<)fnKn9@@T3RAz9a|9)xIwYjFEVzb6`0RVH$cZcH$i|`54;S zp>(orRa>xEnU&DC^Kj}0eDK$1omf90jD|LRm+;&$B9o1*kXdJ<6|TGk zj}=9F8m5B_oZgiqAcME#dggA@qk2_&V8PF0fZ42_fSu2_5k_lV?{UI-W3q>^F=6|z zfS}~GIgFjJtF&XF$oF+ZE8SRNAJ%u|E~?yw3nKPPL?1{k4fuj<#tif>XVh4Zm041I zs(ZCq2hvq0|7#9FjC$>H{)G4ZJkDXh-62VOc~KVXqDG0Z1=QSoEO^2=-<5d_mW{C( z70eYcQ<=G~o<=pwVNA+V2NMPNV3%U8cTkKES4QByjWD%wltaj4FF$RkBe&r~)PZ z0HxvKK}~Zjkz3X^t=(CQCK-Mq^4!Ba4Kr@dq2oIGr*Egkx$+C671?plsWu%KjO)wp zkR)%lEs>!JdnH(pk+5WSmESa}7!vm^V1%7jvUMvzR%Y7n^)B{+7YODqMT(pE0 zP#B^Q8Vh*#MkM9cb#dy^8c_?~+x!t5NLp(@N28tL^^UDSHqM8cpcghG4)NOm4&I$e zxX9N4p88ec7gF#?S8hJPZt4b0T@& z7MsIErRJ+hsII9L+y#1K*+;$|PDMN9IL1EOX80tH6RG`{QHtoH9Wlu<^Zj0F3FE&d z+8DaBnB;Kk8G?32(qSYeCBx}99ZbWOo41fyRz)IB)mM@Df6wEV_PX$yhgf1tB(w=L zKfMy%OF9HMS-9r97E!VN{s4O=%o!EFX2Hjms+~^Pyf%E$ncoO|B(Vy() zY7gdg42zrTfK1oH;cT^WUJ_3b39URTIQBj|K=WazhP$r=$Ry+daA9``W*>UgR$mG* z4QR6gs6$7R$4##UB;Rn(H|M-7hcr6yB^@`O$xrQMYopX-$G|X-n2>mN7k|#2U zfyp|J)>xJ(F|<2YeLWJkuElyw7A@KV9Jo{;>>V(>!#W@Ccl3tH_wA^U50 z4i3iA4BF=RsrE_}Ca@IHt|$<&(0}DIfDILbrI%4nb@6KC6Oru~essfe;7x8EOV$!E zeiXzi1xur`R|q)C0%*ib)=e0sh707}z;d2@y01R@c_B`e6uyU1G9JV!Qj^cHO6YEzepJ^i-%myL&u3 zjLrS$X)=Miw`{^z zVf&@H=Cwqy^V(5?%Q=_ox{>bj7jZqAYg zlxvOKebZX#8N%zq>yknA&b#{_wNU@WTIfwcN&K3%l)XJ9GhhX`L6^AFc-brWf)p(c z;*Sn9Q*RwUuduH&d-~10p7n(t9J5VKUX~-zpp$Dp+gMjWks;Tf42u+eR&&!T#84d* zvkEl8#8a*C^tVS}d8PK!rfsJ1vBX|cr$(msgLMDpk6JAC!SBBh-;1d(@>eLlsD-ks zG{c_U>ubN#jw|ZR2(ZKNz+J{(9n9bGJdXKMqxlWOXbk@DRqZmL=ytsM%&5Ev&KxL0 zo@DnxkZr#0l-|7TPl9ALQi0LDS!3efcAGB$+H!)77dMRk7OLEv=oUS&8VWDOPzg_B z$UV(GrGMJRI8#WmC%7k=9(4uz2acc?cqq6Z+g+jgix+`SDn34&hTv0UFN^0xUJdN@ ztiR))Sc#^{>nzVLe9T--h5qcI!gli6N7cFqOc=v_8&61mDP3zw(G=m_2U4~3e*N6! z7o)mhfIjzULuZ zGdv(16Nvble0@-D`BL`9yZp)UAzXOQ5X3_6Mwe z0Ocs!C!lsf(g!$=mVw-(#qUX6Pf8eL#J zZoao6eahnq`h)S&`~I_jaZ9F- zB4IM(yXCrJJUyUVaxNg+vaPU0v%>z7;MFV@)3fwmK9ocoD7&$6lyt0I>HUQ5dOdgN z$B?mUx8XofQ;K*ZWfhyq9(D-ibRUJ0|k&O-G8gd^q{{Hf;f> z$Dns%{zC>Zk}+z4-IRLKtce*Hk2)b1FNKOkg<4dHg=#ao1tn$kdawHFSpM3GN;udY zVhm(iTDWJcb@$E0SUWQ!`t?Em9Z^3a_j5)yGneR+bjj<54H7VIzrNdV*{9&7RD0$B zlHALVHw26XPW#{_aemj4I{CKKoV~+3y~fzn<9NzV>Oh*aOAOxKyKv;}O0nyVwMq?p0`@@(!RlYy3aBnA-K z&Of5Z<7iVbt~>3RNx>38&DL*J^#ZHt@U+@sM|}D|GnI<@dL#5S2K4E=uS-7wg4pi4 zewu5z_ovdkeAnezlWX=|#2%Jl%UJF@@6Z$erho8{?8Kl)kRzDkug@nMI?QGGnW$u{ z@^_cr?H4sAh`wA|wwEbzrMU}7I?v;j_6rT-_46;`G5zU+I8;B|;-3ug`c&NIt5Fs0 z9eeEfo$&!hj!L|Bt2y{epl0!gUaA-V+b4#GU+XxkM?%*C0Bq$|Oo!9hno#Y|AJ~pP zU!tp15||xr8Wkpc(lYF+T}Lyq3{4XJ_AC4EAl9gM2e+BVmCrMRC^Gs3Gr^BDmWVT# zC?v3;j~f|qc2jOYD7#tq;Ux-!BcL8yHmx*=?JxCv+<6PSmI;{So1p#FpS`25LFk;h z@jTq8C>roAhl{?9e``7_&_!E!K$=vi%$M8*^~U;8lkg{7K9)em?M+7#w={33+2zYN z9EW_p3J4opN#bv?A~`sB`6>6!ZETT-+j?_fAZcsm`4kf_J7rp z0t8AR>sm$&=u9O)-v=b8EP&2bw+xV+_Wch@>Za0GT3E5FM4uda*&li%U- zaM*lCZ|sDnBQXbDT(&WCKvYVe@a4m9^m;fKHDXlbP3FBdffq|Ms@=JJ(}OjYP(O#C zQJ#zcp0W2<3T9~D_ef(p4SfqgP4qpcs(iNUB-IkY?bg#8uQ$N{-~R>BLsj2p>k7Ew zh?yVosboIo0~TZpeaiqsI`1U{JD5bLK%E+W>bO98&D6Yp1jISD`3;yfvaL2O9ZZ^~ zu7x9Kt_05ibX?LtGhraRrD`t&QwQ@qZZ-44B4c(g*+`&-yz*O8W8##oB(#$At zoJzH7qWyw{daXwEL*VU2+S>v3*XBo*EIp){jp%aX)j7eiggw?C=xm`(-aS+(?H}L; ze7)YlHQoy`O*{njK#jPwIZ2guG}{Sz5r5W!o`(ERgk~j}s$zsEMjuzv+uMqS2fWqb z6=Hd7U^r&Iaa5!U@(av*#RbX3=&h_ED71^rmW zo>@%wJRAY017O1nnwo!lXA%9g9dML;TEoKR!$$YEI(wI~#9{ZmeC$?Lz9p*S-nNID z3N$9TLiiO)^KM~EbAu}V`$?mSS;8V8GPHLHt{*NCgsbRJQ`c#}zhGk}XS-a6JI~SU zj)oy92Y|MUN+XxZFvo}O+>=;Ecb|$>$(Wmo_^3QK5$G43$qC-9sdSil5^*cgRDge^ z(KHhD56)1x^QrB|f-wEsD3Q@qp^Xwh4gNqj zX5#4n4%|R~{3?sp9aUw94!ONUQ!JRosG=z-UHq2LaG6sNCp1dt`FfTmeN~$IaV!iw z+=_GjFv?=mP7>Nn#}yJ`ZW3!CTz@CLl!O<64Rx+)iy2XWtZ1^WV6W8Gy%`DUWl(uDF*Zpg$s8VDd(U5uiBfPy09%vWt{T z`GcQR7A-nbeYEaj)h3kv;ER(!7Fb8z1&H$_Unrmykw`Bu_MUUO9m!d1xlz6QuC)?; zxAi)rVmnaf8Bv$03dO#rci78ioThw}8gvn(79tc#&Y+=1n*>EJ$KEj3-+ zut5aH2gKVo^z(FXp*~4qc}OjeOD@qmBsL%(yfBN{Cf9f`$hVpljvhZWvQ6(KO~xN zcB%g+LYy#>;hY2{NG5*+vbO8xq&{2%GeB4dK0;ll>!w_4H0h4&-IG`=k)NU&lBvIY z(jR@H(7^F1%z)tZU#a#Lk#ngSTK^vai-NE`;=7VuhY}e;2;uF=Yu#u1Z4=HS&I2$E z2o4;Tcq({$S7%IzF}&T+c5LuAaakNcAzdJy-?6tnZ17$irtac<;j)E(+-kRx zt~F1{6tf%rBVzYOOWzegL%#ZAglA2cEgj`+cG0fB_PjajMjC$Ht<)Q(L$*cqmw*|i zd)u{0M!=lTata<^M*rf8)tz7deHLU8bVPAM67^nMNrc$6cv(CrBJG79->tngFSW7S z5`>mQjd+=X$^e>ILcVnTEAI8p7EMf0(e#t*JlZ)D*9|!4u&TN5uBV9vTUfugTuv{Y zlnQRz6#v=imF9CnAawW|x9xCN5++}@9<1}QMsp@KF~(&-KD}e6cOWoVo#S)Lcl!m> z&PQ_2n433&zo`Ivf78(a!QNX(MHT*WgLFzu$IwWFNJ$JSh{%9Q2+}PrT{Cod2uKV9 z3esI7LrDmVpmaA#Naw^}e!us9|JnUx&)Ku*?C#k!2WGevceu~J&o@5tSO6glQe*KB z-*uCAHre<^J9@5T7!@%QeoJ@=nlm>m??&NXQY?TyFm>pMLk}oh;BAZklJ%$bYr80p={^>>yd4R&eE6tQSw8@f2(d(_a}cQOt^UK;FjhC8BkWQ9T`FD%c5(>u&ymOu@!*rXXffr)fZ(2tC)J*C0M&?=}&C!u+p;@UrtC z$c6~MqqZR8%KLSUP5g>-_aQV4r$~I;6YcreE4qU@*T!FuPkyK`MyuK>7@+uvq_xdZi9+QwR0CzsEsJk=2DGJB!?ER!l7Tt=GhoNz z8NDBJCID8@PMX8DM{cAX4UnIdVPlKp(&)ZB|L|SzPYE1&!7sM!xK|@c+Dts{{<2=O z^ZsSy{u`JLT=zI0n2PEOU&?>}Z_4IRm&lrOp;6l#a|;@*?sd@k$JWUj>kI>S@s9(Y za#D!TAban=tK7@y|IC`jh)UFzly{+XTdHS=x{<+G`5{}ruKlGQnoRFZ*VP}Ats6PY z$@C+{dH7_nB{U~KbJlWIcHBuDJ4qVR&ffuw+&8<&3MR~j>G~`l(^fxWA|Foo!444FK~=`V+0FDpb?8!Gw>O1VVQR^o$$6sf@ z3mVetLO4yx{={SgP0jLEzFQ0cC!Ps2@@i;r8qJ&ju5_f}>MtR2tF>`s-T4he0z>Q_ z%*1)`6GMOfkIC$iogI3OIY@eCJyXqB)}V7BKfz4`9B@XL^H@?RuzC1=+>-53$Gv;% zGgg00T-EacMyM64eHh^P))pJ8zT7}(od0$waN7dLb(kLPJ=<)`LEDC&>s3W}zB@PS zDp8JaV|&I~u>>XVho3Qf4*nybZDUT71B<_nIAt)2UCj3QmQqlkh`|e%ZH};dn9k9U zVbPbF|A1pa7l+{LzR3s}58Oz>347Xm)5xs_5bTFQt5}!r>d?}O7uCwcvFBysZ$CN! z!)b+jW%uNFK)C-c`7}Hx1#x?ej_jNHCGaLH0nFwMQ7fW>#M!c=`Kyk{@#h6^h?;lJ z3Wy#LQ_Pi{$%*MORHVgV+~g3&A+oh0yAhqFtoN`#V1&VmuxJp+KO^VpT!0|wNj-=) z93N9oc*@NS|AqaY2DS#5iC`_dcWP9b%Z8=(oofmvrpx7s)A++pDIV zi>Lx%7T35EJDgbJMbBg_>o^nYGB3Y#lR4A6P14WBI#QEe%vlttTW5C4wwbTDLc$N` z4lKXer~(|X)5}KBmBNMD;UE8(SY`iL>1kOrZ0E{b?~DI0(5h|aMBVs&)7U& z<^v_rYmcd6Ck~}GPe(1nu$_s3l7E~=`(fA+T%yXh`LA=a5!)188?-pzj9XVR<=MD_ zR$gI5^oBN9_L3dQEgWQBky6H5^^(X8BWZ1UPc;3?u`1(Zr1F8DIV(Wb!b@l^N6QDY zo)wimSND3oaFSHeX;uC3g%)1sT3OXf*L`ue{AY{rPboVSE_Ut@7C-+;|1Z%3tE!G< zMwJ~6)JN;HF*%ks_^vAKFiJt7=irIg`E5bD@a2EWJlv#sc!%JR)(^<93H`fPIuuQ+ zgo_h#?>@%?<+&qJwIOuuN6EsOMkRQ%U9g%0E$&$!{X?!`=qNr>tN=m}W^`tGajfWa z>%PiKbZER>xa@x7f9-M>jZMH&#t1WaIqD#i$88OLNa|MS^hq@ifgLN8f!Uz2r`>Zn z;=t=Cw3D8B^g9e_@+yS^2q*-e12{z{lnXSt@c4F*&`q?PrP4kg`!Z5481Da zC&;sZoO(XELep=I!6!Z$+(~`!P!2vZ;zM^Gi9XMh)6|vcinxiPRa0naZvi1lJSRVy zBo`CPNMMgZe4c2@rNE7_EI{ksFlUEw1ANI#~gW&rH zkZZRa+B^h!dvApPQh8;LQ~Z4e#s}VD+fU;;KMtuWPJpU7dDTOyF=_vX$TEjP8YSPA z`zsn|$4fw&|EVXqNyYfppj)U2MaLY@oDToAqnBCTjuGwya4nW4_jcK~zk~;R_Q80*!t&!u;O0D37Tbc31m}h|EODf zb3roTr8&G(%Dk%H?Z&bwCIintzV*3(h7`!ROfJJ&!pgrA#MuBhl)f7VjmZHO4M>=| z#Le-irZjcr9{nZ7;A5sUk1c;@wRATIfD8pB&_q{HviC&SEI*crst_4+XapR8U$2%O z$DSa=or3!n{kKrOj!NA7o4sKAWovE+mD0FFm;GtAFQvU5L5$&?!T)85yT2&r=%qfx zkxw8_c6qoP>&(}OAWFoA zjVEjYH!@LGOco6D&+_kCp}g~felpN6RZ>zz%iPDgZ%w^Oq9j@8)e%rvUoGi+RTaJd zdelOLSX|GaZ1_LVkjNcx-^>$pH4$?bq{bli@a_ptfwrIC(c#Kel-Sb=$(Ebu@?7d3 zj11zJ&cmFsgKB)|n-y!qh~8LL|7dDy4VZIj$%bzFU{#NQirErRY+VU)TFh?EJMAsf zi(tSIME6_x3uvS!BdyRTi+2=n#1CwsBpR##y^7YD8aLJ|U_4)PUm=KS1DwbF}~Z)_!6i}pKt?;Vs+JLv9wgqX(EKVNIlIV5Z@DkX@_gYi+^1bnhMrQ0B~>` zGjg*N4GzF5kO3fml1Jv=|HleDGU6KmOepy~oTT>WGW5RYPCxJVlsNE{tkKY3oKYzG zVIunT{L2sTJ`Td4&1buAUuh$D;>EVCk*v94aZa6;GM6>x&TGr#9&~9K(n2gXK!FTd z3-w-!eIeAL2Oz(y-MPT&w}5ri^dnjo7}%$77+Z7rEzH#U6BDx}_ij7p z-`>M(iE7C>KDZU;;^^29-4*&~5KCI_O&P>|s;Pi*{|e!%Kjss(&!YElTJcL%*Wv%z zAoL{S?Ey`-FLJr%^LB>DR8N|5e1=h^lh%;q6!yTK~F)hy(rVr{F$i6p2n&ysS^PdX$ zE{qciHs|@G5_yyavJGE@0zr+n{Bav*+K5TzF0^ok5ZHwmbv zGm?*J!Uv`NzY$Nl-vXE0 z6bSz#s0yHWmPV)ITvG-fF1Nu*AKvjh{{E#JaU*&STQ0s~v>52&=-iE4d~*Cm4-lE z5ijTX%m5Kl#4k}9dX?=`_~;usdb0dyU@Bp^ef^WmuP!pCL~F}~-D+>57gJTmG{#A- z4iG^$Nax^K@GqxmqBcO%w4!MN%&0>kNXAcmE4&wdf$FO0jb^v#xO0am+?RJM5{8}| z+$a?Zye}Lvma_N1Qfe14%M79*(VrrQP!J94q!h;_^^GwmcxxW`qR6U#HRIXRa!%Lx z)9|0vMuPmAbQcS%N!89z2Iv$RRd98FXfvxWe)22Bc5@avPxLEu)}8CeO2|JJbPJ7( zoyd}kT=L9%pFQ(;*fEF0j%;4oMMXrvMnmu=I~I|m^=NzHr52!|KlyIvf1f0rR7Y(1}{A7EyW1^2! zQDz(N#Y|Bodr+gp=O%8-Q1a?#9E%$YWA5{PFlO^RED_dq1W*hZS&X+>PmQeV=Z0>) z%x#827TND)c5wR7_qI1EUglQG{VO=;3g)E&6o=%c(fo#FOdKAT z32z<|W^kJ~5B6jaS}Vyg7bWa!A2an{F7e(@3Mj2mRt6jsk49_W%!y-86;_xs9>;*RXiVqm5_s zfp6El~>_jk3#A3BU`N(EXQgA?EZy&x4OX2?F+h{Rh)zRc>;*LQ=ti?%ST(`1#J# zq1zQV5Q4b@Pc9WNnY8*qjsVG6p7gV`<;tbG*}7vsf{6irg4WBhEu!aYt7Zx|g$oEB z0nF?Nhh79}pdqxfzc*D%z9QSclWDKamGj)PukS+vOU<7E7(G?aY`+V~m2Pvk>Y~ZL z#O1muXX&HykuoLzCj{?p2(#0H)Bq54hw%*xsB`;ulDwi$LQa(4L1ZTmTS^vgYPBoI zd;EWBhUtY4LEd3NzX7CbX=2d^%NQN9>+?n9WY{KEcfN*|q|SA5uik-Mzi)1j##>>v zA78GItn$!t%dQJ%l(92Ge@gZ49`nahuyOxJ<-Hj3FBF%`es|a-J*;jd!xVrkkf~>=ANOq^-$uz?!pDda~ zi%S}v9e==rzO&=XTf!cUT*bk9!~yjd-zL}T^mhP8HPjRAssc%oAhpWAKYe`-Q^l&B(bC!&<9w!_E;p>YN7fmE%I-UW%sJm~PHL7fo*Q z7(gK@_YD!k+@~FLhO_NC-+O;=6MYc>esdCR+A%jL?n3Frx+@onPQhU!I`xdaezx!^&iwG6-)jN~8ogooZ}t z#)q6_Z3ocvw3{>NWD56!W5L+BawH26WKse98~v2KD+q=Z=`Un&M7vjo0Jg#tKY+dc z=Y}dfqfgL!epGKe11=HkG-SJ6MHhMZ4)h6*(%7DcAh4UIOZ;+g?)5Gi zHJuoqYkWdSe^Xj&YB_lmK3@V?l$XEidergQcij?6CcU;iORe@dzR~DE?Es+2$UpuB z|IgVtC`Eo@deuy2duWEe8aM?3A!|*KG%n6y$MJ`a%yfe&alU;zlrck|yK& z8Jfs6les{OGXqSPQOKJvDh;Cs*eFeaASvq+pc)SA_H|p5(!Q5FKqFUqWA6KYse^Of zN~aC_@rLC;=JuKmD|6M?y|jy1$08B!9~%j!0(GIF4)q^zXFOU`DwA`gHg(z z0Y>+9EzC+M*m%ob1VY}EqGr|~2hbPkQ>`zY1b9ZgxUM=&T&|@w&yFNMmL|vm7Y+?& z2m_$;$>3)j;p@io)=)^6ewO0on=^pd++z2(4$UA4B!HSH9&S)r_S}k;tMAAh{Lhn<2*eubwt>S+w_m5e_sOm69u!WPB8V z>rTO9^3MmKdS?X!`^Meee!F`oTs#?+^?)2~-ki@(i=rxN(9aP!m+S_AMk>;SaU;~T zyOMr)LFV9r5PArxi-`VjAK9G5`%xyiW^{)~zTcf*A#PzgIeHiSa+m=?qA`-9@eu&t z!Tbsct?kf)ZK7$5Lzwq`^PK4&qu}mFHxIep?FtbrVmd;PTlNN>N_RpPgAe9mwad;U z5(Hl{@3qE|&c!F%Jg*%Sb%`Hm+B{Fu zWlOfgRn-ZWnzr6=WU;YC_9M2quS)`W?;U5}Zy$KG!F$@eeIK}>9^jxDWUZP06>i+W zE|IzOi!j&w`b{$SwzX?2ajy5g)utDiCug0@xWrA;+R%@Q48`iwf2Ih7FHxA1Ki*#- z1r8ZFVPF3o8XJm1A?4EhhN9f|`>b5R3SZaOIP7}ASZ8z*|K9q0#-RAM%0B^ma=)+e z*ul0bri}`wypBZ$V9uF@05Z?DffauRc;sNdV~}n3b(_!YyPF zdk^;?Vq%L^l}ll2thEFB*YD94bDiLPYZ|&Ta_TQW^w&1d8?2yb%VyV?8+?40xE8lX zjWdh|z4%T(MUSVa0wY{#>)+>owd{FA0{(GD_ImAKr@~Qb+D;J#^?M~W>AiO`;q<3Q zl#ZRdq8pGu2iVE9mo2XWHe7!gUlq9JaNgpJx6lxLC6aUjGgE6ff5%Lkwm`9Z*C2dh zwn0H(-K|mg<^3@-C&he2d48DNa%K)v3i_5$^3-$7nlqR9MN67dL8f9ieoFotW5XOp zXDQk&ii^Jxl0(zx?g}eXE#O;6X|-0iSTQK!;y<=H#8J<#8oG-9Bvb>>Z1>}&5H}FW zRJUEL~#T(GAd>@R7UG0tmD)m(K%eE%)!ij6H-TfDi=^ zxHbl}KvXji4(iI(Zj=dj_Y~A&MZd=O1l>fAc{glbzC0UuAbqcQXkiBM1-<^(sSr@rQ|DdJgc1IQ1QA?B$6S;0;LqDhDM_D{L{qzk=s zG4qb4*Dn*(rW=Sv2iGarnBv)NB%8>!0HIed=|HU`73||vLKP}1bD)NdWGQs#Fs6qa z`^^9)WE!5piNhzd>)*3I=OY5q^mH`{6XBb+5;>5mU+{XdnXK!eaRD&H#Q+#D_9dq4 zZnXnvoIQt;1iwA>=Bo4Z%@-&}@!j_>dyyp%3yE_Bom+;qgr1Ap2=pGs&HYXV-SU0C ze|~YoQh2P^noqboubp)}*^EiFB64CaK3H7s%3OPs)%x^LTRSQ5cAGHWd zpLuI*rgDPIbuyR$tvB~Vncdm@n^iV`&>k*K4`g940`*4lK6_^I?&a5C(W?~~96 zcS%~@UersSLl_?AU6yFb*oN6gB?#x=(O!xS>KDz*k4^+FZhc?^mdyMiPuMYkJ~68s z(O%eoR8ZHgWR6L0=HM)aYri*NC+exu&iUNoA1@L8(nfvk>fk#d(;7&B#neP>wZ?Y+ z8~ZLl+YHUp53xgCvZ`e!Q+{IVJs)vgALW2q+x-6f-yW(vRmlpGYrgD~0I-0CF^tnr zCFiY193bK%$N7i2Pb~8vQVJGDJfsbiEgI4EC1jCd7asO&jJQ?_#$Si~Nmgz&C-&8)i!i^A$yX3HWY{@)kgas$( z1G8>f410jbruWV`P$0e@d`70kWmv?EK2~~;FN__C6-&fYzF5;7k?!&aj(u3IimjEl4U2+IiW1+{Q=HGj6g!vn( zh`pme-~T#$f~L)<=Q}CZ4EI(z+wyZK3Em~Kut5fWGgh}xG?Z$R${%AsYzD=|YDrTv z$Mz#IZ3sMa5hW)H%0;oTO388Cwp<;TeqEhTaXLCsv7|8G)l{Y;=y>)^YY79tTtR4WKFFb@m zS5AW%cY`-JAWy$Al)H+z{7kH=(>DME5RMQxdkyGyr7RnqBx(?)+Mg4#m$t5(l+5l9 z;6hb0A#rD17Xn7Nn45aW`n{8mB!&&|KsUKjMp$33^3;?F<*^a>V^=j^SPtSHmwA*B zq*PV`kSujO{{Bw92cE9Y7k>?7W-~Nx!#v%$1Wv`KgYrh8bG-yRMjx9k!@fsE!yuQ5 z7m8n>C#ZZ;p4=w#onm|7Z@ohqL=bk~dk_M8L!kKPB=?JrxYSoaQ=SGV4Kunye=i1( zT&!=Tyub7Z*2SbKm^jx7yP5Z+>ndT4Dx%wDU(W_hVw2HxjJAzF`RfbSmBL!YG$tcAHjXnC1Z~h}>+d(av;VQ&yP1tw>2&PbK!aC|iS8m{L%qr2m%@Ft zI@_aQuLb|x82Vtq2+;nM+WKWK$IK^(1q>vR<=~&%9OYTnL4L5RY=_J~``~?!vn2X; z5PfegX*hjdj2PDRO9`K#9?wRW|FjF^DPu9Cc}ot<8QXX+)0?(jfnVb*eeSOZ(7B*4{kUe=KIez00xvC9uTc z(!T%p4(I;=$z98vc22WBk~cNZk0qkS=2XIq#iC-zQ49TFfA}*~`PY-1bQ$Q8JEW=#IwF<4^dw|74bXCwDZKPDF&{)r1s!#=ja zgC4tg^@#Ntjuo&aatyQYF53kl>LCfgIE)0Y;&TlopHgA_ZTkwQ7jBT>MVo6(?)0st z`8Q4S&ikX4Y6ZL0u#>7)kN&lZXx}=6B7TilJ&?O21*0MjHk-LH#G+P3_b&_ z+*Oi8d-0|)>ZokUBft(&NJ%WcHr8Wk;|t%%7uV9dOP8PV_+LQKyV1>%0oa3>_c;9+ ztPv(aqmK1Q?i(DA=}kz1u6`3mEv$=%Tz?Y{UR@nYp|J4(;B)8z3O-tgLy!6(7O2S- z3)Ht1=tV8`sPP{L21nubgu5*~#}*$N zrmE{r<`3UYE53pmUIC9^9qvsj#$iZ$Bn<^eLKf%vjx9La2Hx1U*P7l2_Z`C$ne}ct zwL+$f{>`vG-svNdMJk7Vo?nlN>dJe5AtrVvx+o0!7jr!V zNA6l6&oDQq5KIafbBsAb!IAEm8$R&W%j3+)5^v}|_N&XmqHKy+riim|2$Wz7?AEC3 zNE)_#>>hl6%xn>YbhceYI1E_Cl?pl^c{Aahx^Bgr3nK!m6~qFiv81&^eRz5rVCs^DXF~?PFI3 z+cuTSncL`|R>(g*%(@)Y_at`izJ44tCVGvY_ivS1MgB5GKj)dWNdO22leczA9_zsH zD?fh{z}lF#XmbyRyxy}2xdx%mMKOfSR}=rBsAnKd@5rXmL9RV4|1q0qO?Avg8L%4d zGUgnD9)umQW6*HS4&t^Egj$9j@5(q#ppN}wAfs>#^hv@p>gpNdb~qclS0W0%T}H2s z!9E;V-Fjbkp(j&d7hq%p2-6C?D8Xz&U{_!mW)MtMC<=d^iYe1X)FWk+0`qN=tfVIv zltUEyk00^X0s@;mq-F{{x87_2Y*TmUMC|s zA45rF2=*_rY1%4<)wG=7E9a1j!0Q&Hy5+aT`r^EIvD zXjX%(`tQpY7-x$s2<%`RlYuxVMO=)WTvtIu?W|qEuN*TY?F#AUR{E(*I-ELE+hyo2L_hYJNdRwc>jqeVaKDU`G)2ODLndVxyFn_sW*RPtN%|v(8~d3_NwGJ^-S*7+r^Num0(1UG zx!@Wz`-L3qOMJ!Gub(beh=?hv1OI)c3Dl$f823n>xCk);a84-I@9W)HyN|7|lt~?! zwe;-4Q%#jXh;QR2=BKRRPleXe`PRHe{N9DmQ8@{(xnQrYh1fda7DkQ&dg%+T*elGh$=~vr-#J899jqN&s-P-&n8on%>KDK!}QpODS zF|hlRR5c5$?l7AwKj1ItE)ymwml$Ak9?@;gmRv?pP2`!IA(w*L)tm)(W3M@kBKf4q zt3RtLN(ZW-=`)4guwik1lwf1Yd*Nn0_6kg)DoESuv@k;1A1sh)B-daDK@laG1F9{< zJ^kzobslwt`bH6vX;~-7{DtP>hc%}bKBebf&)Hrxy;9}%?`)V-;#5j<|8`{D$EH_H z@nkBs2lh?8pF){xjgZLl6kYdf_xy_v;(G>wuIxNMpQJq$HMCA2-Ey6x{ddNo3`(gj zP#?b@J&T@SdVNysmo41C&Qs!T`zO39uJ0n}$v?X4yCjtSgUdLwH%XoTY8 z?KtiWTAB15=s&&|OO3 z77CN%m)RgvrJ1XIfvg4B{T;0iJdmhpxet--?|xw_KA1?CoY`h*^aSGM)E7Smdv4-; z>YPwmIE9rpRb-H#!_7SQjhnm=F`W8W&~NukPw4lZRAr4co~i)2Y0KgpW7%10dr9<0 zd(@?lN&K_l#+9t4;x_Neocd(3^L(ZvJK=mAuwLpsb?FrdQRLNXxn^`UMJ6SXR|bC)EsW-S`{9(GQ6Qky9GwG}{cVT|5`@$`GOPh-0`tONOnP@*%TjW$%lnUmeD; z{fuO6y2mKUtv$LDa#O98&e!EFd_`gV!hdc(EEJ)p7YldE=N6B9C);BeK9GuI`_gyp zhIOiBZQnHr^!O9`7IofOaQLwPE5=tTT6sU03K#wR1QXnYoZ~ucaHtjYLk?WmU?;o^ zV~aHug+r+`9saN5thn#xX%$k4hD`YPte+k~c;4rL{p>*ti4cssG8IFd$IYvkCAT21 zVa;zUgNq)YukAg?EhaLxUd%0ZAEE>|^)pna!iZv0VRdgvR+S3I-}MXhQ}m?wbMGUF zGSt{UMLRyTK5vf$brkoIaiDeX4X~Ezw#w>TQ-r$)sZ8ao#WqJY1Xcf>aJt7G>@ii2 zXmr8H$ksCr(b#r_h;-YcuC`^K_D4|A&?vt{R8(zbJ`XDBvJ>s#U-4@DVX<@)V?@7v zG+`~@Qsi|yM$vMpV3lAFm(x59cG$&F`I*;#Z>-UBSx%6TncW3xd|r?MJ6cvMWXDA- zVVMqLn$}2=sYO5Yh402Euaf5Y*U$BsRLmx~rgd)AvS@xbbxw{J=Th&#K_SC~6!^(a zn9cQNtJ!oYi&5Kml5g}>uSslPrVCdE6tY2soEq4B6?ep^xWB8v`%_1_ z@cd_!z0hF##)~7kZtO3vD|rVpT=acSYKvs+ChmRWK1vMS9O?3?{O>zlLbe7SGoHh& zU)RGBo=2|7Tbsw5L&kp^U*5yjtc8v|X?Y`{)xu~Vf_(CoU@GgYY|yv0C$DLr$p2}j zzCLx{n}j1m;6r3(Db2;!b;TmBwCi5t^5bfjcr%IB*0!dmgJk8sT=EVguF#O5B$_eE zF$NQsus1U>PnTn@w6DfckEkopSL0Q{EO%diruP^UiDjkC7#+dQ0@in_+CUpkP+^hX z*4AOPw#-fJ8lsRT*Ja*EfieBvGNIJVO`IiM#>mcT(6x%k&D(qT>dgEV3;WY5B<<~m zFx@z#1Mng70^>?8r#=ueWoyo|ScZz~2-4j{8(|O&cS``}8_W1KnqyjhYDtC-)P;j2 z45Ts-@^2=ziWhkpsUJP-1-g9dGjqaZ% ziW>cU72=&);P|h?Q~e8}sNwb1iUDgGSjptOVN5MSTY_*sT8{UP8O?$jb8-Q56fm(qoG^N11`tEZeOIRqUvFmHbRm`7go z*tf5+nbuD9hs$eaKZ?|Vyus4%`+sT-@|KUz29pSQEdGA((<^2kp#6nk}W%B+|8y>9jde7$9z&=^13Gtk25TI#z2mUC~ z`sTpo)x290uGgL9I`nlCk8pRB!nK!U$n~iO$XsW4BtFD&KppqFD=~{U=r$$W zJ73oGaCzAzJ~j$&^X@R*z3;4bt|87H*j6zARvr!1V<#fvoE;*H|A1kkQI;Y`B1*=HatUm zSG)yw(iKOZmx3n;dwp>3csu##W-1es-R%jz7t8XGb5myPr6ZRIlN!UbTa(mQ!IW$f5YvQtGs8i(~3_g>2gob(rF_-aN|iMS*;n z*6vi?gODwd+YQuzJBF6eN>Aeccj&_> z!X};ULcE2DlUKll(G-0^78I|7WH)i#^`B~1A3fP`wLkE=JzF<2CW)-#GWlaXP&kiQ z%mWcr7^ZPx`$-(|m}@g%oO-zLy9tb};ArrK@4oDB{^d3M>;|oEA!pg!v;d61pHtqk z7bcs~U8XJG?0DuzC@BFc#8e6HYT3A@+4d8HvV4-y;}r;Q9~rd^ccH=HwSs@Ty4(iW z$@jpprB*;|7LA-Ly{*14Ms$HAqk@dQ>JcasMnZ`Z7NZ6j>ibb6BH!Wu3>z{mhuu=3 zrZs6;pnf0qmlxqrOLadf&f#+gu(Mbgx(2+YV9Gdn@jO!z)O{bB9aqOdfOPE+Z85;t z;uPy2D`NnEsn$FFa*Pt(olVK%B%a@R&GAE*@Ad)SWc_BnUgJ9Cs7(UrZ3m6~33E<5 z;}t%Zpl0v!yW5CdylZZv_N1iBmkovGkI>vKG*E#(foGQ%jlL*s0|<1X&KsY<_uUsL zWU)#E&eYzl_Pi}D_+o$uH|`T|R{zm&w7YyAwT%43k6{e29vaLEoa*9!BC|6_PAHOH~Wxkq{uU@sCzt5aU;osaV-^_QMokGgDM_Zia?Up859ynxm1L5$2?ZpN${l z)@^0!iz1OnIy(TDKUZ`11!zr)UBHw54djW3#mjP`zfjCEXEfKqB&@Dzw;&iQM&null|jg;svOXMhsb&XVyo=f?gqy zeejU11RT&uE}1)-SB!=ho`W0_H!oEg-I;tQR^I=x=+)0%wu{kR3Tdv^!1JMa5y|$7 zoSNjP&dnFsFRoV>rh0)Zb3T>lRsP;@RB4tpt&r4W7mNmS4HV|XJ?}np^L{E$kNTCm zW))YilzmSlPK3=U+06HMb7Sp3CYh_kBDdXo0{JNw_9COifN~hp39;23f0`GpPHWVB z@RWxGqu)=g*q(sv;S@yO5emjPm4Ol+_l*$NEB%kx>@)zG0&q&n7pJDxmQ%0snFt+s zMNcQcEMfiaAO-a(wyKiRi0g5^FwQBHbr4IIJ|*)>eVR1WEdGdCw@Q9A_0edh!TgV= zoh>MWO~>2Bc%J5VV^gTMw6E)m;U*j(z=aL|Texl^-T#@~>L^ zzu;u8b`z_6i;#3K%}hFHih-Y2siH!rIBUpoxZS zGT~P~e}y_+Dq8ZbFzUZ7B`o3kS5}wg6AlECaT|NYrq)KfjP@O z2b!2LoA#0Bqngs`clc}DG4tnlU4nj4uXkMh0CGZTcP2Y{N~JmuT%@KyihQj2Xf7c* zeq~o4b36GCB;Ep@YS=QeZ+DQY^Addj%p>UAw%_Y0`RuN#slV|JuF-~zH&ZWOPw25j zy$fSw`d-}U)}j%5EXxnA4#$GLd@EQnOXdo!xkLvdC+^2V6U&sZ^*y&1Hh3Y)f?<#M zca285$HE_6-%L||i+AI|Jh-1sbRCl-&>1kZ+P};JrS{PMh;{xka+zc%cU`X5^rb92w_t-6I!JO&;0s%T_8u`m6V~> z)FR696lS@?laVtx#Yg3hUc#Ky`B!4!gANBRx&mZhUhY`& z&5)h&F4J8P)Wk!ZwW%fy&_z<11Z8M zJ=^W{hcL?Lw34)T6p&XUSfm$^*n9E}JcP}Q{(J**vRInID#4aJagKozU6 zR%4VorRuGyxizn663O?kBA)D%eirOgC5x7QZy9%~Y5cOT{FXxrRhp*7qo#SETDoO{ zmdnf}(7#Cd3KW&8Di!v$+d}055MY|1K`*lw1G_`b4MZOMsLzXKNeVfA{Nd|JY4;$D zvN_-7!Te0cCgDNjM;A4vh(gNMimPam#T*_!;g(r)vNd)s9^-=>+)7_Yz0+SCn+Z(E z^0ISuk6IEzX3R-DUUD^3sAU(;NiO|&5Yxy!jkORO+!esiQ`Xq!XrLz}k@HVv(tA~O zHW3L>l4xY(#zYD+aVpgJyskMq;+`^*f!a4w=R_j80Ban+4Uk&65Miq}_ano4q`o=W zLn!(w*Ex^cYtL@oQvWr9Q1OO25q>$ar-m^}(-!-LP^U44-Xr>#H$cJ7h}})I&b#yW zX;hEld@QB1igXXn9@PdedYC~!7*G21!~F3V)=DLP==&`%Cl5&DFxT^Ea{=d_gk+_< z85P!9JVLOwOT%}Pzpd@YXFpXZhUZ6L{B1%)F)@F#K|o!xy9?A6zvmw;<$Sg73(Im| zO-pUhnOCGmi;UNKQe{?F(J1;=mJ?t`>TKq! zr@FBCELnW(M>$laH@v6Wp+ur=#(@#TA=W0+Z9FBu=G%Y+T?mpFc|t7T#p6PnKHb+5 zfn$&o{$C^Pb{HP8rizoi6~)!6m`%2g3Xq6;{(wNO^2xo;8K0>Mtt6clk;AaT${5_o zS8$mXUjUgCUKvZHUxP#6S?;~R>&3+rrZBwJi|%pe7O&XuP$z@N$llJSV*>e&)Lc^uw;g;TJ$b~JLB z{UcTU9zkU)n9KMc?_G-GpX?bzn9~FiVr@E9))UJuu-20s=}!sxXFznPN#9fPYe;T#|UbWg1S)srqq7K5cI;vl_SzoYr zoTnk=QSbMnUxkgJQGU#?Z1P*7xm*2h!YSYSrgZ}CgE+ z*Wwo3AvkTZ7Afu&_u>>O6nA$C8XzPm@A>5KPO`H1>{+vBuDPCj)b{z@macwkQ3_vG zN;5q_fDzb^3F!6_pRE#qu5B|*%=bf96;4E;Qy-OEGB9Ce^^S7lB!Rpd??FaAi8@9- z8Hz9n5}O*}Hm4xOG~F`7b`@@#}w%Wc-mBWxkPtr1!}v1&#hV18CA zruUwDMh;YL1LO!>MiL>MX9x~qC9C*%N?S$nUyEH-Lij8l2CPWI39YP*UM9A{3+60V zd{y%fs~!E_Qt!uCON=P>C#n#?g?4N{N9VSkyuFs5uH7Xq8dFHNzi@-VgPm8w7`>9- z*6v6$i=*S%pfauWn{@E-0`IrRM;D2`<`YZfAovcRiHT{TVWxR&Z~@EE{1!Vng8YwS z&v*#*D}1JE2us>5d6=tPvawtxr1{Bev7GU@W`uY6;mnM7*BB9vuJ3NUcjZ0#`^2>Y z6=p6+f9F6Z1n90ODjV2EY>bWCjYe3xaQu1`^luMSZF%-_=Y`oMYYon zGJ;XcX3aoG4EiG?1=qRwLa zCzMR`ZiN40`m0%!WL(ksCXj{rGJCN(%!*&~8UpHk*49~^D!3lF?dpxoI1oih>_?SK zNhYF;Y|~Q-qCZ7yB&up605gANu^Xg!L|}D3Wpx^X1ooiLj?;Cbtl)-7eidd)(tIv5HUqpQJoWBCa#rx2Q80tElJJKJh&IeW9{T7Jk}c$FtE4{#Gq) z7uLOURp4(moNhnN-ay83b*OduO3~(ZA4pMahkX+eMG$&97;P>mollG~vWfhmCi;D1 zS?j}iqW0DfzuLTm4H^mvlecPv7%0rcI-h43yu?beeSk*+^eSY!Q2#N$@ZvX{tJVwF zv{sE(gT+IY+#d(ybcL0)L6UdH(GaE|!&k4Em(SP|8B^z{1}1;gnPDQrNa+z@#^nhT zQ9$v`m>jt5)50}!uva^FUGPjIdj1i4;&kbOnV=fihDfK^#`FIkuMeoq?`O96Cdh05 zc6z;pOI3~$npN(X-3B;Imd!})9Gk_DTo)KQ&o?kM{ECiXcBgt^>`cy~c2oN+H9R!(nju7biKe6<|F+tvue#lVhViUgK* z?hv(FjrN=4oQ(jt1`P$ARU{ep1js^eF_Hln>&O5;VqjV%sc0k&+(liTclo67;gXJ4 z`Oy94c1$6LsR$hc-{j(hK*R*5eSe(by`u|ihE236)_`Zi~jnj!0FDqWGJdMbntkrooAvHK7PM=%E|}CofaXe=7%b=Sp?E zXSpt{k_aFablAJ~4impf3vt!Za3px3nZy6IbJSR#%m36xBt68jskV)te%trs_*!Jm z)%NV4j}NT(+>Q=mf8!8ZD4yGM77aMBn~QGlsrML6Ww(SaHr9SBT5g~y?7&AXf2+8< zI{fs;L*~-W(b3i3Oxp%_Q&xK`*3ciSgJt1uVPlCL;iEgZLNRg3TvnpPUiDe%~#6{pObrYNBVS zafLsD5Rc%v&j)-nL-Ni8)uRz^Q;B}oy~mL0{~l-Tk@*L8$hh^5UvQm|RFmQSvS-6E zef`wlLH?m5L}U`hrT$)J!Y>oASSJS}$glKs?c|-RzQr7usfmt($=zSP76bjUSsSwc z+OCH_*OJaFfwU*djFM4S?Y5Px7PmRC16h*gA6OiRDtK-37e>uH=&YVq zqj(a78@_1=!}5QPVr;!2%pTN8pS5u;bY=mlVjw!p{MB4lRqvD&fsi2$4boiTBTVr4 z%3n*2AC8H{Ypcm&Kj>LWBqw8I9AD+Zm05fR1Vr^^0S&)xkw+eodJy;L?nLm| z$UD&6)4+CRZ;3#JGH+^yz5B>wOD@I~;>Y@TCY^|q7~5E}Gg;i}`COtsdP?B3;T0`i zMk>FZKux?z&2HT9e}Azg&9)t4OSN(lMWvL&J8|ITR648)T2{I*M~^VUV3q4AE#B4B zpIYwo5w(2p-ROR}a*_DsW94;_Ffsqklka6M`V@Y=SQfnUQC|dnVHwflrAV-v$RJC_ zY6nb5>yU&aq#Tw|eE)p=RTXDenDkt#gX6GYVQ1z_&8)Dub@DG5W#OW1F2=$GuEM9) zxm8LPT=}IDUxt$E3~?Da+$98wKB*B8PA8Z@kA{|h_{~;ADpnuEkwyKco_MOxfjqeC z^7Af&cg3YYxin$x9aiGYUo<5}|2|;gW2tmXPc#1!qxM;Ti~_dVz1$4er|7baq1)Gy zoO%R}KXFA^V%K+^7)2OOyBvkL0FyEXcfh|+DQft~?Of#go|JS&#^ zvHH8vuT?T5p-V z&V7)}`TYeSM`@mnMP^rZ53z7iB?_Gsdl89GGnl%(J*3buv?8@oRz8d-s*&*Vg~Ok! zCPbB3vaUJyOg=|KXgYLIqA;G=9e&Ys&`h*?w_{DT(P0M`Cv}-JFxU6LgwkI;T-&k( zKKGXHfh5_Dr+DxMDs{qj+e2SQMC-&=eY4tpRZh6IVS(_lR!fdyWre1D>IzOK?0cKg zOdRL-SMlND&#=+y29_mP5NE-uME|8=%;osPn+} zCdg5fqUImDS1_!R#cGbakxVgN-wduIRvTeO`^_g~bKm9%v#N4WbP{Q5?iFV$2{v5t z74l9TK2$yg-!SvbPO(ae={?v=dv%%^O0HlT^13 z6ACOQC6?*r^$`wzLuP5)K1Bk7cSeJq8_Xms!qwSQU6*cdJfp@JLf{WOk{@&wS_Xj@ z=a*|S2j+;eG&SGDU(fEo8_q)@tmc8H>CZy+ zT3D;ojHd{l$oCqgV90h)u0{Lx=Wi?fZ!CQyB7d1Fe%si7OsqH@oHsEr2$_tm%@2Sv znVtEC0UfrFqm$-MPj0SjjOXjZvoV|egWgPV9rXx?xmkdPj;OQxQj+P2VXOIX?l@~L zhnD{s2Eq{TV~Mx(#u0B{;jTW&MU;?GC89(RM9JVYjVkJ5{)$$~P>J(l7Vx9I18#l# zX{FA(UsK#D-aUHQtlf98v=qJ=0xA6?Y(*4=7&wGuSjV(}{f)DY>T%1Kyf_5Cp zi&~(#`7WtnYWtzSHHdmXPRGptjlS^>>cg=C(7|?r5uaZ3uJ%((RbB0>OdBwj$eR%wu!Mhn=!UWYOW&L(fHE&a#kGjA5KwqOm5mekEJ}-^U z!&I`pRq*0LA9Uli61W~1KI!>yA%lI?b&WE!R`2_+DlLuZd{=H2bTri|xxZRd==^M2 zs^B$cEnYZg-&mdmx=p!A-V}V+gkG#_WnEg{R#QNdZ(6x2{71f|)GD+)WoDgM`7!;p&DOq%S((6RZnHN|;% z=cXi3DHJgEE|skCW08E!<%PDT?}^)#`fD%rBhkz@=v++)!02;p|1PixzJ(Y|4Yvk`YFFvdMw!qov=Hq)h)9MznGTMKZ z)tnjxKF|Kv>XeUD7S_PaU=}rhKN_qy!}(lngtS~uX-ZB@I0spQ|4r>J|O_aK6#`9W0VT-gt~OJi9|p9=)hsWof6nJEf_R{ooQUZ_ve%I^O7E* zoCy`S=R9ZY%SiiJ!w25I^jZGIg7UL*%zL;BvQiM#f?rEL9VTDl&rEGV1tN4sp?4NH zUSj|j;KC)(lj(r4y=1J5;LD?b89c@fr~d$EJRD@u0v?#|B}gy(3-*KPCv{cU68{+FUxGfG!pwMfHI zjI32aMMy&4NUA=3#I=y9gw!}+S%*B%lSC##F9Pu*JlQ_fvlmDvOX|-Q@&cdvRhLJv zR|w2Gfs88i((V8T8(S^oJo&@-;Ps>S_d&`DQ4=kl^%?peEcdKvk%mrjEWMp%@ zIOesN`H$kZCn8hN$p9kfK_^veEf)`a^?ntW-G;m0TSqsvGwq2$h2}P8oz5}&x|Bj1 zxiiEix{hS%GotUcI`^)tg5W`*kn3tU9x+Grd(n3<*_K>3T5T{z06MpS<^0v(or>$4 z@AbUnOaAtyuzPY9MDX$-V(eN=lYm=)kl@mH1MyD-^fc802$FnG7dxb1?3tpeBzl;E zchGrU*7#xC#BUf(N$y=@F$BZ+MA&d{4v)3N0K~r-ZYDtv+bZ_a?5SBPcq0XVYB1g~;!`vlyc2gh7C{UoEV-dU zBn}Ye*>BDk$7OsX%gx1jBl$P1XRlr*C7tR@!6$+2r6p66?j%W$S26l7Rdl@?Lpu-Y z&2?z@XG$=k5v^1oh*4z|9cfUs-n>d+H8JS)S=CACk?0N)p^23&Vv`3OSfeGYwxyQA z_xA=h0vNaFTeW$`^#;m|FOr`CcInEN#g^N4dKaFs_#p|B`D5KM1=hIwl3zDxZCvo@ zzN?&dBLNo=JW&sa-z|V|h|7P`T)wBXMYsvz%oN9NZ{{oFr0r7T)E^_xk7@yJLZ=iZ zwhI?ab};(JjjszfSL>x;fwjRNkcFW+LD8YVH||aOK;B8CYFfgo_UAm?{`TYQjY^Q0 z(hIUWS>64KI2DsDi)eCnPnYESoP-aCAgS5hxL2jhR%YPDJ|QmZd&#e+?R@Xx#6a zmRE7GS2TnF$VldXW=%ph#nx4!2{6cBlk7(1g9^kf;jJT&WeLLjHmqw|zzxgkn}1Ew zE-|B@#l@w0@jc8ePa6yC4uugozXqgh)b1=#l8mhn&V;wb#`xgyTBYvKlYHNDX74x4 z+yw0=3f$NE?Ed5iK8{+B8EWe^+#Sx{E&3;PAfEJM?tA>w2fsPto4e&{JJs+9jn3Et zG#++~z`(vXaEThNUzBwKN9;-_=+dW`n^+3o_gU}jAnDB(Wc z+kJYb%FcssfnhWy?}ZO48(Xo-RB)=Usyclv2wAv?YsMCMkGd83Z%xGJ zym@6eAaYZ<=7MyNe$7*hdk##KK+s21s9O6xvpyeRRzrxEV?g@0d$C(+F@y}EN`-{< ztF=u{&O?91W~ILhx(uC`&wk=HT~`hm(ZV&KnT3zHRE-BCz8zPP{Kg`pT?H*p zqrAhqb#is+lS4jR=gY?ZPC*ApU0t%%SHrQ6laS9t^p2aH?oRZEK!QW5PdC_qSlsPh zUHe|=h`Y?wUIev%_)GdyAfYMn*~c6<{(YGOSWtdT3sbsmmt>5rdV>-@X5;L;0ig|~ zcxXn(8$zcX_%wsR=&G=}t{>fI{jKQ>KWBc2+Mh*EK~B>&c$PaSa%fWa^Hh=H zAHdCy;6ar{8beBQa+HjN5qu%@=P^pt{EZoZRK3e}rjOc%DB|Ve{=q1Jw^Hi8k&Z>MYr*q%ULc^! zhlfvKeHe#^8E^SDi95NtY=j?_N-#xwd(n!8o`qLwa(%9*WgN#JSI zQhm%t@pD^T-IHs>YR;m;L#LOM4d#8mmi`0pMKgvZkz?Pq2V9Tn_sUgwQPH0!l zPIh7%dpSYl=LxpoKKx%-BdkZ(yz+*@ja-@30L%)RJ^ zK5NjD4cwVl{|7_mU)fM)9QKx%bil87^X?@dZ?4_GE9@xYH1U(;H#g?Mq7?+%_kQkZ ziaR2LeO7SUantcxjXHhTpB69RK~pDg!6W9{FhhL5W$+}|U#R{3gUmOq9H4SS z#0y5&xDM&QXni)FB2mVnMFVno9~222mE|eE_9%8JV2k0>j**x`hIs%S$|y@>y*jn7|5glRS*=+`=~Y9cV{Zopy}e}0?Jt7X5<%)RM_E$j;wba_u5 zI%0DuCmI&&57`Q_`H8+&Vo4@>@8)Y%eVkhzV0lB{^C1nzh9!t`u#D}RCrP%df#mCq z<~V{#VJgvR$%EiTJDOI{VDl6Crt*tJc%jpCjL%ohh7(4%&iGi+uqMOfgDDZsh1B0S zpgyZr{?vxkifJ0ROM(@xkY__lJyK9RSOWjoN^;KJ!os!-w!JRs(#}Hiq|aT!<{cU{ zr5*Yh5=Dnz1fO7UWM10?&$)KM@GA>HxxS@rcrrzL^uV?zv+uODL@sFmZ)-eL!!9xp zOTgV{2POz^+nTyJ^Sqsa3Oe(`qnK*kU1qv(c1ZmMF=x`I>mK*MKVQ~xU+&^vZT(drK5rN+=8SeC@OO_=M z_7D+$d+n?fauk{edD<9${D-g@PIO}YIOP93o15leti98Sw*tN~+aZ;fWCBxy=S%O? zZ{C3?6Z2ii;krXrV$A20MVId$Q-Xa5untBhyLTV47&3h`*kAy04p6twBe6nriDiLB znd;>Joza1-gmb{r(uR@s?T;*=+PmM#p(2S$Qz+NxoKO0TB${MBr4BF7o1_SBg%q>P zwViJN)v%xZvQ`iV|B-Vgx~3BaWU73v8?1C65k16aX1W^)K023h;bo_(;uYBM|Mcqz zn#h`a;+Z~8sj#~OP6dtdtIUyuIPG>>s!b~NGCf3|=FA_{Zp|ZILSm8*rtgN4C59E> zktIMYp+JQcdCgcriy3;{yZ{wzWUA{-tPajme<3Q8_)utv8{3z2F`0{_sLG$2@vUdI z7e{_Gkn6}u;YBhX3T@KoC8^lZK4%slv^?E@SY(xN(W$%b11<;6RJ2321V7<{K=J1BCK2&QQ-_x6G#M2WS z@GqB)b$V>=kg)DbIi(9wTC1LMG2sJv?Y}iv+3pSN8<+@`<@=Z(+l-;;{Zhl((r(Ed z8LL=P#U`4H$#MDOp5`bQ*Wa9>fwQ$?m0sVC7Vu`1IjZfNs(eLNlyBS3qocn5i0<)M zLC;opCfr|mPqAA@Wz;Psh%My!X;Pq!9BvcRJH4`?BQthe4$pVd%col$c$=wkTIIu^3lc5KrKW7cU#}Z=4Pw z;&D}YO27zB)Ik7}TnU!20ua19q9Dr>REPk8Z8_buWFPpC0U;NnW*t9VBYLCs#!{Gl zR>cn}I^%({dChC0tc7z}fZ&iLf%e*|<(b#9NU-E~jUu4m}li z!5?~Z#s4BnXU>sQmnwD-&o8yOe(`GO%bqs|NC}=&>c{`N5FHBQH5e%+QKe*@ew1yw z;y7oV<|w&&4aQiT8N=)ksR|h{V*6KS-zX_VwCmj96l{Ox^F;p~-BM>Bm> z2sgMwXRrU0|A1;Uw_?wpHkqTImkEQ@LUY7Ccu!lu!fcTnY&4oRc!>NbRC$D>?^vj) zjU5lVDraw4+Z;^Vfh@K zJ36+`@?BCIwGoj=M%Z=cOEs@&DA)LSvBS5Te?_qDj(VPTGWmQiLUg~zp(pdv+~tC|kA$^QSEHyu2uc!d)VY639WrIF^?CnfVR@aiMeB_H(w1++mD4Eu%S~h=FOQ?A z3phjlQK8rN#*|^YEr(i_b3u{h?`!P0Oo}^PG*x8ZZ_y}bG6mfJ*VU>p`>H#ps~_k? z%v=_aS+HhxQ|Z7uP-c)h(p;nloX6VwlhuddX2Y!|?Jv@2Z8$reAqt2Rbdobgh2ut% zA}`OB{Qt|kZ|&mN-+F%OeX2ht+OLwa7*8zWA@EBGU->7+TC7y5YP0gU1aN~}s+)W5 zvU9NN65uK=&p`HKa24aA)}@99J&@pV{~v*$B?v;}7p# zn2}?=QAV2jeFbk@$W(WxZH91tUtxxTBYlT~>>`m62l#g6$}}JKES+k`ztG23Z0Fcm zLFvd*ncPvoy-B{-tK`%qWL#IhWgO8euVU&Nrn{%Rq9R}X6mk$Y72A+3fFo>mIX%tV2pE-3n_c-Lx1mQ!q zgV!?cHbdad&%--$Dxkh|5{QB&u_ z)vOui;86`|x=S1l_1O4$$bI7lMeDImf7?Rm-8_~{h1?>$9y=F(K4V#GDlU`BU(caY z%VjsPOPb=c;Yvs)!DUNuCeqh!u*H!b+P;3)?P*vc%&`?qaBJJ{M4lNEg)dnNti*C_ znh>URZ982h!@5b(jMd7MK3Sq#zkytIt|@W?n&O9RPwQ@@+rnG3Q0_}dhp#%v=eSVdgk`B?@DDfxWoxCy`l@)Jt4?{OyOVxgS>B1&P^J{IPG`xD8imwgQ=@>@K#YQsb;IRR zy;+u>?fgZG>~ocg8p6)XtGjpk^$-H_s3o+CfBJ~Rhxh7*Bj=-nKo3A0kQk| zCIN@Z9RoT#O!vMR6F|#ASq3avzUe^&%}1NB*7rajok}vCDCGH}r7$Xpo^{3YN4K!J zV=B}VQY(eZ8J4*WwB~@n_uygHSIvVn3$Sw^c!y9mz`$se(L(1(X7Ujs(N}hkt`Lz8 zztflOAhG#%?c)=r06c-vwFnJRU-p1x`s!iss$ z;?Lu+?s8G=4&(gI9!6_^S+!nEl={^4K}U*Y0DOf8K!S#si%Tw_VpJaQah=87bWB(h zHww|wi)$T;m-``AV)WNs8@cgOIK7x8)c5hd4k_rez|gxFpiQ!^w4fG0?Ra0c@p@IC zkM`~?SG6r#k?x0QHdE2T{ur8X+|+1R7Pm2pZL#ZJLGEUd==Yk_?2fykw$?>129DCk^v;QkwF(#W_Lnl7M3kGmwcQZm< z?(Pw3mBg4G&QCm~TV9qAC9qr#?M({Ge+A8rApDZQ6Q!~j1nRfHx=H{es4RnhA!#L2 z$DbL}z=m%&56~@sZH9{xDiqMptWUS1x;2>97vi{m=YY{-W;5gl{Rsmi&Nk*gnuUQT zY)c{5B%c$7gll>Uwm=n0knk6hP204VYi_5@l?QcM8S0{tIuQH`(!#Fckd0X6 z?Ddy4{(Lj?F*txqJP@@25(Lk-2qc=H12HuntEWcuC=P=Y#gW=|C=?I^(w1Lr z&8$cu+qkD<4%w|H6VmIjFFzJg!WC7(gF&6%d!-DcS1^CcArBIFA+2u?Iy;sbGeFjE zAgP%_m|HdLyryrp<=CqrsG3+bwn~-U`=QOOPM@QUG(pjQ!yO(;ViZM;w z(Zp%&qruV`Oz`p?f5`~Paiu$MggXj@Y{5vu=PY5s)s$EZVA>5$eBnbK>9l9!GMNZ2 z{thQIV;;9mPe8V`1l)Tb77b+%%CYWX0<~9CV{>~hJScjhmI)7Ge2j}k_H^d2Eml{F zw-Z+hn4FndJ(a=0Wr*g0@n_1#5wJ~yX^)`Y)RffU7p$#=0rR;J#}VG#*+T>mJ;;Nu zH`Z_XBx4!yMfE+PVaROF70Z_bWguQ4&Hcvbm4fGeVg9h!=cPu$&`q;Xephq4yhB8R zw+GetY#W}#XxRmUr0X`R6n)^)-$cu|{eX=!m~p-yo`q0s%<(ntZJFJvN##z}u;NK| zrq%50$>PwvNpop<`<1?idar)Djh)~p&wogdG59YS5j)Fl{{qqaO9{R`b3uOPv-`fP z;%*P5!Z}&^Jaqfk@B#25&X+g;X04#9={;LL)U>MM%mBoj#AFFe2QJ|_T92?qtE#5T z$9vw>3twYUb9X=PKe6XOSRWb!k$t+LrfVC26s^=D)=5T0r~8)6;b5P9CEtQPFOk@45);%l$GhrZD&TUcE!S zAdLVXv%y#C4wGN_&U|RQRHW|Go+9&sU*5J!YiF*n`X6In=beq1Y^J?|+;T8$rL_jf z_ZT&1zbLy~Qz=JgD4#nDT|%4zk3FaOW&tjP9?H)Q@(?DtE=hXT{tyw+5M}%&;2|n5 zZ6=(HQ@t&ko>YrMpq()ysMk(DvP7zC^lnr zePG%q^n*S9TBV$l47vhkCLWC`(bK}J$RPvn9GcvZ&vtTWI}8~P>Z^(tnX0G<$6yd<9_d+_yDuPtG$>%fbgFA_vlJ?|+ClY~4*+edTNTN41sUi&gK?ss%%jVV0@G>2W$%v_-bSW5Xp z5UtuL+DU;}id>YwxX@rQtZx?#9KBK-f803=(%%1nSpbSV?3It8%WjE%&u#XTh3Q>&IX!}Z->v6+Q1IHN7>JrBwtR?=`-f@V^u3se7Kqd6fpI^6IQZaES#)`S zNq-HunAlR5Ess=A*=rX5BM>cl^cy^f5ZQRPFQ|oC{=lIT&@m# zsX2UA&23M0D@Zjg`(Lqqog7&7sNc>qNbPnaxlo-lS%RdxBY=d4Ft0A%ya9zh)5NruL6MVup@U#exXq}a66G?J<^=t8Q?14KazkFBt84()_V!Ly$iiREyK#kY`g;OaqZi~h!tGxJ3VS9{*i900(a&jtg zY>&j%St|fxE@YZFNUg`#NRPkwC{nVKwA&x(Nlcb4dc%Tb=Dq^8x-OVe>=7l|#oVxEj*!}NY#-{4i)~vU`WEAe7 zHoPCmwARwc(Mfg3GMBEm1u^JjLEU1^m6;}(M2AlNf9yP~?JVy)*z<$+(G(#C;x24( z(gdI3FYtM#jVr6seSEkYaPhS15PrOa<$uV_emNA9=!s{2cHi&I9h?DHozmPNck40(VzWWbDs$RHZIpJ-ERS=N=XnbI=+-Nq+v(J7_(ubH@ zfe@MFaGUazaSEQjk|#&XF~4P_->1lo$R+ItS=XQIZ>DgB|CEYlo z<+{;6Cc=JVD#H0X?t3`bf_ZV6IH4wqSJ!#d@ft)}$!BMnM#$PZt{1tGT=vW6Bie>3 zlYQIVA_07Ol8LfTTeG;$^x@fXLs}~M-g*{luQ83Oqw1K*{)Qg7sg1+4@l=uQUukL1XqtKR%D?;GDCC5jpmfxVAQY1p4ZX;% z^P0)EGA@uH$|9GZ3{g&Q9nGY0HU0VOKy$_U^!lugYGW5mMp?4|1=%Tm1`E{EZ!a%{ zqP!+zIJ*^=KF*j#B>4zK?!75EQgE}mpKnG3XP>FsX!B{%IKL9QuisA#wmH+*bg-dL zaVEp=*F?gUh9qwm@ggOKui`MdSg{*2snG2I+!lN*2MZTeLBVO*!$YLZ@&7B0|F)FW zAd-X@DDrIbY44)G9RRD*H+XV!q_*m7scC1C*D9NYt8{u*w?|RY3(Pz?Z5O{L(PS4% zmz~+66oa(BoeC(+-h)X7bWXxkaM;o0cH@vbFAcKjhn?1%Da5ABNWDxUi?G_vLRXKq zuyNsHH9E^;?usS-S%wMENOeJR<22@a+$$sNUq&`zIZs}9#;192dHL)|(6q_hz-nzW zWA(ObxM|B@4*dh$yQiH&zX;54U2rY))gWlk{%Wv0NW3ph65n-l2q&oOM~3x$vG{0G z#PJy4>d#>ZvZIS#huRKNQ1ZDs1I9P}&Z3=u1wd74$8&auTr2B*sycJ#dT_2hcec+A zIpS;M{?+a-IojhbTi0rOQvw^mx&1TY;f90~r^rNZ6wtFGO ze^r8Zx6Eruxu6k?s3f4x7!S3R+4jJ(o~I#6`unx+j~`=U_3I>4WJU_dF5;T6X9BH+ z`2-P@Ub^#)Q87zOk+~Nh{1+OyraTz^E3MGd@0Q*+SPB? zdD%9|5fQk@=hhcRA(SJerx+WDgyi`iJe!0!nVXUd^PqXTW(&|su+`Yg@rk1Y<7yPC zuNGbVhNiKi=oNO{fyYrE3j;d=(Cs;1QnJ{A0wDlZ zli(k?CNv;P+8%xccpG(eFD7Tyxk)orD*gQO9EsmH;e}ky^xzJIFb2ih(+8M^@ZK5J zWaOUI8I)sE)_8*7uQI8bs1|58j5y`r7E=PbUe(5?%q*wbzT}x?rt~RJ?pbucEU=SA z7VYve#UEY{B6A`b-!Xn^=sLMiXVq}J8L+)Jn#pie&S4@ekIc4jI*prh;ci(E9diJ@ zwfT$+QA>_(N+7t%rMv)Le47EdtO(L{T(cgmh^9~z(lM?2jmR}M3k$b&OZDKW%Evan z^!UUY=DRNf2;AuGIa}yS)BLL}2pP)JrEbBtZ7Rw82m>3Hp4VP*2X5~uZce*EEcFcp z+KGTIbniT{BI#)t5?TF{*<99+K;&qVVoM5{V|we1rG-fn4p#NDv8-SA;Tee5y01B( zabZW))eV@&Do=NoXde0fjT5<2S}2M0tufbdrNfeYNw3dFE|;K_E>4EOX(7Q!g)eRh zUy@tKxqO9WuNPPZRk(2G53P}X$kK)uyFWa0xpi9hB9I5Z-i-Et_OPIi?*+Q$#k->y zBmlrFergx=-(^W+GK52G?!>=_vV@es-frCagu?$4bwd_)Yt{3l#|w*_WsDHP zj}9YlAupzO{M8@9Sh!G|A|D2$u$VOIyFZn@LG$EOQ;_fpjdaG3ih$G9XD=mGU!H z>@=HvF?&RhkzM22ctQA^INCg9xJ6HuAr7uN7@N#EUgy#33Xd8XG|N3LkWlb`vskNg z{6&lbYPMRRh}=tc6Rf81ZSp_xa$$U$|7R&+$6 zS=n4No}37$&T}3Bvy(@I(@`L`Nz4Q^K5^slyyg&~1-FuRjXeHLf0BVnsly~;XC!hK ztG3b>d!d1OMS!32vP^&M%eLfn8@!;=wpP~L&z&U=3lu4%g8rm#DU?yB7fwqWMdt6k z9FH7!e+xf@JOg%Kg8yiJVEwM0j;5Pi&w5ihW-lLkQ=zPKlDTp}#D}0Bu9-skWh$~h z6Q+%lpdhWIp+9Zl@H8$A>vr$fy z(<7jP7cvM_!BUU_RzeiT5PJwI$v8!)H<;mv(yScqa&fU{3}WRXWS~<_0nzRC6V2)_ z>w>PE@a@*UX$~AD;KXY&YTS$18>hvkE=5sFD2N{4_gdAQ+>-G)S(=t*`a_pdBuU#j zxFRJQ*QeSfj_n?UxY~Olnqdj1mxe<`Stiu7{ULpoIwAf`4j<6C#Yx;vC9`!T@6mL_ znXGaB5^u;du>0dxnN?MB35>r`PciL(MV+bI6UxP9%HxL%k#xqb%-a2dNc}`HoJbKQ z1bL{uEk$zT9iU${wBYXX{7HA)&nlaF%3e55e)JCSp}_OE!3JW}+H>>~T;W_@jiKp* zn^0Hi?BLqQ1u+?0#CFC1fg8o;BH_~Yan+7Pl4l#)U1L+Uwuzph(;v(w=oIzYszMQt z+5T5MSdyAOMoe1uDlc^>K>~kh`wZydnxIR-^@}&8C)pK$VVE$nLK1!6$uRsuqsXb( zw8)ySu@pl_LrzA*TfvW_OHH|?Oq@16dICWiF{Veo285VO7FNWQ#@TH4vO5w z)X%&k$dl9<*xg|QdS4mwb9zL6Vb+@Nr}c(#=rMJqypcKW15Hk#AUHhbU?1{c;BjVd zVw$TmN*jfu1)JDts|c8tq!DryrWGYr7NVXVl3+47(^_y^twkWB(O!cY3~2+6di80r zA5;NjTxPGC&RoBU^wbyoA;IxogJoA|q*pqAuo5eFlp5mI>(jL zj!=h@VQ1Vh#Vjv^T)O{?`+0maU^2P1=|k>oYg0AZkJ?ncp#6Akh1O-p!YicO<_?(w zmriY)v>z_CP;rMg_U62u5l~nNyg&Z?6G8Xn0=*FA$XDYWQH#qmg9Qgn=f?v8rFdu5 zrbg;p7nu~3cLP0InazlYi+j+{4Oi1Q*5)Ov=X9_bBd)3!8qNBqu(0R)!xVuShDOTH zkJSAKWTm@?5Z*a-)|U;>j(DHX2w(WiM<37BiUx>?8Ani{<}Q;Kk^RR0^~M41P3JXS zk0V1ncHgLHe(4$&{sfbiC{c3`uY*ppwinlja@V1NGZ>Pky>98gIHpd$9?k3 zm46IASN>I9VYzXN)|RC@tkq+87a-s=q)hmkyacxJ8e5b&lQ-la=p{xQAGt4a2QVZY zfr2!Jn{4XJ`~=lvC|UCCdvVjMxNHXwyPNRPBTHWKbk=Fl#8fhG?X-lI&2W!+PWL$$ zrV;!=TRvn#SKarvj$1P*S}ou}ew$lag*Nweu#$|wj=*s{PeN+K2?%8sg$(f<8$rSZpwehlKvKvRIAmK6aOjhWEJmL z{Qi%w!_x$Dx*^$C$Zc+coYc3V`DY zBeXHi-_h#lZsvAsxRNLkz$yGWA`(0WDv|#l89E6f=lfh#C=?*{$tkY!x2Meh#wasHa0%v@xw;}Jd>0OamEfSljl`Ro?w|~%rtl9@u&z9 zj*a7is zMq*5>%Fwy-^ZFdU*gaY_-sJ?tv?6%93gIO1U0GJL`-o~dsSbCY|A($wi}M=Bc!p$8 zW${v`|KS2=Es>czf^2uU#rGGAoY8FOx`zfPZYxLVG)!(aRn$54`-z@8%$ zHIax693?Z~anGZ^js*75TxX65K%WVPmI>OyVEu z5&xNoq`3+L4N_81(tjC6ibtzNuo99Yt5FF8T~aE+4gL-m5B?L9G7An^07y8yQi-VD zu?74ozz91kq)cIjMv6w}5hB9SVEY-t9}j~R=V?%8gM09q2IzdIf%rDzlM*~ihzgN} zA)I*>(1hSKtysz2E@2ftaF->L!s?DVEPDLD1^;QTk`M;`36l^%ghF!b>h=5Hyr$(% zt$3*-{e2-?>Ba+T)YG%*5t;^G=0K(*QBj-z0LjCy!i{;^gq%px}wO@SftkcVJ93S{}T<#ifAaT2e`2d z#KlKGh1uC4Sj4OlEa4(Q=>gZ!&n&34FaEQ-@KlRqzJ2K|enu%=z`G|WocD{r-*B;S zmNyu;=9woSe)%=G@ZTHRX&!CD|Ew9;;@o!i9RIZ|hY;}sHy;fjJODp*3#{LP{rlj2 zM2_kbdV~bE%W$Ij$z`NQHw9_@XUv@AlNmaw|AK{dbDV;pL=wV6$8SbDV~1xW>3KHY@(P4FmXB6t;Gro-{S z%#ejF9X~D+Se}@cfYGo~!p+*d3I-GS5H^0UtG`d^6V8`MSs)gwTXgPCYh$J?oKF34xkb)x{DnR z&#tRg4X!Vpv81I-{Xb=cI=Za@jzw;`G-F*ApDCJBL!gH2_TR~k*S>Q0jahreO8=2yWjVaeuy#FNIzT=0;9SC9*|3MUU{KFDCg(z_tveq0fY7(Qq;~&?b zBMd>2b>CED_+*N6{Lj(CO&?8}@e;kQ02*ioqV4ggU;X`G?mhm1DPV)RK z?}!gs|9ygJ7ZJlC4OEK%rYk{$LuGwuvLLYSmg~Ry?nkQ1Vgd!IZnxzI3B9HQwJ?#2 zAJd;8oj>9fm11j#{|PMHZMpv2?@d-MHvSp;*3C8=oBR6Jho5}uy$?PX{|yYPDI(bO zwEwz(3kX79gBGK}2H)O#Z}J=Oe3*TyM6aIpn{BZ6|6YC@50qivbb$v+3KXm-(#%Yn ze`qSOaFpH69wB<4DP7@po8}pUwvn7t!E<3PMJg+5-MS zAj+Q{Z(rQ_!EKWOg0dkNm@7fVAA*Wc#)AM@uEgq!&afIm(t}OPD9||EREcN?3kYU} z5|UuGks-%JCY-b?BW99dai+v=l4Xst=b}eVvx(q;ggj$QfCcqr+E2#yUZ z@j<{yDUs9O_<44EBWvNkHYs947AqDdRVBMH!K*v5pvf&-?&W1AM~}mGuw9sc`qY%3 zx^X?5@2Qr~{ zm7vz71J+4}U5VD#tp-=_xkfcF0n&nLO?`;h<>!v&|IG(iZZLpfYzg1g)s!9WQbHKC z3Zk_1&ABTUHOg%`F`6t>Z(Mq>O6{7#!Q zX92s4OYyVkvq(hhw zl>cUwX(5J!EQ4VC;(5ydv=o~lE2&Rm|8umMCN(Tde)N++()&j|8?FfW<>eFVYO31u zKSv(pWGS!zIWpX21*iM38rW@z&7faK0Rq;f1i@o$-ED_WeI}+ySKci7a7g1FRxm^|n7qb@z?#O&wb@jIyhKIPmlYEu3S2-aGT4h0Ru{Gs^2 z;sC-4l&ipmN+95kzF-uy*!--O@;w^R6wel^Fvh2uZ=pV5=ZkunE=>wky$zfcnV zci|cEN9yqIe`N_EV6f%I_7^VxaiSpt0^f{_z!I0b=6Q|jdWIBGrV<~{cT_4N?ZQB< zjJT9+5zLXSpNC|`4?QUh0*z8iukr-{1O5~n_|F?fSco5Yv?@DDfa6v%tv*?L-Lrll zpC&J!pw&wsY*G^C+w2^IN2(>qty^1NL20Lp{GYH%eNTG#W8S8oE0N{WE=S<|)QR)w zEN@&Q`u*xuxJ82|`ap}6E^42RjilAGFlyCU; za>-dI{OGK2ptK)crwxa|Om6u%I4rY}>p#toFi;BRWm_igtbH(!6;D0T@BrsIFmfWBLAwr<)C*WY?~viQ%kxh&L3 z=oE~9uOv%nmujmG4@=H}i9p2qNf_{%7y*YHHnx9PuNje&wl1&`v7 zQSb*d*Y5on%?fFbXI%hCDV_)a1^Iw~cKv!{gP{6C#Sb0^NvRMY3|&gXfD@Bjn|#db zb{V{qY3YGkN+w8_)?#X-MeVqtfvQj+C0~e;6eTSoxi^=G-ar%m%@Yn|fxAhfyez`O z{}lES@&SL2oDe^5+9AzTV#S&!Rr$54P zC5%SszU)X9Ky7qfx!+^^RvffeO&8_QwfmIsy9N%vbyKjE(5^f}hrok+cuM0cod0oI zGTs1}heFhba}BrMaPbD?MoHvn;~x)Gxc;aAcf!~9FQDYrHzqys*#BfNOp7S7|MkjU ze%?vzue+w~dF}oeKZJhslkee~c0NhGvw)UqNhzMy_r$ZW=CXa`tNV=`Gcu=(XTQF- z`j?kYIN_-Mv!6{j8dH@2Z5==4n3g6A()hTAvq5{-Z$fXZhjSP7_%?JUkC9UTd*ldZ z{s0rw&P%4S{~k@Qnen%Jo_XP|>;8C`>(2Q;{>bq=Zi9>GJ^trNP!j-w!%~Xp3IC%c zoFj}OGniB&z#e<%l^iB`Q(Ai2xhK_B;l(#lobau@jJhf--6dB^`B&bU^v?TJAWv5K zsFU&!eeyRv5>-5xlAr@~YwMhO_>=z2xiaM2IVa)-Voq;s>yLldVUv!H9T(W6)fDMssj_2eO*x5qxU*kXEFQFUaht>@*rNV2Z zw6si@m##M=kQ2BBLF?8eSE}-+J<{n|5=q>r2h5xiD01~52-?&D*QC%M5R`r-Rd8SM zUyzp)*zi13SzsQ)3=Fv>Ir&GhVv~|dEwl$jz+Ye?&=f;H24U<1{u(tg0i^IS$j7EN zQd5+Yn21>%MS%$*fvzK5kNy76#`X0Tay1sQia@8}KVj1zxLm^8B8Rc%xGlK{r4k7_ zk@{d^^F_}rA{V(+Rd+ak9+=#4%iGH`mWpWb4y5F}o7I=`uAp$5ah6g2|ci(26wN|gK#v}hLM~@tii^G3?<(W_a`?>@6 z-YzX;>UhZCw|?-mmS#C3=e3ja-!NOUU!Pug{^8_b&9|7>n({x7C29kD60x~)3#OF+ zg;jw&tLO5oZ^pFx1o0c>7nghockEjRHEJ7EG#R4Rk>`Jk)l{L`;*Uoan$85d#l~ZA zy7t@weS3+Bls)U~uDj~Y0&ju+`@bHB7BEW*0$$Gazlrw1eYO+Cvbf!G&m*iRr`T%K zb#MCBxmaQ_#W+w|S66-c`6uta>sC1e{D{hrCVxU>cmHzPq%q~cjDq0i;RsJo2|fl_ zN>u{R+_B)Pu}6P-@2nm?4i+bXNSw8aR#!UjxlXdgm)@jJSbXc3ar4BFvkSedUPmUK zjwQxlW>8A;Jopdv0{#-6A%1`Wf#CjupiLK;z~tI0D6!K~6E+PYlEnfe4b(~mt+%M+ zNN#II>6nCtgh(ef7tq6O!~=rPiTpo%*8yNvakbyu#xA?CbfkmQY$*2L6{BMB8cR%K zj3&QEQ;dmerl=`KjXlO1HTH@kiWN}=5dmpZ1f-Ykec%6`b7tnAd*8c{U1Sw-hGpKF znKNh3d~@d9xpSLQfe9p$ja_7H%(HH3IE86c0pF+6WSo8|m{c~R{V{R)h? zp(#%@pR891@j^PE$})!@ML9p*rRc^Ft2V9w>D4~x++l^rrHCeIUldDMPpj?NK&URD zlUKY^R@+DulzKw*)RJGqfBxp(^27SJ!_5}~h9unvt(a0K7UyJgyNt3&GtR~9C;4jJsnEIz_@IK?2fX8j|OW{@P!y2%fYEUZ)%yZ*^i z(YBjq-4&j{vg)$c(tb0_sjaWS>=*xd>5&^TMrk!D14H@eYrc2cFK&Wm>h)W;E*o<+ zMw#Aj#)gmyq{yH003&{3?0dia`FHq?JwpxL+dt{myB~Ra+$S^VW0z|9kOAkMa%AUD zF|XH6B&uub@Q{&HhMM6f=P$x57&C`U&OZ?^ti^uE(&ek#7q(kvUQj~g{?fSjR;{@Z zpSQ?Z^N{^Ve#p8_|8#z;IWc&C4F zG_!>5pAW|-p9O#x1z4o(zGYGK*EHqz=Y<>q^7ylfdHcyYf9ZZLo>O{xY*xcGCpnl| z>R6?MOF{zQS{58s+kW$Apuqa z%b{Zf+pV{s&29d^Fu(3E2PE=%89~D(s|p&nV0jTb5q0xCLJ)uQN4pe1GP`z3B|Z4M zsqFuUyRs2DI88}w^)G8k{oOw6KGj@)MWuTKv?!y5?r8O_qayU(WyIMutD`pZOG#+CUJk(se_T zASpbeb|Ufo{OElgWJDH4-MLMoKWV{7ABkNOgc&#r&N}G`YG^|N zmV>Y%E>n&t7w)+KSxQKasQxXI)2&PC0eg>VNsHJtx%J+s!07-itEl?v?{B;NZ$Hae zy{NF=1!sN>e;G^DGw*und2~eKpG|+KgO-B|&VF>hqj^3D`fJ>X$2N!|fP5MVo8u&C zBG4f#n+PH14XuB)aH9@u+**L`#Fa+!fA#0PSFBzu znOgtP7RJ626X)KSfB!W6Nv$9M@}Jl^le+lS%N4)A<)Pbt9cjfS@%feP4?Q`~wd>0t z_}|N7Cu0ABEjlJkNb6dETaw*ci7Q%H>^7NLTULR$P8@mgo=#L2;+ZjT$H_r0HGlp3 zZA12N@i%B&H{$Fc`G8uILc3TKX5hKH$6ruvk^k}d!;BCV4lT5w9Mpknk|U6#8bshv z4!?*gn+gy+f=c;#W@4lufMX&^9K{iyAQ@kml%~fE)7lbg5BxM6ifx`t3o zx?uG)zml0sRn<3-S%5EYDD|wv7b{_y326FrY>YgTn;d^?f`hg_w6aN#U%$NWu2;r? zU>ev&KOat=b@5dtqn*N#b$3stb^>Od9%dQ&kv9GA8j@bWmW8Qu8g*UTTY)R># z|M}2c{Ls6@KTdy=CcT!}nTN8U*cMx9Kh7#L2z0PJA_Eg;Ww2GPe@rc8li-cA|I8vI zdb%VNf4%L|>9ZDOuE@DJ{`S%yxOxv}{TEXKQy7{{OkF?~i3R_dS}xn!00_Ky@S(@Y zx}uFHa5K)sPrMpZUOJLY%#Yt_h%|?N2W|gj$4`3Tf3HM%TAM^4fA+iEtE-Lkm%g{u z_#LopCkF614Y`c(!xOk)$IezidUmtiqPwTy_3)BoysD{3>p$Xsg{L+egS*HS6mBw`MbP)yc3Sr&6y z_3zsu_t$&d+Zl9Qs3pmWULgqVh$^1v=hxQYwdHR1Zq@U*s;2haKfLaSTmMVP!A%h! zeEgLY&if_amX|@87$IV7jS-&JYs&t1`=gP}4JO~8GV{SF#zJPg{r~(Acg&c*V1p8} zFyJcjKkoRSVc3xV#3sGt*jW$}R3i0_u;@=F{f#sc%N3mQW)~I}9+T$fk68$rwj%n+ zRHSU!i_Bb!Tc&?zc>+ufJPi$vKf3xKh%?tsB)WF)__yC*7S%tFTQrwAUH>e#xX9s8 zL^AQ~zuf)NCv!JcDO)*SLUF~lx5z3q(ghgI>UCxRee%_8^;KEK{;$3Mp2;82R^_cG z|M0i_pL^xqpjy^pA~wdv#jUV^t{Bl@l8}bi*5V$GK#dp*PfKO_Oo>s?D(RN}j&N!_ zB36Hx4FckH`{7yOI$}ggqg*LsKVn71rg%7!DROLR1@4+-M>w?|;q^CAC~Q6cf{8O5 zM>w-_#1?8lcmo*yi%d?MJJcciQ})1YNDIG;K{_!!+mM7s-tA$73TmPpyp8Xe07>>yf{AuYB>GU&epJ!~g5hj>W-i)M^ys*IF~uq4X{%3wgup8w0}PdbT))S|Jvum1S;x7!}Yc0+kZRe(b4 zsrvectA6vZE3f@mVUJKVhaXKL1SUxw%mV+K@`UN2IQ$7E^Uo1k>ZR*X%#1m1 zsmT6`91;8Mn74&LV*eTRm(iCv(r|YDSsxlKTbF9L(EhnDi6=Pd5B4MR2JK%u$aT0T zswo|)RC+-&nHO;YI?pbfBnDO2>(AiJkeGq-_CrQu4FPlPspIfF@FR8!@Btz$7g2&)v2v_8Y7@kW;vTEZ7jjVq=ojdi|+&7*A{LhpJdSL>-sp66k>-YB9m&2K68N#-u||{1IOKmiqjh4^&CN|`UHrRy9(rK|7bm$ou3?__{ohqp)yn8) z#J{Gl{?ecS~suvFWG3yZzFi|9##1OdkWzP>r%GtEyp>c*3Zfuhl~x8`S6#nZjn-kFWmcsx_Hz z5wdlNkN!vTZ)j}%+3#;hb-Zdp@os>`8(5D%^Qyny@t9L(0~JEWt`b{*3+;bH_OrpK zCswXrH{rt>qKMLfG9($&u?_s^#>`)5_KsjJz5YCCnd}GiB>VgYcH)ePZNuva!W-aw z{DbMCtdB?@1rQE)M-bG6vBaB?{i_u^bVWey2+jz`8v{>$;}`^j#KPuc>Kux=yVQSc zSX7s*fS}S0JXDoWG>G7G`ay6u=LpHyu~XX-QtGOR<`T1fufG``J4POVZ~tTx2#Cch zB$k=GDdz8gX4Ev%)oV8c;U8EoVw#(rl-5NJ(k8@iT#)78r&qgQ?P>4QC(e!0|GPUC z1^sn8NE6VG2zFnFh#m#Q`X!#2S2K0By{Q;g5JVJI1A*A}p31_ZHTu^zHve%_l@TsY zkbH%qOM*a((2+Tp1O=A?*%t4N|Ytp7p13$EI|B%}lM3I2G13<7)<5I9I3 zIVjjB(476db2`5UQj$(3QceZ#-_|#Omn>WTyBq(r%b^#3@5&qAp7=4JwWwZaB0run z51+2s`JnTz`t5BvKQWll*zvFv_yRSL3uVFvxHOYA%1b7(ZSl)L-Gx&f2+zm`#c+l5 zTW9|CJD2|jAJHU%5-@5AGIpdxVE_Mq_{E(MI{!yk-!##ljhHIoA4r}#XW_+H{q?BR zuY7OP47QgH`r}%0xSmPj1%cEkg6JTGDWspcJHYW=Kz#!`qUwhACr!d3PE<`TUbD&| z6ZizUd&WL209U;21`Q93DOEbeHDR?yH@T9;AXQyz8cERs@|)=bdqe?A5@=!-&Nis7 zD9@U4FPa*gF1z|4c#B6yA;>H#Zhz4^$BGWw+5)Rn+Yur(ncP5^BW*)r1jMP-Pcsb*0Oqxz!(eZ@l{v+(M{MIPv`02`Oy1V?f1nBsQ9d^cNl)I>5tj z36dx&!Kv*CTN3;_vvEXvU6LQzKVg_2q*YOZ6t=#Rb&JE_>rV(EAa;ac>xN8__xQt* z$hy8+_MhxNd^CD&ZA2y$+lnQ5rt$1Ic4P}^5?ZSg0x<=ZAghvvtQ6T;v5;L#GF;W> z8lko(e&Z^rd_{Ww&Cx|6sD~&P&JdHX90J!4M|fO9ENlQU0}84@k>RG4ad`V7Xd)pF zIF+x+&CvdDKfL29+Z7sHh;%M48S0U>BgK62>t#>RlSOl@?Pvd91y7Ie9CA2HhH9im zWPNjDpU2iLslf7%`NaXYnf)ho5@S#7yh}IrU9mW<;_}1Yy5-~QKYUPWrX-45qyK=8 zxnoc2+_|0FzlMakcJ=HIwGvWjMRn8ekF2Q=u3^WDvm+R3ajWt_ojaBE?cJl#Hr@O5 z>W1$KcI#S-GfbSd;&XamEL*)~*{Y?>R<2xK#!vu&hxWU$O#EZLE!4m)6rW&K$xA6|H|@Cm|jsNRh3we{Mx7*Bbn(uBsYIc>Ngx zH86^d#ZBSgqg$tK2lnaRvn%EfKCp_8;yTaVZpWodiCwy zvtRG-eR}t(t*t95DZ(8+%U7*^{p~5AFIjG;AvK_sR%Fj^UA7(At9Q?CnAF&s$<58d zojxm8u7!jt)8>>{R&(D~r0qn>iN!%x4e1c7pady0i~a8~WZ)am-W-x0PK-YH=kHIR z>DQlOR6^j|;Q))uS7a9a&FG=3wg;V1_8-zg^M~59S`fEp(zW9%sC-3w{k{Fedhi^r z-3ZW2J$kqjn=Xi9dAtRP7(w&siJ^^B8+VAG1Ox$QY(mH?154L(EGzwLnVLuEd;K$v zmj;oB*9MP&nr(agX-oULd1}?k+qT2y6>Ejs0e#Km!(K^Di-F$Gz&&$_MUddM= z#TB4P`vl8regg@~3-GZCaGEJ01+uluhM_BJO~L4Tpm_d01CKM{C5>N^|HNY&*L_g= z_%WRWWuylZL$L^9lA@j@bgvR;3@!ZAq)PMQb5tiE%Q1_6=3|u>d<=sNvC|YPa*cU(fX668jIKX1bqqHu;$Tr)q4Fi zx2t7p9YANs-|Nq~v;qDrSFOWeWDqE+ZGin)YwZWm0nM1R2yd(~qF5*k;W_H5bcvh@hL*LOvDT3A~#7~7mU|voJa%`h%a)N4M9pc(*0sc8Txxc>Zd@VRae>!)O z*=3M^MT);}{Q z0x^Of4DkpO#|=zeK4vd@8I~E+M=w~d!jWIyZmc7)YNM{#KYi>x{$fSmem1B1`{|m- z7Z=taH>jQQ3^eiCMGztG!l-MLR1gI1ovi0+mz#X}=uXEzxBj#BcCUddY}o&FE6h1& zki1Ypba0wDLaC%zU{F<((hzH2)sVXRV_bkz{!O}KI>h><&r{nAlB9dr0{v0r%_a2{ zmNo3x3%kZnXOfgP#CEnvQO?Njc^|IiS3f{R%k;-h3=fRzG_*5+&l0tCk7D(R(LoLw z6erfyr0$!6*Fb^>GpnK{Q#P;TO^ciatuf?ErWc{QYK-FYTZVt;{^@q9`7KR<-7Am3 zjAopj#N~gJ?Pp83f3H7#VCVmF`w~94QCV3%WZV8DM+`mrn1c@9Z&&RkBJ{lvzerBc zxDM<8#N})M9I57#9{(`@1WcTtiOcu+ixq8&$KS2X{_VODMyxspl0In5b6(a*;vYC8 zQ)H%u)ZlfVaOuh+=Mp#|w1X=q(i*YbS5=#ZnKVoaISsiLqn*t}bJDmJdi_2AA=~rz zlWE$zysv!zUjJd`F#`+C9rpS#N%I_mW9>0QJWlTr(WNl?_%WrUpIy7ON-mPyK^Ku) z8st-#so;#E?eg)FUj)q%YEJ4)3_eFtrAWZ-3~Q?CF8{CCe_sD##j~Tk7-eERB3*Va z()!G_&W`Mn|KW-T=A+(9wMb8OGC!HP=ZMmMw^5I8=wf}CS1Pn**>uy?N`})8CB)Le z*_a4QQ4m53zjFMyg5#IqDp+QS#SRE(kAE6GQB15+>d6BhnP^ly@IQY2W6<>T*Udmj zY&Q0@!;k@QK6h)H-N$CEU0442bFYUE9^U?qg?s;*W;SB*&g>nr+1Ni6$ril+i~tQ+ z%=x=|m=;Gt1jI@DFEJ5xRJ1rdKpi3QtGYCel1aow$odP+_ydo`#LP)zp=*K?GhqY; zN8${1SxKrD4oU~FzsH}94R-78hfvt;=I`uvjgR7kh0qIs`o`F1uI0_KN zlW{ZyPXgY)B{@$Y*SU9bUd!$O%%MfpiZqn5Ik*{8jDrw}iC}%C51g!NXujoxraHf8fW6 z8YL)21HrLi5+JiV>mR{Xl6LB$a}MNmMpxwVZ~gq;TI^>_w|}oc&2c(lg^yp9YEAh0 zAMU_MM&Se5_w$hj5&|C>9JN_4odq!A@z0FE!JgqH7#$%61~#fofP4S5r5=B`TKmr- zyOiK;mk2RsCddPTU{^pmkTugEg2W(HT`J&R4d$R~CMX_ilzk8pxcJ90Hz5rn<~$RJ zH%;D2`Qo;Qr{_J{CrbL2Dl^xF}n4Cln> zKQOyyZA}v!(%0aBZu+R2dK>w3I)gx$o>jyao>Qjv)we0PF4<&PjR#cBQ@H>KQt zM2FD>@f1urpaw#kfB-|t*MGz;bu5ue{r-b;^A1QUVt!%@d>6G~g5sfuDJg&1^iQt~ zZqyYpKvGhIk{{M#tMQ+GLxm>Ck&6yanPpYYuA_7lZtGqxXA zwFS|0ue|&0n764T-z@*>?Vk>v=EzGfQT`k({`||}6ZU~y&HBL&w2V7uKXsHsD#gq3 z^o;{4HnDJXWw9P(OpIEHv!w~yA4SYWN@A;)RnF~Ygm>v&Z$MzDL*nShM@SK`e+Ui* zo5$bVPt-PAlD5gK=W+&D|1xy?%zM+h@{A$C5FkbD~t?3nxf=+47B@n;%s zrv7#MpN`uZM2IfVLMvZFT-H~CY)qx@n3fmQ8jk<~KmbWZK~(WuU#bqZWlylFx~fm& zS{j`GOc?(;eo5VoHT+FY)gVc9jX1a*5z`>vG@(kAxUl~1a}y7Z>U8X&Lc{FDIzmY? zq#H^{NP$aQ;=Sb!&wf@%j@|ozf(WCa)^J%6Hb7_l$!I_E6?i3NuaaSWpbBCdC@aVx zf8){1P-0=jFTp7>;|gXw_4v@_?cW0O71+NZ@9~dVY#5eEVOsLFw?6#w)&EG#4rO@z z_x_U%LXKh~(`Zq`+s_tfKm6zf#yh-lajwM?GX5^1`_^(@p$_oBw!(5ZvNget!nOi z+ybCsg-jAne?l-BBsrHExR#o&RDvY)_b$$P{)EnZ;jIDj{wFt?Isq@F6bURWh?MbD zrX)e==Fk!)xxG{ZXT?h^@v#XC$dcF*oDrKZr6yKQQ1y8I)A7Fz@1dkR7P_FpPz%w7 z)6&*mNMFsM%C1DJxFm?~MLEwL*XfYH`OK8Yt9n5UND2$t3~0poVZw;ENzQL*YW~^V z<%U6?`t$x@4F`k8zzcdhR>%`uYwSmhQ8Yi^=<)wb@RyM#@ZALQ_HS*`nY|-yiTeF= z_V4j0(_=UhJ3`?bOExz*|L2jH&b{;x)p%$Eqrjf0wf@KBp9Oz69FE8^7~cK`6z@ON z=C3mwM|@@Wqu$Y=+Elhm{(_S1ID}x5xG9+y)c6tulG*u1NIH+hf(YxgOp{V4t!bAL zlIiugBb5=h5LT8GNP7H%41weA$6CcEw;z1A@XEK#*<*;gG2Aqb4feL>_y;{d*O3FM?-5)AEPBjA|egGMI$qI%e=^b}Ox?^J96ViS1iU!14^xv^d-YX||-nBd4{ut`3O#&Hi z3$Y`NDlz2NuF2LU#Tv+%njM~KWV)PAHF=z`ZDP!JkhjsHovb(>RFUHjw62~5iy5xm<@urpRLS);;m`8ULoU71Dqka9@CsjCGpR! z`iPLaLZs(IT1L@v{AddN(x4Egy1RB5uR{jSCClrd293u*qphal#2Max(s0>?_VeEI z`ujeuxp4bJ<0)u)A}9aW7l;dpbq&f-q}46RP26>8hl`q0FDi(J4Ki$39tnx@1)x7RV8dg+D?5V|-sZZn) z49?I32)9)7xIqQC9MrM6fV;0T3-CZqm56Q2auGWZI^Qlndwt_yKdMsosS`+wCZYpk zZX-AbA(=TS0OIOWKD{a^gG_2SEyVH1g0Us&|MW|5EnKo(U`LM~{$DI#GrdD>B^s6drQAQJxV*2loS`$*3~yQHmxhGShJ>V z^0e6>&z#rX1TX)M_MhJVGZ?>sVq@}q{``^r13tfk^QpV@l8zTCKjnb-fT z8558HSA%n0D_bA_#d*o`r*t0FQC_LwPH+G~d|%CZP#B~b_L|1T;ZLuf$5+T*1-zW* zlXJRt$L$Uq+}Ch*Kxpk$;<7i&ADdefce1Jlv^CwWC^_ZK?uB`-9R-RamF|vHh~)1+ zsQlN*)#6SgW%v&0ul6dwWM_P*Lx4cJMnpHDs_JabuIt*(2)ydznCHr-uA*BiGKM%| z-v4KWYm;Ys{5LtItrF<*ZxvjdoY~{Q$sui(K#zZ`;M(NO9{)`aX{!Woq4=A3v!knW zZvwBo&1E7i-SCGZi5-zWUrICZKo~@E5CSm~wh2e1=Tjw96A;uqq#7q$B^%vwN z1|Whe5gvasBX2)s5@HpBZKydYr5SkI%zmmHQ$HMEfip~4DXAv0=xAcjM+qfa>mVPQ z_K&s<-~!kO2*E}8hQeR>?+~&7-Mi<}E`w{4DCbd}FXTLAyFbU}DyC9pbzb_8dK@})Eb4=SN92O-kyB4<@i^BvQxs0C_EwL`TzkAFj*r5x#d z;)}}0duP<3TDtyt#pZLPOE1|8ug@esM7$-ezCpfepV(r?B~%yfqi!;B!<1^-WpJvK zXatG<{3TokWg=uSg^+w5qcq?_CBm4oWFoz8h@XWUX&!$DOTZLNVzJ`U$-v|BXWQe6 z?$>WT8U6gV4l2WZ@%|^PpZETUU9vgAdHdN+)}I)N^l^dXS69BUhQphs`EV$NyxSAi zqz{gWB-8VyC5augS<0FPty|d|;lhU2GwUF=Y+a|AZYZ{{*WZo={aOqiA;^3Dz5N7J zRn4m)Gdu@3t^G`1+3=T1RlnM^m<*I$kTX-O3NkWfn@n+T?7O%+3qVrDq!Rn}&Of+M z!CPN6IKiXji;P;V@QV=RE=6vLNkp?+M%AZ2tE+89voXGlDu^D2h$MMZ5`)XPeWu?a zQ)C!PtOfH4gT$i4-=e?DhO$d_{FbbGrXOXK3Md`xY1H zr@hlh%h!6xwMG2FxDYU+57&L?i~5_VR-+fxaXuIsR}VJI;+TsJrG7Ra{Js96v7>P_ zBd2YZ$1`)j8r7h#kzsAXiZl$`{yqL4e`i9@_PqTBQwBS~X~Fx?z*H1CL8f5lkh6p^ zNF0k{xUM; ziBy-@*=ZAoSCwocYzkDJ1qwlxY1t_&c3wd`IVfQrn1?2tS&@nYB1h{(I(YpFPkqAU z@%Q!<%wRoVHn;hE%e0#PwkbHQpS(9gbjH}(c~95CSbZu{`Cy>)QsY$=KTDv&M z(;fMdZYHmpP&sjVgLr<4QQWsU=dTBJ;Bj$v1aKDmrN_mLT`IMzCiR2YE1H_!G06LW z2e=_N=lSc8zcOX~@z=TzKY#uF_3KYMN~+@}78d4)N6x47Ab54q-hX=kNj6~5qhU1+ z$W~%Md7+Nc@|ruQPX4vC3;}@&BD01$>;?r*7@0PR;|{uJaVe|$upG$~kwL6>BV<|z zlT#0j*Pk7h$3L(fu_tdof{1ox;d%G9&tLe8OWvq>^~6s7JK(apIrUZJNJh=cERn7i zVM`v~uK>@9HIsWw^eN6A)>&@mVkc%!#8F+6VA1&zQK96#vc{>ant1;r^X94M{>uF4 zit47(FRZ`sh|-qiYWnYHo&)BYytE zQq*)|yRng93ha0aeHdjstl0;fe4;52Zy zJhW5(r|ak~gLsU>iXw+)7sujKB-ZugVc+}y_Tt{IKKYeY}m2X$ruVzp$pg+F-`KNtLw#Pn&^ea?1L46>iUg3@L_e>W@p*UrP+kGG$+x#QQLe7KUM7L$Jd0)g-`$giKpNN734UX&1G zXCliaI{4cl6vdmv`p5f!9t9c=J7gHSukHLj*?su2vU*qgfTZ07zDyUHc){XNcfj_9 zj8B_k&_yyvor>cM^7YDAR}D$gcV4IhuRo1M81Zl_z&S48NuHCcvTLbWD zi3+w7Ki>a@jYy{gI|-Xwknios+mE)GI6H~U z_x9uM$J-Ar48liqjY<3*@#l{b*uo&jo+WR!$FF3!VaLi&Rd^>a^M*p0NA327-V31e zdA9<47KD0$A*Ecem{9y|l)@n;h>h9M4A+nBc>AOM>Q zVe&QE&kuGg`oq2@aTe=b2N9v;o=ryc&3@!}UN3)sK5aD2JhyB2LUq3DHJ3=tUe|QM6Kl5=|Jk=BXWNds9rBZSPGL!9^PKff4b7Mi=1K*KekHj-*|q4b zp+&hlWC<2c@+6^-Ye1+hQ)uzHy!LtBIj@$>BQ0^hT@OthkDtGr&-~TLbZt7}F$#|F zvH4$<|MB`8pXTwGLx4Ioc>j~Z&-nE}A0*>!!ymu+C}0*F-hT9gNgr#rV*8OtCumv8 z9uV53dP?hnTI=hz(`Sr===F2KmU6r7Mc{8kSm=c3B^3Ds&2fQc)Xx zUVl4wna9QB4=WNo5%Z>mw;u*YCi3QEKX**8>0Oe`UsAN5A><^~jZD}{gVUgxeT!ue zITDyY4Slj2ADu`hhIY=ST?VcZWiwL5>qsri;_8ZELqhJ0_^KtzVd+d*42_$L)IsUj z=s)o=1^!qE@s^~W=s%!i?vF;aKXYhd0eXQy!IR6m3&>$)WeO3VoTU2Pd+Iy$h;*X4 zU_;|FWo6?i@i zI}$Ri0rH$foF4xc;w%%~<1d!u?MLI#l6mUwM&@7CXs%%;Ov%-*2aKZ1|P-`fvN zVoRC7zj?p1Q$g~qVTI;=NT0Zp7l>FlCuz>hIj|%hkMSf6%bUQmcaMCkZzQO5=FnMI z;C#pfv0xG>gsGbtyBMz(aIJ(0m#=*tI5s!@M|RJ{hR*SW@E!;BggX=TNcEh$VYNr! zq*nw8^T>t*B|{*X1Ywb3TxPFtI(zK;s)k@=lms~76z<;tJF{^_CZMzB=|Pjx6H)&7 zn-MiIB!F>1rMbNUp7Io?lMa^Y`kBjRq-2zVJ(|tElGm zxit0JoHMVfOMSGOUM&fkSpsSZL=m0{ZW)CL!s$3`nGrdzmF)NzXr9rG~KuyE7Xamj&fbr@U0Qrbc!dGGLfpf1@iak`X$E!{&&;*FR#n83(cL z?BC<>@lW@w&S`+~{zuTz#~O`~w;yjm5gX9v;b?C^>2{(qiNkO!^FO@G3Yd{CY+5u;l1L@?t{9T%A=^Tul?MaS8739v}pFclQ9n zB}j0GAcMQRd$8cH!9BRU4DRkTocaE<&ds^&n^~)O?>*hs^}hAgngV-|LPiH%oP_CN zhPTo82uI!|#Z}2mYf1-|3Ch4y0zzjccqHmRteg^s75;)f-n50^S^VwNFVke5hk3#! z#|wQI9Xl^ry}Rewd}@vhe~~yCUEM9OImI8(2n<&6 zLmkezRGCbSvw9b@r>FeozfW5ByxjgCGAx$ZBppAL6>1k3nLeH&N(5Z1<4mk$tfNrp z3F@bOSWNyH_W{L5FZpYz`a>RRo4Q!&cPl_-&{nXXakh5q>CrrKmHGma1nt&kHQ^(M z!eo8bi)K`VpoKaxFQ@?cg(6Tsg$m|G{iW?%`YO&0fvW8jzi%zT8Vh*S%42CFp5%$x z4LUx-I~#z*NpXzPVfAH*a@1J4;2X}-Aa(7VAlPAd=3 zI0ZkmMobm$cm{6<4~p|f^84=2c!Q{>RQ7JWL6FT?l>%pIRo>w5e;<-@F=%Fj@&}aH zjrbXV%czMdK{}hPvd_5|BE}|(`~C2*9OE_m+lMGEvfVyOY_#}n60LOn`jlyIG-ONl z_y%u={xelfc&gqd$jK}_ozD6Lyk`?m5yMvNaO^atCL{^%Bcs-OIY37kZi;NGo`+bo zd*barufrQ;o?g>_zI%>p8c*#ny^J(A7w+I-@VTdx)Hrmb3WR}6G0W|!^$}!_fP20o zO`eE(JZd=S9dClghl~>a9(LtWI==6dz~6$(CX9gx z3?p*VsOeUGnRS@BXQ`iSXY0pl(NC2qWd}7xv{Q#F(Kqc(Lhiw&k^B5RqAiv{Ut_hY1O`JTf;c<<<7AoYDZuCj@o`+V#L>UB)+PB z2T2I2RvhI%*EeEsz&qX{kS>NjJyM{V zP|}kqG(Py5?%OV?>?yx+!0!1uTh^24Ix+KZ*C-b!@w{#z6`r<}R8LYtiKwAuADMAl zAQ+W5hITWfSDfM^SQ*W}szITyqe2??biJitXz=U-@$~=Zv>ZhX;pfwY0JYg#?NyaG z@jmJMhj8Qp7%qECgKTg|nCDj5sc02xP#Tw)KHC2!6JW9jEBAj_b{4+}>Lj z9d6`%B6Tt00#v;`KVV1#FTz!AcXT9OAtBu~&Ody>d+hT{tvyJ_R+AXLK^v1))(vH~ z?#_Le|F7QJ^n?Sj2o{*UYJFr;hiT#IfU30}Rh(W@J_KSYq}m}AZ}+3KjzljvDb9FN zPQt;}Q|b$WwS3Dn{*(=;{ae(Fe792U=qP^ULXu^6I&)ZB>8l@-YHQ43)9X@$@$y}!;=^mye$B_kb1 zi2C2)vj5>B4MisFjz~6(Y<+)FZCI}}FORlBIwkt*|0#+sCVDxdor8rN2;|Fj=I$5d z+BM8+6W|f8scuQIS&sp?NbB2EaaOh}xBfSnKP3U#N4Km=SZXzZAA4N{ifuEt)jk9PPdRchyu%-W2T`_b9RZh5tT5F~2Wwb2O-A;Te zuC_VO?jSNa6j_{aEb2{X^oj|0BT9DbIz>8n8DhU4d!A>UKr^0@@On5%kg7cKNAr0B zCJ(x=m1lXm?DMIncI;$xY~*VxZq2Y6^ z@8=!jimN1Nlynxfwhm{h9Hq%x?}b&VV>dB2bT>m~TmG!=GYku;lwn$Q-gtn(H%RiB z-NyE9wOIBtbe(`~mbZt~uQX8~=(?ZUJZX;4z42x<`LDS!7Ru?Kc`VmO#;RI3V(Dm% zR$ZmNj~Y%H^M)_BVxa(Wd6C<+V?`oQ&Ep;g&3OhH9*LnU4gKmk+@X^JfHZ_x@_R3oZ%SZ}>vCe!xK0nq~W@X~iuc z-SB}oyuoi;WrOt$*RNCj-R!e#9p8cYp5tz^t#~>*o*X4*9d~S*z6W=ZO|5D$mW0~- zu8Bf8V%4S=;Nk05@sv-DR@e1d09y5h5)@YKjiHsz9cE8;yv7Y3j>CeO?8j{qq=I`6vjyftz70!HAHValpMo zAtZrTo|iZpj^>-YP_H;~%T4JF#dM^>lj$Paylo2kF+0;Ps0TjF|3|{ya7>K?);Cg| zd?FDl?H~?DyY{=~v-sU6*Tq^u%x|B!bpBfVfj89~Jb_2>{F|zl&fV1ArD z?NNyM#p7wFBPir~A1PXKxOc1*cABj!yH3c9bmuWx5&dF)Y7rI5b55oC`Dc%woZ#*b zUK!nZo3Ct>+8B!qR_o`h0?R78>p(3^NBS zO6~Ke>%YRiTJGKdW+h8mUcX;3vVxWw6pb z7HzFMX(L!Ne)GrsTL7&L)nsyk>1hd!u;#5m$qCO8jvw6mfW^H6Kwee__1Aw{gU^`XnGq2kTl zNbyTHIGa7L4peHq>^b&tv{<$OIAf^xJEcEWQzY`FG&Lq7&uGd+xsW9*2BoH?TP1T~ z$aljaVvV2Pq>UBkKW|k%$RYL^3d_A1DH1hByMGWl-Bh$YYV(Qm94UGWdtK;tsGNp# zlalYw_3iH%5V8489XG84J}jgh&X-b9e!jrgkOPaxnw&K`N=aB!Dv41`In#)bT%Y?< zr}g)Nb>Vm3R~M_({P~}buWiJ&mQLu#Qw9b}Svb>d^@=WzC1m^FAF5)`$J?z(kCF%w zALrU{&5_~KQf}LcZ`DSk2g$2H4z$yxL7(kFH=fB=6awxn{O7%!D4%XedN_X!RW=JT zAUnx2-b=PzTvN2)&1`xSiMUP$MJ8Ku0S!5R#+29OB4s!kcY=zuAucYQSdq*dJ#A&y zU*brlP2rLzH1}7_tycL)n={J|x8tfuEDbyG!uVgwO5$c#91<7=G|r{FKd3g|Usu<1 zI9X+_N#oa{9>fA%u7R3*zZFHZq}) zML@7v>N`=~O7NKB5575(C%K2lrGR3T>19_=O3C#$m#_(@VJCxIU7~SeOm8wN6sIm_ z-EFL2&JVbI_Iazv$z({X;-w4oZP)P7@%{>Uge2=_|4O-l!)juaOY z{pzFiMFtrguR)}MsF#BTmj3EK$k!FJq-OIMel?!o4tl(00kE7KmgBO~H-Laqx715B zq*4iK_ZZB?2`T<1Rqs@%~3t(xLX`*lg8;QZO2N8)yns=uG> z4g!%w+~5u;1c$oruVd^_z7i+kR-1kKz8xZ;A7bgOzlL#w&$+B?PXPbwC|EF^nx515 zE#-M6H~v49_N&jBr^>JqavO4>tz%?z<_~b5f4`~ERSPU_m7%f!m^N?ACjjS=nx5%_ zmR0{TU@Vj&Zo`}K)$YEeu%%H9hdsk$)|faTCblj=?M!v;r|rWlC(L-U_xljkszwlF z21HJ<#-HaZibtMVK%v^ZI67<17b3#5?LIH|?q!rU$fGyQ;TD7+h)o^sBg5(ZnKLzIbkI=J(|GDbE;fEf`ai4|g)p5utq=>BVM z_C(u+y16PVKilM^k=KZI3`R+5&M-?UAqh7;cy7Gh%4_dTfU2;gsv74CiH@GdSj+5( zgwQwJ&+71c4cOQup@TizcSN<94^w8{JoWUezr4CE@U${GoLl()Ao zkL$KaSb`2uPwN!9BDR4<2Ar1UfWChLEm+Tp0Sw7qvyw%xkC*BO8-FW1!b;3#M&aOC z;fm2kT2qpNSJ!T>dFZE$wDu3?q7qRWSE6WBlZF$VXitZ2K?lUujKjm5??zV8j65)W>9p zj|BmkVf8R2Yl1Qy8;^qrUQTKA)G=^{(By{~L?gBP_DHks6I`8S#lemQI*J-~Oc;?> zg3F)z#IR^FpH>_-NUI!yueKQu@r#}KV?CX_!dJq)>jPSn*KB0;OrvJo#r`O8gJ#PP|( znlTCvF^h&8RWGUL6V^AY#;l`yu}#c)7=`%U#BP zJIQCpeP3WJCyIvO0KE{35a{M?&>UUpj`o}*f$~Mav&MJ$9NzVat)`;d{Jx^9qz%Na zlhd;qu;qgL_)N0W!vzlcy3V=x#Gsl^1qFuG_-KWk%$j|Oo)oKQXRWByVqd9d%BMEl zuY3zq+C#c)+VYQqW8L5$-vizs^_!HUJb?F!a`GwR>ZbRkh9)p>BEZ9cFg>VQ2ZLF# z&LSe5mKk-X%0=UG7sK;XyC`t)Lu@lgBcr&)we8U+@9_3iCw)Fn5F`*rfoyzyy5E5S zH74RF`{;eHm%{dw8`S}Z;*Z44vm>_E?3VgE(2O9lQUYusKj{wQRyY=~> zMhg@^xB1HHsP=k=gQi@4?eaPXb{9a+Mxg z>0J^92<~^LGmWzB5Mp(*bHF|7@7*;SN*!}IPwuG-9+DVKUd9CtX3bz%&A6KPlFAn)T5m>$9SW8%Z}1eiH1v5Kk(i6dw1<|Xy<2_wujlZ*eidvOTeMx? ze!GrNE}K;0<^?*f)=++o20sZsHL3IS6Q)QxI80%%2^YW)YjB%BH26$ko6ii%m?a2ZgDVFpz>W@dpKLt&OkvXG{zn+ zW(i4z^j9--*eZbT&dSb8XE8Qp@YFv>*h3IW9iN3>)V0t;=n( zO=INL0$adRdlRhDV&rXTbow&yb+s@8MP*1pr}`FX2ggYQKL{W81^(jJ$L&IEtzOby z`QnuOjOf!>^?6jmpB4l8dZBuk)1SB#vXjp@|5(YtNx-y6Im*<5IDRF_RHa24CVirX z3Wk(z)PVjwblPH-E^&y_phgqm* z+U---*zh&?wUCZKLnJipd~gsC9Eqc7J=&SSH^ghcM&1lt!uKAcJtuCg`z%ae~=zJc^6TVpu>JrY$D1KG4x_xYkv6WB(jY@k{iGgsp+6cSW{!)?}`7( z$)TJmW|Do7c&F}cfAuFTPS0Edy~LI(nwC_YeAJ(2_A%34)|iIVo{pB6sm$i5sY>SB zaFwbwteVavkZ1yO_zY5*$BCC0sVJ5gf`2+$EAZ7V&gV>6HOnxpZj@j|xi=Og{8BWj zG1<#_psiH|u6sSAPzQ8vN1i*#_TdEyJrQ&K-*<5TE>{H6$`?bMva)6$tvT5f#%9Mb|GFiCW z$%c4Vf6aK&&;*1z@BqPNWG#wwyo?z81JpjYOvDJt{eEqDPVqYoKCZXhgukJa`|2{SaCbXMmsq%Sl3``^3k9+(Tqi5M z->Ig1UacYTKYNOHyaihQ?wLDQ4?p*Yw&$nI%F4dU8&X(RmZO!qe*^{<3e?1jo*3ZvXM8)uVWUkj~HeaX+hD3uX#I<=1cdng}m4Yg@HE>`DB5! z(4B4#($9nkabVxne~&fzq9p=Mw{458*HL@cYo{9!g=&icJq9k3|gW=|OHl z$Jq|ZBlJ+%#|?|v$}1nECFtv95e)z0A*c5PjX0L0TZ7wf>7<%k4ue zgE_w3MM1vVHFohoth9K4&XehbEr85F>Fbu`aVWhvET6`y#-? z&!rn7X7{JVR}=Py3YLz4s!)@)7`}N(Sx)AUn~68kIpm~y{`EeVG*)9~w*vW-TY~W| zI|`}%gBUR+jG_&RNU(*u#TbGkOSSfr7tuOBTX{nxNj`b=)6u3(^JEHR>tw3ab5B{a z!VT#ORXfORI4Yh^ecyIFidh>uT3p_Rq2%pHQ}lscYjwr9Ita!R*vqhXT@ZYN!B$U( z;Rl)XSA$KN`Q4(S6!FtqPg~)}eAe5BH&F1yF5#xZPK(40nHOt#I}S)o^f%p(6p)+3 zUk&XPYaKPG``x#cw6u3%+=v{`CtSeMxw#BkORFMBVy`za)QU{sKgvHL)1L zz(QoNU^5)`^etVV{ClIN@GT0;dLdO}Oqn$GJPtvcG}=Z?uZBkVFAvaa{8XwVV-z@| z9+tym{qOVfKyZA|d;f|jlW6MRmjskv{NDVU_BKDCMid%*Q<4L#^jz1A2hV9^M$bgr zsA#2hNXuCI>YI$cclE%5 zy1ZZSg)6Yw`KE9T*qhL|eLkqGE!z*RLLs?i3#s2i#ZXnI*k4lii|J^@2#TOOkJ*Z8 zk^o-2W01Z?P1OldP!G9{@k1FR_dPm{`Pv+)jIFO%9d|PmUj&H8HPt^mAHhz4TU@wb zD?OY3qDV3VLq78H#uE!<=89vXuLL-&3kDYXy;0BRlm)N;GZSt#<&MEVCOBFBeP1|| zn@6;iuXa+Fo;Tm>>8YQ-Ho`(O<4B@Ku~NIdi6OGQupz}-WUE!)!Q#jJ!fuOUE80?d zmfn|dF~`!1hGg0{LkKehb3S}hFz5q<*F-BD$uDIk`W}Pk^R~-Bs=577$x(DKE#{;3 zaX{5Kigh*PFq?%%oIQm8P#fm7_=|R{JT)K0{EeE-sV18-P92nO_d`nA)m}CNN+bK> z;dO#>1{0D*+`X|9ln?J)pI#CMtRLK{C(;KA3kkqvJ`aQ>W>}%W5n3oxr9$S1)o_0&vcX-7i>V(0UQ=$gMLV+M(bAD^6s$qY|bvTFT5J$3o z4vo#nr9$HzfxQ&MW@*>R#C+p~r}>Ggla>064!TzyK|~8@ zcqp$8^|zCb+hJL(hP%ggvsRcK4l+CCz9@Q$qd2;rj$7Z(39$n-wA(1OBK5O|Xq7%X zKZQZWvO3H!tG^YQL>MB!dFf(D8dMbpFR94dXu7X9-n${R%3dIdUmj=qE8Taj5~B{nT2T{%ePOWPIiwMBrl3&b zWEZ6Gov7ZwFvl%(3du&rr5Y0g313wM`=iX2Ru_9SJQ!zM@fwY#{eDSVBXk9oa*o?- zHR_9cQCj20hgTFrDqpRBU_La&H4pPv|1(UM`aEtbbuE!Hn`@g;aToIN%p)$-N!{gS|41B)16)@0qB!^#(=_|M1! z3Yl7-;@&fcD3eBUSNj}yn;&dhh+09`{ z{8AP3h%($!j(|eR;xg}++mX4ZXlC@jQRmfl@l{@w;(bBS#|4nVpRc>LROPgzt?=*7 z<69osdM)Qri?b#_p1fH!3iENpoo|h@87l;{89P!~%>tG@#mo5|uB70_ec2%9?4sa_ zwB2tD)Q+ayCLaM~hD{?%-?wBxT~zTkwbzi$%QECMEjHvr_cCdG+4l!M!F!_|c1rkx zactXhIyWDp#^OXP$;sSSBWoHHKW$Ymo;jI87z4#O8OXPt9+yZFm;I8TYG|lK%{OqJ zcpOXRws59nDV`3)S8`WvTgj#bULsU}(_wZ3uf@HvdT+IDE6@livsgcpu5uQN1uqz> z%*P1+ILZ4?S>*#;-jm}6_Y3?yp#`UU0T2u@fw8x+pM{h>6D@s$Nm;2tiJt&I-* zNmKwqb(NA6?$!a$#k_HI4fBPRIf+E#NxI5*c*{}3GOukUv`~KS4`^{XB_yoMl4eRE=-l0bl znrO6xKSOrdthXpqEQ_P@AY_raK1X(d&+2iKKbrczVkz(#;U^8yom$5cr6;dBgH)j- z5eX5Y4njp9;hMpXra;l<0yt~dZv8-LLb@<_Mj93TQn|(L9940io6z97~^`g0ue&wW>d{!A++6vf!&y0 zlXItsAO9-RKo`E6kAqg84wal{tot5J(e^{YKXp9R z4FPrfH;#*k8&?>d+2=rB7|a&Z>qDXUj#;R{rz6l^&BzwWggDP_Vw;7_PWIU zS0fN5HYJ?uc0>r~=oxeL+8qr;5nP+NQ^Gdo`eC-&`}Mz}T40|r6Yu5?p-~*t{&i3! zdu{iKfmX53v;%^0U&^1|$W+@JK2?YCzyti&CAMLxV3sevYCG2_P;oLHrPSIxVD-?!YQ=eIn{9u&;j@JgQE z+3^)_kHhuahhl}(j@-YCMUuh%^jm;4(aA$H!^9+kCKKWbf&s~4)fPA+8^V=R=ENCuV6~R0 zPr@Q6y(hUhi~PK&f_zYW2XyY!U&;Yz(Fp#eA#U872oT!|C$U=wj(Kk{N&pwN7QJO1 z1nlQEmZM9!_ZWJw5y0{ceDN)lkfaHV`!`#`V>-t$Z#Bg`av;@ESb;TN^s#$-6^LkR z@?k{bFBCnM$hQ-A{eqIdSJVOgaJ!V4g*ymFe9Yyy4RjI!%WGOHj3frjF5H;HblG8W z{Y*{6O;6XsveNy4_l-w5V8GTf245T*`&-Q|=sm0SgFLgU zzI>v*7(QPtXF?;fh(z29{@nH!vnBU?fP&v>u5Uv7gJ8b?%jt%i+|56WSRm$T%lFN~ zmG4q^f^a^PFy8u@X({|HHyr(6$(n+{uVWA~V&FP8_76E1G}H@ghWXlw`c3inJEcLt z0QJ1G>Cd47&}B{H+z=wd=QWPLwPf&u(Tt^T_7nYduOe?`AV%BDpZ+H`DIT64iR0Nz z`M%nkt^!GUpKF?~uO2W)BaB>oQJ;uqy%G0CILaIN^?_O$kM1(6#%mdpRpV44!$RrvVcf8y> z3nznghk}TyX^YFX%%)xsofXf9Ssb2C=(dJ?AE91m4B7g-J{Y9_b^vwsynmC3oWuXi z{AgFwPG<_SKtGjbYc;o3+%ba6>`#&c`*!UGHo$-gzUYOWy?d2H(Zr;$m=>G_qM*0+ z`<@y5Q@j0&Nmo!PCH2Gt)}#B<_d553kNz36kf5d5q+&7v-!Vj318tw(reVlck2QYn zu8!&M$1hhgCue@f`Sy8vX$eSc>{5LHQcr)azS zf_UDksA)dm6{=X~D|A*JNl~k(Z~4r5qB<@BlJ?A&ieA~rZkGPQV?N=*J!XQbDD&MZ z&iE~tbgX3tffIMe5{6+^?JSZ1!=24nEXm2j0x5t06KLbdFR6k^Q_y?Ot5MWbwff9sQfCQ#N@-0Mf6hg*IB>cEiiZ`umFD`|#`)(nh z%l$)hkn~_VbQ!Tc%nA^ihhDCQq?|jmWCg8rRS`wMn>4y~m?i-qAZQSqQ1t69|9^e! zDu!Tf*O@7=eOrdV%~Qs{Ed6jdg4i_75>iPmNiWLuD6YHVRnw*)oc78X{wk)jfu zN8%Z1lErYeTwxsPIG3x{~LloCn zf+O6+11;WCd~L2&cBO{3yT@Wbq_BiIZhP1F@2z2tOm%EGOz;-pQQ_^8X(T zKv7_)`>EjIYun!{3G`;jSM6f(xr~L#L;$?B$g@*ap9AMHqWFN#^dd*=tjj>j8i|>H zH8oO#>+I(jd_24-k1X`3*NiXe^<1?HY`c_V_|*F5lu5SSWSD-ftjz!0pta`xRi8Ys z$GYNv`D6zlc7Yn6d2@zQbteUnAQNK?Py*~#>6__;i2h6RvT;7PbHW%RsxDsWpD=bM zyAM+gMR$ZUOI(g;t8wh%R=)KySjhXlHqa-r1X_|=52jS$_XWVcTQ9IvMv9tDDPw=! zrrN4&)7N|jUZQRoYUs*!roKPG;Y)PkmY7+C%r`y}6#mvSdsutGN>E2}%uN6hY;XkB zs<1$fezPsh%E= z`Mvr_D(z28&up4PF~4Q+SzupL@E<%&)(Oj+@x!Gkeg`qZ4i(|7!zMZ3>7=;7YA!-} zpel)|;z`76eq|mf2@>vj5Nux(8)8)g$jOjPVh8Es0kM~hC!|p~ari$sID-YO$MC$t z-Ca7V#tx*nH%2$=oYH4-A1aWN`A`XG`dLt9rcV_MB@)vZL4$+VLw*mMIm8wIXQ&nD z0cQpBtzVV)*IoT0eMPU8;0RwxS9Gl4b>N_Z#~p&zAA3({2Ye<-M48K`^z)ToF)lMacONfg9wtcCsJ8<-la5k(yPL{)1Nq%o+!Bbl zz-U|+pY6Uz@I2g{HA*YaM#apcOCs)wTny81r002%D$%~(L#nXdz80fy>-94IsroqI zp>H`6zk2tS$K>-%)#0b4h|e83bk<-#dSBV;?LHV)#ZW`Y58ny?j2GoFE>!`~B#i|# zm%{apESJWWfCb#A&3n%8f@t?;XBNu9ivSI)Snzodh}Q#VxSYb|$DZKNtD|CI;2jbE zC01RyZe7hEn69?N9Ty1QT^VCL&%%Is(8?WA%f`SftlENPiTNEutN- zW~(_FDhRwvgC~^GUZ5FTU=##rc$p}nKiN5fD6<4Xg~*WyP{@sktkleVP-I_84_(P4 zrQGp0zw~twH7E1uaJ`4v`{z|{pq>xLF!h++(CKW}jf`Gkta;Mb3{PyLmBW!;2aMI$ zc0uJ+8tOnGyrK72MG8^?msB21X{ZcRX(U8EaXMP5AJ>t@25dLQ(JG)-$B*W} zT}f|pIm(k2klV*CgmO)$0OTVD5;n;I+hl)ebH|#O0v!jz5IBA21$+g3WCLMSdf~tc0R7`-Y zU}t$6HFzMOO8u-R&14;$JYJN9RF7$$x;U>7QeW&#Svs@Ri3-P~AQXLBwdp4rHMuTDUq;Je68woJ11p>2JZJ#pV1B)?DK>)kxe<#~B0_dk zVDCXSA2p@{!psi)7aiP6Va||fxKiy0{_J+aLyt+tgiST!-A zYsI`TQ8e94r-_~>n~UpV#kBW#8rh(@%JDgg3&ohK0D<<`q7ZoUmZ zoW*YIuP2?FGqSe?y2^3g$i)z%87_*;O517LcdBs)zWP&tAOb6(M_8T0! znW1J|?NP;Oka`UCN}|Az28Ck#n~+;wJqh0?0SP2Z3JudS)II1klm-SE-ZZ3cndG7Q z=Sb(Z&~2VmXtqWJo;WCHdmN`FAJ;D+>A`_?diAxxZI~1ygMZ*kIu?d>$gibgnOSDL zZ3(*aH3+Rq&*j8!lYTJY7|Fly0_x}`Oo55`i$9EHU z1z|O1_q54N5yR&_lfy;XNtObwe}G;U{NKci^p`bIrReY8OZ%GdEC^T*Y3SJ zkG@zmCniCehVWxkw-|4ee+P`7ESHs~NZ5CwsV5MmCWv~ei87k}m`yn!+m=ifDI%z$ zaYu{2^1mJn!6*&(6Wg|jAiGKeP*pZG*nL+5B9kpM>QSbR%SgD=muL)4x>%1bp~`vH zhHpE?5wq_|O1Ra!Aw*pj!QHPyVF3bONCzVX`Hycs;PBJ$AIN)Oux+Sq{CMU0H(5WW??v|maYQkl@nfypalh=78grisl$-_SBV$yW&-jj_th*Mb+x zTS=D~4%(5Ii(HX8G0XOWo%8sz|@}W#9P{%z#9*2{F;1GVr7iP(%dwAgiMsW^3)->l=PGe4YTifi zBF@tM^HXD@Qd6-qi$I5mwbCxncuA7%U#5)z~!r+_gASzrbXv(0Z;qw82UxW7f+?*&kYWx4}OmuHY2anjl>NfXTPWjO2j5zrVC<4Ru zS*L`5EC^!=k5FRrAYL?{@jnJA9F*J?B0(ko2Ae>6119tvmt-77;DR4 zy@of2CcbT5XDbS(HOcm=473V^o(I5B=e7xrZxP3Q-?X%Qp8DKS>4xhel+Ru^;3&|< z&#|CKiEEBxt_oa&>{3FYr1`m;AS~9O1-vsrpHAycF|G)t@AcQ!D)MQax;Ml=l!{~b zBL4YvjkaBVRw@LkXSZHJ_lKQfw&O#(uer~OyYi8m$)X1wlZ?0t6@L+wdQ};hIC5s4 z5ugRP9__zMGANF1X0tu2B{K5s0ofSfLt<%!1o zU#!Hfc48I4(Gto12q=3Iu{M?6R|bjG%(5-jCw1+@sMHp>0GM04#C>}tg%;0Tjm9B zg}6UdK+IUSwwktQCnZdEpL@%i4uf<*+GU?|%4AZTrUwu?Vy2?%x=mC06G_*Yo>FJY z;=BvZ9Q2Ye{Cj02EzZvVa)u{USf5@{FhY|K2b!V$rh>d+K3{=C{dA3SRk|-iXSz9a zU5`uQ5d2T*+kPv_g6DlX$XT?jI@Rl}JN_n9hB?#@O_yfpC5YMIX;>ITRMh=7$4I#2 zKlxN6nokp1s5r3H*$VoRBjf`j6WRRy`D(pJ>f2jnyF91JCiFwlACXqUxvWhNPi62q z`Z&JixL0~3Ols|e0lPsoh|rO4u$8Jeh$(EIUniY{T;8w61R2kH#k~yDqmLV=l^=Ki z2yk}o7bol@YJnn(Q*j&h6AAb2Ix6Q6`=|RYVG`b-pMIbtX=kVOL z867&xzux96#B}mg zQl8Lj0+B0zZ`j1=N@>@Hem>D1KJNQ?z0SKY0&!-VEzimEXcrGG_-y1EE}KHhAz zQaH9|ie{1A!(pt%T3Wd3^^|f~xr&{j_wDbtmXBGkQ-3#53`(kYIjqLBxNZOHHCjHN zcUwuC=xHc?FPOP?9gs7`|6)XMyfOHfWJ{9hWA2O(#(ng%^L>YeKz=~Y0PGtphmV@c zE>!)2>s}N>A#Z-##-c9A0TaVTuypHMSuF~$7@oSzc83yd_8QDCNuySUNh7H!K>nS} zDd^>s`Q&|><^!TC7aW7IIPyCZt*{QAcf*!UhR>9H5|4*g(67YI*}*T}VQ`mfhXaO} z0^~WgHog`alS)pVSH7bZn2T6}%kyq&&gXjbADh`dxAy|LOsEE<=mHq~7K?{J57l&0y~79`%c2_AP1#h7(n|7M8FRHasD zq{>}Te~S!faS;CN!)>4BG~~VUdp8JHop1S%(A#oyN8amombO^@I9)Q=gsQrS{Ce(S z|Mx=nFnIsS5?Cg4eW8cxz)e)((M=kve7Qcw*f($ zwRsqL{iTw}8uz#kl(o9*h+AHoz&_cJha1i~aU!j!U`KLJ`DXxH}Gj0Pu@yKM$j~Kl>cM$iK0V zj^ZZVm_|T@Y&}m@tvq_6EquYS&?lM|Fd~B6XF1C6L0bv$DW4HW=7am;f0x{ekB(iJ z;Onfhy)vj~f_G@9b6uC5DWMd$!MEO`?=M$n(;0&CZ&;B52Zw)({b|!y|4r0(D@))u ziW(HABlOVn1KIi$;O@64*!Bar+#TlprvPWU7}wmIaO$_V#zIG~NBOfW3k!eB-o8bD zm8k)S@Np3s2!mOz zz5=3VzJLEokng&SMFv(=@wKF6{yuKHSM(!s!Y=dAbt&!PK+lf+KP7*Ot!t|TwGOLS zSU9i!C^i}$UvfR66gkfpS&-i1%34G^6s5p-TK^3(g%W&@SUAQhrajI+F#$fnTcfBB zg?e{1sSn#Ei*>rK2sfXLWM?|~9!kKGV(OfMS=Q=AwC02DEkFBuQf^!V#Z?i;y3&uDz{1DM}G2{$;N*|qEO z{{Z(u2)|DgJ6DHu8LznNwx*UA#D#%pLv2sdf2!8;cL68)#Mj?_X8dc&6(+b>e889y zvdaKw_}T*Q?Kd{#@JqyUzrEpZTz(TNQe({@|v6(`(qanFWWd-drhzvmX z4>#ZU+8a|Ti(CKfi?2_Zh&LU`iH}_dAr>Z~ECLf>e+M8-S@< ze?TiW$t)0}6kw5AY!;YM5~MiFq?8*1L2M2r>$kfec=|0oiNj^-u!gUkhhDhp&(x17 z3l7j*^y4Vsk4Th$L?GS%gsMgI;`lRvl96hc2um!GasoWcdV2h83l=U#0K(%BXMe~p z12mAo7{VO#%Tk=1Mf?OCCgHk>aU1fB2waZvYD<3NO!BAV&s<1s1VnxWi7O>gDe+yE z6eUM2*(c5}z)2K#E**czjA%`MM$GVMEh5b^{b3LY1|D2HPi4Hv`z z{o7mDtzXZ2S7iRZ?XE{}yZf;X8}L;>7L_ldahlyVf4WyLGJ^a!{`tXsA9`lR$~ry& z;v)ayCtmpO&#sowKr#>P1vDUlzQ-MPNTcW9hQ>yrL26@Dv&gJ{NRC%CuDkVtKiqu( zoOz4oT9;(tuV%dY_Vge9;#!s|_xP9IAg11-A$ZpSug|!PaF!om47u&D#~yt2IguaO zHn-3dQV3js>jT%_eE-~ei;)Du~6Ac#sE@$Lo!h@uG~ zML>EV;Rb8~v4hyuKqbG=PB+0a0011Bk{oBlGn-`#LG9-k5UM~@sjX4Eh|ErNc0 z>9x1ltXYQ|Ag>*q}=In+4`M-a{ZXiXoXcVKt96%$#@bmseB>4FAoG8ZhMaD1E zr3e!^wp3KPAa)3-%pyUNpTK9<;wKVwiBKH^`cH%*iHXc{GWmf8z&HGZf|~xw7y%A4 zWC9>Oydp2CO$bEU1rayuw&(Gi?K9#uX$IPifV3Pl9B=lMZIJ+cgoQxpNy7xu7#E$K z%7<+kafee(qq-E0<|&25S4By3i84!*pAa(qMaN8kZ2SU&$Is|~VBh39XR#s0{WQzM z)TchOX3{d;fYqjJo3=l<9oO~D!O0WauE}k-F%K4e@bY^T*6R{x|2upB!W$5^y=rhS zBmVHsf@LdKtzX|D(;W4VGHOR0vhSrAe@eRSvMX+w`oUZr=ZSAC;QfDGf#Zeq`g&Yk zsT2NvX%xY84}EHT^{VNHt3h1EF2naP*~#3##qDQ#St&lNftADhhK6O!R?&rL>_W3D zD=YApK~-fXK1P5ohBW!ZAjIwQ9^Jcv|NKSEa7mme|5bdfAgX#}%CuizbsGYB{eIwp zet0iq@zRytYRs1HUH?FsK7D%juIY}8TO9NN+(ILQ{77kI{>8T^`_}eCCsve|fdgH5 zqf=7)2t4ry1RiI=w+|LCU4i@RQk@=;di{ehf$lc2A5MH(U*E8N#p>2pY-)f6g6$zqY~M||26K<$BP z2%sQka6t)30bQe{urKupIV9((FW`B~ua7CY6i<`ifdK@8p8zTW4`+gwq_EF`oBrr| z2RNDjv$AX(`ZITJ>w$ft(t5d<=yEjGYfGW&k)F#9t0|cz2gYv?`d?X^J$bhZYBlMj z+KyFCkAk|rKI6}oAdoZrsk)tJF4FD*N(gMW{lw$(_|rp2c!wIo{0kZp3B5zT zNB6gL^A|3|ANLq_8V4Mej^Lto>9UnemaP;bk(!7%TmD2*yu|p$?e{tJ=<5mMoIpN` z-=KZi9GN?xzBI{BiySp3;18@;5c-zqz$lK2gDGNOvXW*T(`KR#w-g!%8$HoBWK80>$v>m6BGOD6F6|{c-D-?R5O&laSQ4V(p#HSA9)A z%?oXz5mP~7ws^8wVQtvECyuru8>q*QViW~Gbp1J?Z)ruTZ;X~k8*p^2G}FmWtt01Y zYP)2Ib}OA;hvzsL4V6V?iftMuB*DgPxY>UKGj_nZdBJI&18^5v!F#Dqv#|2G)1c>&GLDL`8A6x&(VnpMBv6DPV ztOto-P10gqt!FUdAx1u>BOvBkh=NZ=hyYF-sfZ6#ptz7RR}M-_LAub1PXnH(URT!t znEU}o!#_{2On+oZ?d0QkW*vXw5?F{~f|f)Uip@$9D+7~5r0i3XeR`F;u=?3%?avrU zOR^{OE2(`DncAv)TEd5ll0USjWcnJ?BeVahcY$P>i4VwpptJ1{3_evM)ljuA6lu5C zdZySp=U>4oC^(|xw%PFu@j6?7oT7TQG5v8m5dh)1^a3;o0n;A>At;If72xs#vp=Fp zHhx7TcJlS737MBijh)CQba)JI6XSQr8ag#iUpyyKC18F~ z^*=2zvzalqWHy;odu*2~|6$nA{rmE^2+51KdvngxbBZ_Nkgd=u*}LI1$qz&Re4#q5 zd#VjPsQmaO$)r*2S-U+9%CWk!n1RYWMwXVzI+6!gS3azFZ<|IWB zOew47gsCdpt~`-K)Z}-@T^S?8zi=@c6ecfV`m@dI&$QM2>O@|Lg=jQ&a5u`0D>@on zq9CO}ZDnR?j}m%5VtcSZ2Vz?!mY<^J)UZ}T%9JQImm|EgsmBKg*e1Z|;>A7F|E;h8 z_*}wIued?_gYZW;xi03eEa)iR+i5zCbZQ9qbwm$tK>@Z0{U>=dnQL!-AZTr(z}t`f zGALb7Zio1r{5)=wb+zltf*-S=Ti<@_PB8opf4$BD8WOef8-_`=9!Qx(^VSda>V#J; z+2Hrw{Io2yviKTwPshKyW*Vt(og+Wur*)p@lSYGC3Qqhq8muO$Azthd5Xr?i`P;@J z4O_#%ZQMxN!b-~YpG}V*YukwaELz{PqM@}neVme?BZp1V9T!E!jucoEXI8R@_AQ%P z*QncwoNdtl^sUMa=~2SwQ@oURsSO1JMWU)0f(!7`2{q|PnHomZ|7}(OrTfrvY5i#S z-;HlzrZkRq&7OTH6XqgC~c6-BMk6~SX#(_8c*WGH(o%lY4YkK zW(5GrFcUvb9+O{sL1U{iG5k$`v~KJ?>Y!okzwgvln)GplEq+L#aZD}biAQZ=hc5m@*f~l+V+4# z(x+xW&3>Bx7ur2-RV25NMl{Ft$MmOAH%x!r=-6h~4~2bXe^wl zykz)j>C6Flh0kkH9{yN`hlRLoEh?+vIZzT7=dx-=7_7ve5>HAeKi>tmAy7aQ@nN>Y zMAu8qdGmq;CO;t*;b!;)4%Qg)86QE(VP*OQAa?BWJF-{l!F@`dDI*O)gV_%OF+OCe z*pO)xL-e2{zqYFMx_9b*OwzVR`}5UNm1AnrUKU%)9~wfU!%4oUa#58&t*+&UcN-m6 zMeH;4;Nm;B{@DCQO5xRM+t+{6OBOg;hnV~}enlD)(yxuO_cvcR8fKfz?5BP1k zbg*cBN?dq(OTU}#O#VU$4gW$+(o#%+3X$0{^rv@K$wzj_O@8mh+K}(e4;dl+rOb%F zSf+b<=8-u~OEwfYdE1EoK#X4>)}_4Gk z^Ox6;ZAAaQI34~q`3oWAVP51$6e+%~-(tK>e_}i+X8KczNm`2OPa!g&!ZQzB7P7_@+8XkbN|}XOGZ^iMFMdMMA-!qkTDE@ zp4K>dIUS`bQgMKYmDm)83u1?Wq^J6Ml1S8Z2LHT2uEH@3RfIrcEy((?@E1`{e#5^| zEt3?}e+*H!BjP8>a0rQ}KY5r$QPUq%A3{O=2pmo{{mH{5ikkkI{`loq7(@(7>X_OD z{VDC*XBfsISD>C^P!&f^HzUHPz^xjkI$@Tib6lcr)B^%G9K=cjCVv}ng{%Z;wk!eX zB;D{Y_ON^aa5Do5&W69odFzkgx~AM`M^vQTsSW7X zZPxx=JhJMLe*DP^CpMacrb%-UF5a#<-D&;hlj@gjX!DyLjxhDd^k1M6g@GRe+pPW+ z?aLr`2$=jufyzq+TyXG1KnOzA86Ek_E3BfR7qb}xhQER*HJIS+J3>g4bdF0jl?(Tv z;ok;6X;}%*hX04R{y{H4T*6pzHQ0f$by2BrwCuNZ&mgmbDEbHe|w@6rofX1THoLkp^Rw5eouMm3pg@=?A&?hP zfWC{DtiXXxz;pk7M||YeBde+^<}X}YY*wWTb};;h?y>7$!v+I~Ze6=9TfW*u7-6N? zDnkbEI&#F|K?8|?`N}}=%eD2J%2QAc!1k=^cE-s^?6=SGrOQ^WTfZTXWgtUf^YtIi z*?V|l^YiMIXnW-^S~w(bn*5_i4B2zYZh*Zco2^^BE-(pf5kOm#u3S;rH6Nz@Vlac) zA)w{oSlHwzgaUL3Y`*?)40D}t_#6HXfuMw1KQEP5f{_YQ&c9u;cxoG zqr(kk8^16M&o6F1dyfilS@;-Td;8>t>+*5t{ z_UFic<-1j@^_`b2v7TCD{d9w#WU2j`7Z)|*s}pdhKZPjznad3^A9eb*MeNVP`;WT# zvmd7p$mG8NvuozeUo>pUZa=#8vxuI2+@ZhC+%;k1TS`NB*!lOOPnj5u<|EJGAb>C4$KY!tp@BH+4#rlpg6{kl7=1qn&ioiNMxBUdQwp6mSaXN`fxfPi!>M+!>O*0j24ZvydYJqTv~oe5Nn1(18^K z+G)il0gCbW!jO^D^|q&MduUAlw)@xcZyO)epSJPYF7)TQ1 zYbT-Z18(L;FN_?q2NJnEa(fNi{l!<_BE9rC2D}ndmj%}z{p5;5brK*wXiwSp&}=#V z5s~uO2?g?p)>FX4Qhe>PY4Yzqe0O4;%i>{^>MmX6p$1=3k=lJ$aTyBaM*|c5VV}6T zGE+`B5}LO8A9~CdL%AIqOFTd_9g)0q5iW9U0NAGh{k+a4Oj-|+W_Zc}v3^kB>zz5hlEM%O@6PiqM71{ zPV~L%e*2f|3yE7+Jr!&H$G(g!$UH_K)8H}`a`cy$bto8aX=&s z^D**~B2l9CQG~TiXp#_7!$9DWG&-gDYb zC`zM|k+j|+6^JRYIrvLifmV1E$Lnsrf5NM8;l-l2-~E8%T7oT)zp&?O*z1V?1o=@i zU=tdW;Fg3oU4D`?)WMRrq5m=pxQJL?DuB`=2&MaIUOLfvm(V1kO_v`$1S8i9-yBM3 z=K`CAzX<3#B@Cs>d9Ahevjd#f$?(_XLaUG{72%VFO#i(VvhmA$0qywE^oKSYyyLO# zCLyFrG*5A9>()Oy?8~`d(sDT7R)B$a#X~@HJK`sx$ZiE+6A)h+O9iZ?Mpv)NFZdY# zz`^uK1}9w^+xX379-G&6@yK##K!U;0rVcofp<&o~L!rn8Im+iU_=M!qyOfPz#IIuz z$H2}7#8_=j{JWNAzqEHH$3;{+I8#aEnJdo$JatoDdc&4 zPu}_g)z$W{>C?MsF4u~qB>^=T&zB#vB1t4Jw4W&Q`diaN8xRouMeNejl7YMQ?bfw= z?t;Z@*5)4-sY^kaUNzlod-r5i>(CZPRVqjAZb9YZx?~d0b*=7#BN!VRo9512+}uK6 z-i6chBgRp9k|P0*xh7HpVA7{|4_rkHEWRg&Eb<|CpW2>%d-q&lU%z1C(#9rNDHA35 z7mnYE3r8x-OLrUCUv;JH2ra+HAIESG9MEscvX#(SWbfMv|HKToD)1Pfff68b9E$dM? zpNhFj)H@hHk{S`oK{z3yoY)DIJOih9XEnKIlqiR;AYBH3+8OcdXjxxE%i_W&KY&R3 z#qehYO#hP&L^Ws=!PxbOykS` za7@+sMeCyCK8p)b*{!KRU*5aCCq1s=>LkhAl%JZV+wP*AoXI{k2W#T3DnAQ@+LNx? z*7Qf9@qQv_3}cesPWgNHtoh8RPCxA6eM++QI0_J0wtUs?cR!loPgR0hZuf!xfA#Zk zhyefh`@c+jbE<@^swzK!!I}H*Gon||?nuD}Qe#uo?755X`TH}kPnjC7SAY4FuMQnN z5Cjunf9KBopZwydKeF%0VYo<>N@vYk@RxfZfAekJqIYT^wP8(?CA>{pZLfL zgLm7dveIq;t*%=;ZN}W2|N8K<6{{dN=vfB-rNWusJ-dJAQ)e80@V-H8JlS#ggHOqE z6-+{cI-DuVe*E-f&=5Ye(6w>NvK5a!`Ql?wz35Bjx1G`=4a111J zX3kmgSDVywh<^X1_k@7fNqwX~lV&Pmh1~@uy#szWdb2 zPx{#D$6zBVaJc%4?|{d&nR9>k-#3cbK=Nz{3n>pABwxI2#iInv1a7*w1Dt!@kq3P8 zV<+PJS;TJ5wf^hH*Z+F&6P)SLQ)2tF%qXGBp({!4Raa&fE?(}tk{}0@{l|>>!8bn5 zr7~AtcPHQ)z4uVG{$CR&|Nf8nNP2;CW(3B2NICJ*99n-~dHJ_@=?9!M=!^gP-R)eE zdi1PQj=JdFQ-#mvSKjjWdox&as`}dh{eg_8@h?rj_U8K_PGy(M&!2xLUOx%?)6~?& zy8X||Q)DEHmXHPx-hG$zK6cW9WA}EeY#fl$()tRIyM>FEv1(Jb2aMS(Y2fvDo^}E? z5TEUMZo=z-_~X4GD8!$NkxS~5j$l7Z7Yx~Vj0jXBvGp_KknagPr2hgt@(q8}A5j87 zcKm3|R~}XtPBi^t>qM}XR@d$+5rU8Gjzfr}0%0&@zzr$vhX5tz{V|OcjeL+%9sH+h zkWv^8DUutasx;+zkq|Wbi(m^}f&*TLzv+*SUpic7X!o+QwSJq`t=j$gz=)B70zc1% zF+}NNnzDnsmAtmBh0cB02JC0g%91}GU0o$tH|>+3nnLX@V*KZ>&Ha2*gNMT#TeO>+ z?xW7^NUFnxc;-7~39NZEMWZO3k{*&N5M)Sg0`u|ph9V)5FMQyhFMY&^GKDe(@}lYK zp#};h@>i6VUG{@7jv2KlUpXU-Ty=HT;Rlbwz0KaeYNW;|{`t!4HEa9#t3B!1LrSnA zQBv~Sn^Wh`UxM5L{rmjK55I&D6LhJnlH%}jQ@wYu9!DKEwxOYE>Ibu#N%o_s9bMC- z8;Zl7b?Q<0V5F)vi|P(PXy3_Grmb96CzUd{?596@8m?FIsmQ<_x#i`h!-fnRwbu|_ z;nUq?R=EMY^!X38Wi%wglQn23;a^%>T1%4b*U;Gb{s(g?oe?E5mS&;v)$m^Gi@ zC82btr#+E1YuAl?=4AxPWiwyA=%Z(!d90U>lQNmE)m8iNyBBWbzcY2F7n$jki0>>^ zD)nYFCmwS!U`H?DIRaeOONO)vTOmjn7VxX9y9^z?>p^2jPI~Ko?9K=!(mkrxo|0IX zmzOzIC%)C(EcfJfbJmah=Sw1kK#i>>$RBv3XVHn+?C4WlGx@D)*aA5CfYBrN)No@ihTV8bYj53jyB@h#8E^Wf9zj@_}q`4 z0DTkwt5(;&MK8h-lfHd=fAXUzNcwAUyoU`40{n+sw?P$jdu&5}N8@8L8|cvnq-XKHPi`P}%E%EzxPc@cHxTE5zBv| z5iLTa_z*p!@Iyd2b446Ph)A515NA+TCO-pW_?!OFaA@19u|o>8bB*77K4^4iDj6&a zE3yF8A{TDob&dv<=vw@9$gLvr?8{1;U3$Nkf(Vlu!(Z`)B3deU*}~ zJpXvj*~#=r!KKDgVs?Nv(#3~{5WeIGS_fo^=f3)dbB63bz$M2&_?Ts!AhDz@3zNg` z=z+WR;k?|s_4T-5_K)k+Mc}`kcN(smh|+a573Xgy@LM)cI45>_dHD~&aS<-YSYnd= z>@$wW_Cyc^JaF4Qv}gFx-G2DZPvfzF882s_aSS*MF^))NN$|V_IhN$~zJ262cm@T} z``8J5kqLQlnV-Xkh-aID0N?o1C!HQAV&`y&8M`OK&=XT}F#pz9&chX>M;;`UPM-L= z3|evO35SNV_1N}kgpv~l{PWH}@q}XzqM!^G3Z*$$gv)Z6vBm2czC|dQoilIo2ean0 z?4Zf+zFU9f5=Tw~P##s}qV+%(+X|?gI%4?H-GBHEMwQre6;x6ObDzKPEQVTgb7+o` zh=2T12jWQqgwYGlxYXuKB?}c{3j??MiCr%D(bJA%SQ0*sjpztLA&YzRCmeMkMZqDL z!BDXJo|J}POb~_T#l6!Wk`MpBN^!I8}u{{)rEH%@Vp6g&6SmwjvT%Rr<2(Gj2y-V zL6+Tn*zQ6P|BWfrP?U6=6pWnL?R7!}ng8umr}ym(-6jUu>-q6_KIiv7C~fr`P&!xJ zyT?zy`#Fd~WnI5G`F#UHndJsTDqBCZ7E#yv4oO0c8+Em<{~4fUm=h?ZJf=T1%Dm=? z77;QM;tbOtw(X2c0BDQp56)-+FXju+2VFpFNjigM@O>j;@6$>FgIo$oKxEZ^N?Ff? ze$iCo#HA!4EF=Cbrz@p|RzZ{Bca!1IiemcXMlp>sH+1w*gwIYfeqUPJjNA2tx?=(7 z?TI!ZE>*B1OH_==Fa9!_k-bX4G`#YLsSOTYUBV>^tIoxL@0#qF_Nw3tbz`(NaXMLA zwy-HAch3k{eOp8Ro!0)3-DmhwhmH*+@}bAa|8x9n8|oVlJ!tf2&N~B7C8%K=IwFvx z58*NgjNO}iGlwV7|KnFTu3EhocSk>R>e1nu@FNe|=fOwhF0Lw@$vpSs>yJM5BHne! zjqxvj`lEcCI)f|u^Upr%hT9(i>F_;wzwi@yu$)yTllj}JB z(C;&H=qEmM{9X4y2~qGIxKvO~5|aPOL-u{}QIZ6Y-!moY-~A6i_t?`D)~#DVdgPv8 z{p{JeVn-rg)4;Xs;-xEuq4r;Y>-`5Fc@|gl`;Of6v*(|QyZ4~SmD}geKl6&KZuP=? z_V~?av1N17xu;1(@VNU`*WZO3{wQ_Gfuq0mrB9X+&Y7?L+sAOeGZOIX6pAqR}U@wNwXkw1LsAV$^kAyl88xURl_ z?5JU1`=X$Vi(W?cfu|rO(@8}LQUDK4;IR=YkMk6+y8g}&X3oRIFK3;4B;F&C^!@f7 zKK>=V+wv6tu#Ix=M^2FRZ~x@VB}-RIc-YWE1+d_PJVUU2`IGWo##cV`u?ep+ESb!g zKK(I%W`RmCShVaf_dNdg)EPr}-xbdV;DLWHYVyk~!>~RS*-;ctK%>Z~u6+2}IrDW_ z(q6#lE;x(2(m6qZ{1;w%bKEm8EnT(>N=PtLp9(7|=TlEV_v*S08^(?v=9QCS{OIv_ zJ@Dk4Z%_B!GB`^W&kG=mXFdEoJH&j?JqC8|QUwk$M|%$+%=Fm}_4U(c&cjn62aJ(X zhW9&`@hDq|$5x_IcJRIr;#rAY=BuAOr=QqKM_=PpSV+QsG zSQCm_E7BToO}uj8mkksA5MVm+`O$t=1G|>^5+8#7fhoW8ur6gRpcmL_vyZkVKeZ#q z;nig=@2|!ULpaBj!`>r3@g~{qzgQIDgg^o6oEpFqQt>4*BPS*ho|(+EFHHK&J>%vt zT8cv%AA9Ph+xUzI$tv!HQzqA~x=T%uu2RvQd5c!AT8qxa0d@Zv_xyX)W-nT@0)KdU zf%H~X*Z1j^cV}F8%Y8FvEm*sD!}Bk{@%x+Zp)tX3&mjXPaO@HLae!og^4XW~edy_x ztLyL}z{JIB1^S~8-w#~7b%P}Gl0WX}FT#EY5P>8QKM_m9wNzDB;tCl@QM@|& zy}KWLa&_IBrskG6ro8{;vlFFi_o zGr&?xT8an%OH26C8-f^*pZxmwe+51EW!`>o=CA*7N3zxm-zVUDGy+0e*?e!ZP7)Hks%85fjpqvm4uT|cxeL9fOP9xvk?*xA_gA z9zD9^l@mdJ#_R>ZyWwtm0|@6+LRSMy^so}io~)k{f!h4;hQGbb4ctKe$WDeW{1A}z zfF%OJNqhk;flU)39R9#Uvb>I<2$lK}<}W8X#Bnlsk*Jv>WDq+9LY5~s`3Wkz3KPo{ zjB}zkraz|tVcf__0wAn5qB)(RKX^;o4^h+|9MlsdmX(zAow`CrDTgePFD2m+MKY&2 z8ofCphXKKN&IdF%GH^j42{4%o`r`bHvVmM@nEZmO;m-reDG95RRufiougN;Y^k*l~ zpF2Ki`u;wZr6poSboV2iVsS);f+B@LCflt%_q)ToeDaxfWGg=m{lSMe59v$qZ?J<9 z#o8qT2eCsS8VRoK?eEh@3`fX^GyBie6JsHUg+Sq`_=s%M{1slB0MNJI`H0pZoWpm@ zv4`N>3a-|q%Ot@Gux8zQTtUf0*LY7JZxB2){?+%V&1q_BcBlP`IygB|)-k&2&wr-_ z^S%79z4;zqP{%I9?t4J{wTclkn7&9tB4UeIDvDSQ6q*(BF-kj z-U`0%L0QZJtqAnP_89o~dmm6QFii5~62fiizb`y{?gAgo{OHopJ~VFp#7XaBCj}Sn zFTVVy$C*eXRPTM9vz-1+dh>lo71HJi)t)uW{bx9+B;;Zka3ICke(=AVC;AJ!82Drf zvgEcoJ$fK_f2gE#+-fPCz|zo&Q!NP=ak9c&9+^{`%!}T`aOu^X)BEgf-y@Jo0 z5CL<;3p;5m(CdR(WDo$YD-&R^R)ZhrF_y9FDF7Sb4CzoQm` zN*y<#^pd?RuYK1)bj_->b@XT7+R~rySH+TVy8OQF1j4F@+yir((DhqKey?NOlm0N; z@nUyV?dRY@?sNv+j;50W73c^~pEWORG`I^7>12^W!nq6{_zw@vAAQ(Z{NYXY=`-g| zdgHx+y)=3G^3}vv7tCaEy^W7(s=1HwpFL+G&ZGoy`a0u)zIeR6XN~&|0mQ=#9Zo!{ z9rW`S$wmVbK#7fpNRq?H;)Z#?BvO`rd@19&qYfB8WYCCVgK%~OU!JLK1R-{TxURm5WO*F zIw0bcCNp?W9-rR8--?xWxZ(f&%aifwzCS*Iu#&2=4dHHFEB%R3?b$urXn;&27RvKo zcYM?U&;R2g8c9DN!%v&soEDJSm#BMZbr0Og#kloK$nT#cg^ z2ZaSO_EnIyBH6gn zWg-kC{c-99V#8mc)VKwxizXWWX?msMifDG&@r#{lx8Kcs>GTC40zow%yREp-Aba8x zfUkja;bLhKnGh5u3yF#*s+YHOHzHemXt*IJf7CCC82*F--7?sSj+y=hRna;W61TVU zd*jr`GX}v1iri``p;r9dP+7NvP~BD{a(U#88F1;#_OHMfC|+LL92O{%9qSzZ=~=}PdYSs68j^=2|Se*b%4zc8Gx$2?Lz z)npuDxOV-9e6bZuIFL{HJ6>ouM9pNoSL;t^q*Rp2)YNp#Yol;0vi?z02|Mf>{NmR) z<5>oLIw4em123@KfK_g&;gg?^-0ipqc|z^O_Sl;{%iDe&qP# zOPWB$Jw-7sEy6z9J^GS}Di#L`mMlKRz=s`jmQxka2f1iIuGslh5#sM~E1X+BXLRM7 zwd)bn^RLz@XduzDwn!oRxJjr{!>AAwO886E=^l8g2LPV2bT2;IhrI?INr*?>Wzd5Ffs>9s zcKJNB1+S@Zl5e9M!TFt3j}KeSMGabm7zwC+Zi>zx`QqDS$UP zARPFKB>3t9ZnnpgWJQuY?tcQTe)`?dtM5n9$%6A1ELpp5Jq{pDc1$qzz?8r0Hy$0XoObh||fW6oSyvaX*G0Fz~ zxe^}jh4zyqa4yWhI4j|j@Biwc{YM>h_)0|#Nk{@Qh3IWcy)gT!abOh2WZIE4gRtLxUz zoIM}!EPwzXn+QsRS1RxZ1dc2@XurLYHD>g1oEAw;@HoRe(?dgATUxKZ`M$qD`W)#t zjj}!=3xSi4I~YUY&ikLBvn0Kn`1(6bm#?Cj##59no0-3038eD0OhxVD1Vt|jDbo6) zOyl(*kBBB&zgT;ROpmo!>*gM@}#K5_TCdD{%rG7^m6|SRE94Dh->QG33AkNcU0tRS?3fR@cc!1D-ao z>QYrtFL#Ktk;Mu0pj^Frtw;_ZW&iTG$M6S9aM^bHNk>GI-1o#DgLe(P3^)-GhvZ?S zK?W=i(n~fPP!&n$Yh>YYp*ZE8=_052T8vq9=-K_e1JIQz8+nSo#;h)VDm`5PLWUU>NpoXdc8ycdH53r{%uApVwNrfYSVkt2pocy$Vd zk&OmC&w}?y>gyXsVDWWxR;{XIF`PaG8L)}N>EzvZ*P}Qwfxk&fO7h=7`+CSq2C{}w z$sb@TEh)X_j)&2ZLi;J)kTsiLQE_hfLAzuo_y_~8bv43YpaZQ7B%+dMOptHq$&Ct(fA@!TV?_i{$l<=-B1U z@5C1Q1zQo=iPHOdO`yV* zEnG}~hLZ3Qzzl!WAJPR<3-WjB@%!NH#%Xo3BcK3r$S^{(6k}sWXwhOzfyX420w?L& z%w9c8?>(`)M}@OIoJqEei4u#Llp%^uv7i5aaF+{)mNOsbKp_z#L|I_O+AV)eF7w+d zjb=add#f|;r(76IFMamBGguwm{B_cChwkpL{6XNgh=ak)Weyy>*Hyo`6o0?H{L;OL z4TkXR)@{Ir?qye8kE?ef#FGUG@WdEOX1;LYS@>ulPo$(dxWW%#OvJ^%JjIQVJ4gYn zgD?EVDUiX@=W_TkXL!OKkIm!Uz5~bXb>-!dF0ee;4H?*A`e!8ed;EQoWR`Tq2| z634#DVF!+u=@35t@OsDRz%SvK`N?-aC%tg##phFA zE{7{~Ja2#FZ4U&}-3InYbMjE-1`@6)gKtuXGY;{z3%)9KN+x`+nPLuqk(~S_EAv@&CBD?alEbI2^xxy)uB`L&2euLr=74dQ#Gz3a zed1KnANcsg=UT(*A*7tE>ImOl4i7I#5o~qg#8ZUvZ4}->$>HEe$-;hte8LkS@_6f= z8Jw6saO}t{fAQ_BF8>aE+BM7N@ZQIZFTW9wG6H1U2Xj!7u)?zs73Hwf0ffw1ryTj4 zUy!C(6hFUEj<)*RCx1rCFhK)1b)UpjIf)++Ch z_SNv89omdvQ3x?UWSn4yl(8cms0is2sZUsn6CH|@AtNU2hk#3G0xn8S5MWjeC|ALC zf!0|sCXlKkdMPD7vB7_KCcnek@Mk46{lQS;(d3304;dMB@OQfL3%l~`H}RSP3>8#E z{18Aingx?+Tl7?FZ10kXPOk1(jh8Yy#eSBRX0JN5@=GIRmx1NnLh|1`t8vB}dY0bo zC(kn@+-p^J?b~w?LTHOO+WsnH&7`g!O05)ckKuNE1x+VxAB=_4)48d zNkF-1vD*Ow^P>*iuV+s>0swcIKYiY5#~p=_4tN*vr$Of&E=O@CkIzHkje2Zt;Gl;J z`V;~=hz;!}6GtP09W6a*{qX!NZ@8392A>Q#@X; z!M7C<76(+%IrG?>n(l%sWb>Vn`PYQkN&l#7@7z)=vk(V)vcrA#V~*IL&Kk&Oaq;|L z|NfQGiO3iHAqUR>^ei$5>^G8l@=8g=@(qGzR9RUW>ccq&-}%~w0t+4uz}6L5;Tt7< z?j~^p_&@miMf>eDyt1Mk+gsoL+J)%lP=!FmwK)6%5dkK?r^6E;mtJzgu%Ux0t19s_ zLVi~!_R|$0ELd{!wQ`<%!l7pqjNKsreq;9f=9fPia3JMuZABDr>a^K7ivjGy$)mLO zz&vrL!@|XcDqh}^`lipAyS~1j>w<2tW8LoSbi29>Hzzw_m%fQ^^V2lTSJj1$q&f_&Tf=h2HMh)>M%pB=d47d}j#3-@Mnb)k1LTi7$!N zeqT`1iGmX;z~GWkX~ab6Da+Gn{+b45C`zQ{LO{zY>`Z>bNi_?Ps_03|{v* zWgvDLP`ix&Hsnc@vq*YVYwmyFXh^IyxSRd&{P}D0TT@@0_|_2z?<=Y!$JEORb;=Ze z&>!qV0!^g39KNxK!|LVZ6u5@D<(kXznLylm#`Rmc0)P26{w`oB=>x|feGvY@ha2ir z@rg>jYA&_{AE&(Q{wL2r=VZyd;1j1H+|=9>I3xY-sWb0;=xN9dN#@R9GKjy&2}y4L z{bl&903;bcbWnIqT_ge9#-?Urf~(mZe)IouEFxa=$2S_iX#5MY@-_}Q`Jnwr;?%uW zR9sE)FABlkLvRls2oM|w0>NRhpus(mpb5_4794`Z;1Ggaump!-!QCaeTZUoe4&VQr zyViZX=ixrw)en27dw1>LySuBas;ho20CagLx4i`lO&E-H6xUH#xcD@P+Lj`w<4_3^ z+(1I#kfQMqaO0tUZL??Y8?Dt!W^p^&df4J2J6y!;HRXKG^6NOS1p5d}d)qYMhey#U z@b-FtGliTfj()NWDDH@1q2#l3ddP)Xzgc=&SsvQj*2YnQ`5fif>Wc*A%Xi~(2KX+r zLH1T=dQn)m$n7?8$EJozc?NVnBuPrA&p1EVuSbHD$@llO0M$!Z64~3k4kk>aNe29< z^jlk%n0%1!;%?Nrq0%>Bn9TSXq8LhMD)Ka*yU)i|#(*y?8MLNJwMHwAukN7V6$ z)_9pQ?OJ{b|3hvKY0X~{G>63K)dMribAvC}qw?=e(oojckmm=~K*!>q>;5m~7*M7& ztRa7j8%S7p@QfJi+AnCi&`8tAuA3v%(CJs8&ixF)eWh2F2q{Uqr9oNGxFarQ3CWp(&{^8ib>w9E$u$D^bL5X= z$ob6oU6e~@o)GCj*Q{~~>%ILbvix6;eeH+2=d&l}Ubz~qc@BVD_{>iTC3s#|hp;4^ znwLwAD}}T4Tbe^JohyjBlg3;(Wnf^U;%8*|PqcReGWRE=tX%@xJ>x5G8Kw}Gr!_N= zz!n$MZDZA$Zs(xgr6{nEEM>S$h%mW3KfKFuBf-D17x|oDbzYUc2As#fL^8(O^a>KSdqm8t*+r#u9Xxa-k0?#$APzAg@s{5V)8sF(~7od7#wKvn_WF^uU^F zsSAIK4m?Q6=wI`Hu0R!8txzw>4rfN#$C*}3Y9-@?R|j``6Teuw_)S}`VmD9@tlw{m zNnRb8MS|k|87pzExB$$*`&CMA-Vp~LsXMr#Rl4m-r)^r~E2{VibQP1h=v|bX^aR66 z-C)KJ?1U|fY_PAuH*N1H!YVdji;$`qTd_(fzd`@p+kdI->x3mt{<&ZVQp{Y`%c=kL zNkZ4KMKFI7O9d89AJ`oiwP9HXsG<$W!SV^7NwOVZqK9)=*R1J9ibAH*u)Q%C@qbql zA8@OMtz)t0QRJ38Y3J%L0e6Z=@jbU_AS$1>DTH63fuE3Sr`Q%bfXOw2l!h<7{kG?n zP(VX*hlv(7u`P8Z{kG3^UCQaZe2h1{m!}J5c0U;x6pWFhg~WsdKPx*?IpThoo*BT@ z%@*cWYjhyuVbakPvT_=2x8G*L%FMWCCzEaSf?bBhxF*(gB7PtJg7tq?)OhV-JY>ec ziEG!aj`$j%B@kCz>q@0{^}dF}$W~9oSpU3PFNph$pv_`j_#*kJAIB_O*Ak~0*(#oU zv!|ci68*(g2fc|ZLqrUI-{W0AE*m$&*Nq9kXx+cGQkaqUh0MOoF;OU#4q^N8A+orz&!^O~0ciO? zLfcT&^CA8XX_)(m`N_%XFh;j`rTRon+^t5v5-^B|yb)kC4t^;BJp00V&Zc@Gl}D0D z2Vzrar7zP0#|tQK_TV%{&ogEbempFW_eN>p+(S&y#W$sJeGHgMV9e3x&T{u&dlQ$4yiXiWu zwVUUF_!5_M^H)B^7MUCF=M=$)$CV`t?9y)%1z*&G{m!T`FVv9VYr6zt-h0Sy?q>+V;hMpZ*Y~0=O9q=2uegGpcO!od2?p07imMh zXk~izLu@tTi!Oabba;DvGs;J{$gTUDc*cILN1fgSm&3zwp_=aBnELNGGq~yRh?Ac7 zK8fTt5aQFt2@J6&%g0=@`Ddn+TvB+VUaQrH3f>?A_{2Fbpq!0>xi_L7rJo^`o0t9v zA-{O3`Qf!F9FzFW(3${SRNR27@o&^gfC6pxiD}AUKliU2IdYW5E_zl0R^(}^vMXC3 z+9}cN%E7ygrro@6NC2u2l&`~pNkJz!#icuw&OczcOx6v_U%Kpg4HLn^ItGeAoYLTb zS%NkEdcQmVjv3EeUVr6SQ1N3n!QFeUngq=^CX`>RfsiQRJX|87nwE=#MV$A?HGZiTJdB;XlrKt!w!#)> z{ZV_=<~j*053LE~Y{*lpjZ$IQ!IjJM->x>S*f6^CR?EQ|%r6<$?|ubPrJu+%^3XlK ze8;aPQgOP3vAy*NusZ|M`5XxxKT4WQ#d#0E6dC6L4k1ccVWKuI(#Y0!%P=Lrw6jlKQD06XhU-@ zAW7wv%EsTEPY$dA@O5g^0lTA&7jTZxp|6d})d@;?^m+&{Fe^#l2L@tj4=`fl|NVL! zm^1L#5p4bneJ=93Ux1w3Bt%&VSL}Dn_?cvZz#r!BwwzDS^G;LvLjoR`LHKvHYU*l7 zbhH=zKq7+{_Ad-Y`LEUrMHi=wPO0X9WR~|)Kf62DGmUAaA04TC2t$0`Q+N^Ir}P&m z@NLEj%0F-*O3Up7NwcBRCrfxtte^k0%S9D!tqTDj!0@nt-D-bynatIW`&Yq{Nq5~h z@@DLyg_=1?OLTBUG$UKD+{LqGQuAM2(S#n3lnILHd{)KU8Ef?S5nT{9%<=q6?`I)3 z3}2mOv%5al)D$rA;h}S}1Xx!PRN#C4bT@kr=%KXPzU7P5n^E+kMMSh`JV_ta{I>rm z0o3xZE;Yo6u12QYi8dY-Uc;)}Ba$-f#s6MEz0M^5KG+ zPpvX`ui~FdXts(#?@FAPirT#*@tyN|Bs812WMa9Lcrukzvh$yZX)U0tYb|_O5RsJ8 zjDEcLzuHFEvxg>Hn#D8TqW2(?mLva%H0Lz&Di+l+wQ^qUDV})y2Rk|skYCrO0F%gz zcZpOK=!E42$0JE*W+^iMi49-=4U&gI7na9m=^1I4>Se1OI@waW< zigsa>TI%q0?$f6xD_`rViai=~GL@a$nVD!2pyo#YjR=ibJF#=V~byOV~yPf=~sdN(=)b{hvb8*N#L#|t?m9HDU|Jt$jv)ikWZ~)V+S$gpy`auYh?qe`iG`*LHDe-DtrggrC}p zf0S31p)4fWxr6B3X9f1Qj&+&SzU{$U%0_-KQGVhoDNYhv49wi=Ht`Lq7xn-rrX|_(guT1*@XtsA0l_~ou{3Qg{sLfru zo1;qXAPzVA8%u&e7Iv0AVR}z=m9wXS>i<0DWy>=;^XbAl*lF>)U5j73o2l~gj27%J z(5i>=gsQiOvr|t>#LTKj`((qyo3;d?Po0wvNw(y7B5L``=A#c5TdDx@ZDR`-RiIr; zkQ4AO#tavN35_@X9jWQ?gKChbGwPOKri#h@dyQO=UA%=`-Ie*@+Df3y% zTuoH&OL$Cm+#v}co?P9%`iX`MFU(POA>}4FT#tO&<)L`OQNZq~qWqko%Vky7K zr{TLvM?VHFUend^$N6@}u%PvX^8!vb8TTQiJ=uQW}>q}yU2Zk2 zHK2zz8t+xH7tzVj6__2_k_=jecNeI?o0N#qh@hJF*vRP8t>R0U|5Y)vYpeE_o!0G;pSXky0-jndi#Q374<3eO8d6VY-g2-M1C}3^u6{LjYR45aXgZM(s-uUF3F8PdK(Y{_@O_~7y zd%wKGQCdV~*E|t}D7^mG)RtE_KV?R;xDVx@g-Xw`{Ir?rt$NFAK3*RG6Qc32;QK&Y z`3gIIa`>=zc)CxOUs6MaUGkxy;?29hq!$s6fm$Lb)E8U&U{e*g)&4{@U8SM=B&N3^ z!vd|eh%&%?qu=6WrFEA%Jt24&K|y^p4qm4m`>&sHi(g_@ZgR%R0Z9i0aDF>vP$yM@ zvV*yjSap;I0Pc!v5(W*PI+nCwamj#W{a=dP6n51u)&-s8S8Av{X~~#-tCRO~;35xB z*R>Wa6(5vnh3x@sqbzQe&$yqFHzrAJ@wbj(5zHG-^Z)EWuGQ`C2+t>7JoSLJYF-td`rMve+=;h zTWR-HLq-0T4vrM(?1`|=r5m{ z{BizMKu;O{G~vvErXuNY;J>kILcwhePG7|VWM+P<4$pmgsO}gU1?H3W) zRM9nh-MgF+`Z_dr`L2S*Cp7Z8q5O7O8Lw?16uV@Tk${!(0txcP$Xf>`Gv`3{y;gL) zZx|u@irs?pbdw^t^=F-;k&bFM3PBE7x<`SZt!5TDXH#!hpM3ms%tqH09vN?;>pHK7 z*FE9MU15YUQj>w2;2 zFox?ox*(Rf@1kT~56|qs6L|d6QpSeLdmIws9#Oxc;IDog)x7@#GaKDNo}+uIjZ!u( z=OD5D_|Lf=<%s>2l*SElQSbakhUUxZw{rRgE05z^ZWFct`tL;N%GLjhj&dD=si3GQ zy~J@hoJ}S5C4@_ITZG9>D^uMJwPN3}`tkvOy}Z2Nr!)EXvJ5#D8s_o@=ou{0jwbjp z99#a9rAh;2q~pU*_9!|_l)78K`yD0>FDw<9ptJ78qMQMXx)tyNK#!wOj^+fKJlKCH zj2toK{C#n$oj#!7RJh5=8cl{(MTtbOdTz@C}KpMHX9BLeAm)Se#5mbYc)(lmd3^|M!o9!_K! z%UmkKWkv87Z^NP7=Q*jL#z_YAGt;h@^`+d?P9h+)P`k`77SBP7eov#k=#mL9$#SQ@hv_hNdH*#8+v`xQt24Yut#ZVwiwlr2d)RE{~ z7A@ZVqpP0+Zpi{K--QWrCco94R~yuQfZts#@n!c<^M7}RFACr48U84saZ^v{&P2I!poHc0z{Pnsr_*?t#CZ%t%61Leu%E&415I}U+}gX!g+sN7t*53>0iA*!Isk(c{5V z`LrqWn*6Kn9wE<&VPuYQUer7Lw{jFdBUniguKs}7+3(anl}}QVzXXGaNw}_Zw}9C= z4k(8mG<+uJwE`VIK~e6dwmf;zMb9XXgBXLhX@l|)@{P08bMLLl)gkbb;HOXDy=Y#c z!Fk^&)*W^k`p+O$#9R=~w-{q#)fq*`zD4n`T=;p9+>I^=UQy9i|TbeL>yKq8)mag=np33v-NG0PnrK z3t7_a^GiE3Yy9R<+orxE8u;Pp2GlCYq9<#%3ANyRVZJ@}DTF zd+~@ch;Rz=fHeSpiIlWbL^gyE6TdC8ij-^cI$~gaAJG`} z@|T?xl%$|!R0H_|!7my*%v@Gus*i%!n_(&?wl>_%L0F;dP~c}=qFCkAXq z(=fqf*L?fSFuQBR{EwN4I)?SZQ{Ynv5p7^;$7Jv#yW&ih5Y;8rRL4oSUG(Re!7dq7 zCbe$`3lO{7CcB*Y*snQP-i*Mfa!CQNo7Bht@b$g*(-keFDY3 z9~0>ld!#W5>oZ-#*nFI@E7I_otl=EWRKi9;r&sJOzt9|`#1m1h$vk?b%ef4&+|UXt!W$C;fBumktaGOt1*9yuS8IX7%^iwKqye&7$1$4YZ>Z8#RaBP z9W}BLmX(W{h`6(?pZ66n`r5C<-WDX-Q)79wV`)UUP-^y0Ma;k2|AlHMLeg= z1u-4lpn05_JGH67nuveaSujS4iyt2hRUp;TDhO@beXjhiH*w@?PEvGuG-QaZBSm&k zoB*WE3D9BJAL?|J^N7zTw^9vfLb5!E8GGP26zdD*3QKBV#qXJ|1YWt42Q{Y(wNj6m9erW#6nUb9V zujv*25uQ)MueA3Trt8ochhqz)ZGa6;a+^w(zi7}J7o|}vFP7uH>P;=*Gc4(iwU?1E z_C!+CrkxdWwL`ZAe%DQ~WfWK|ANaWZETTim+bQO0pyGF8kAjz&TBwB7a!CsMA* zJ_+(9<8ZaoFGT3x*Z3HVvi8PC5x(Tyk4!*C^?Z4qWQ{449DI{2RY9=lFD z1jT(h1a4)xr z!VaKj$H@O=R#dVot9CbS=Gv4@FH^}N7el~K?VtaH@cV%^_M1+T6N`xKy|7<@PMyl%JTiCYJ4^F8sUCvs>Xs71#cCR0{Yu zNvj5P8m5PJtJ}Y9b)_e;E71pT;sWZRBCDRt;L<{(wKi{xBtUna^-2zweB!q4 z9Qrw;4gcxt?!REHMTPE*e~Y`wY{V{GuRR(*hV8T5ro7uccbMo8dqi{V-*w}g=L739 zYPd{G7HHA7-K*DGFPKI7`DIAF8@f)|nSTii2==BL?){Q1L{xfb8;BLAQQiz(FMi}3 zF>^sdk8xK7;%36*&~(XAUL=tO*v)w8GvL>0>;pIDq*(=`ES4w)y_7>>PUa$%pD3Se z?F5L4qa3<~7ec(mBv~9{Senx;SSo`n4xzjVqWfZ&H0&WksX@C#-Fl<0aSwc=LFxPM zT*<*=(kp(qb0>lDcmMXqrH4bI}lhhg&s(8YNALH@W zS;~8_Hg1AuMn_>Wi8^FdQ!e8{JeDoGlNTFma^?CqkaD!fuPH>xYD_P+SCJrmK~wIf z|3^`yv2T%;UZq~gxsm+Lho+V`ue4tuKIVW|bbKY%y6(wa#tVzDP>;=^tIpoS`C_P8{KCJvlNLz;!OOgku zd?=-Q(%tg-WLf=L)sX+nw8*9EPAjAtN=c|b=@$IgzSMqV7pceF72f1g9z2%)ACpp* z1571l*y$*AlL}%IhK#={Xb|z#KsM|=WY90wdg)K3q_niy!guiRKiTisIOo+>-7ZD& z0|nk28z8q_UhERsx&Kd9^#6Tf#zJ3lR6!q1A$rVOoW}LyA5A!XD^&Shs!Dlyvt|i> zZt2%s4w(^8Gc_lSCl5G8{B6yeS$0Hi9Jg6Z$0qhw!#_JD|Lu*W574=ITB&)gNe@IY8-7ORcXJvkzB&NfZYATzC{dcenBHo?ks5@q(Q)WT`BpYC5Pfcak=j*E z`a{(fvI6;_Coh`fgc_NRWK>SVD=0y@ToY?ar3|$$PAKV@jJu$u7_canEnC*3Vo0Zx zEZVs84ZsjkWcA&g{kS~(R{s5mmX1@6R1_{-(YroCXc%i504u44GZR5+X#>?mQ}qiy z1frt?+Wuc;_7ax@Aoz64Y+6q)Jad2l+Qp`oxFnOsf*h0FG`VuA1ngqjLRdBzIc*m) z3w5`XMs>>Y=X8Pr=&xO@waAF5UhKMFQ5Pr)67+|5LP9&Y&XdW~G>`ag7FRdR z&!H^Vr&Bwlj|?-X%aL+7p3D?*;TOaC&S&>`9ljbptLe5|<1I=kFnvfdScJywsA#C= zf6##l67d2VN+aN2qVJu&I)Oh&Ocu@7j}E(kG2JN-wi76Kp9_7awrno0@WH}&T`^51`Q_8;+4=;7xSEdNk;=op#f>VHHU*p{~ler6J;6tm2pQjXy9`e z!iO&ZomUoOk~FmOK0`bDowRxE%G1Xd8qk_{EkVcELNprb+}o=+T;TQ#iB`R*?;c{} zu`T%{voSgk3VD6~qPy{usV{We`S@4Y60wErhkZqB80~qSc5T24PmOhrnG>i9(AXHi#yn0@IuIA?x(jO?YpI24Q#=r%!pXtCv(0 zj6}|ZQnot-;rk_JTi+>6P|MI;P(RXSui(X-oql&+pF#};>(7$SI4G}{;bJ9WpM{c~ z`}iM=H0p9)H(JD&YN<-23#=hK3xS405HYwu8}3k1A44>z_h%kxK1%=K-DW+5r~6b3 zggIR)H~}Bv3O35F^qGi@21TH9OHkvjX~18a4jCAE>L-p;2#pTxe{WWXxvqz;PaX&$DSTGQ@a;w?ti#yeCbVV}V9FVjW0U%CzN%@I$2P1cd$ zCrQK^7YA?O+v^yA>T$Vp zPQ&ku?6S0m+U$r;Kf;=m+9L_#%hX;zINj-C$3Ch6a%jY&@&yIaOTgDGb6Ido$D(>0 zXx|R*Z}H;nO4$HS9>cN*OPhldW_cd0OPhs6rlVV_!1ZYwXs0Fc$W9ty{^E ziG^(%X-KXX`ahNYJ?@`>MoL0`v9l#wREz&kov3z&*QN^@DCQLpYX-f96U+5kaL3Mz zeWm|~ibp&ImXNBU#Jbh7+PGi%*>LZROvECzXU_ugj2z?r-ONe(djAx(VBz9FaA^2y z*@;6|+}lMD8OLv_Trq)jHd9+)rtp(no|afOR5Qz7_oo8-mG2m%<5=Ib$z5-0?1x}$ z1#r-dMyncK6-Rtyy-ZOzLt&oQruE$@Gw$g3g(_vmTZoE{2tU}|rLMD~I%dyQY(aN& z&q9>QZ_WB{W(mI$92Z=GnsF!PRjgw{R|6$xfU~BBu7L^1=ohbNR}1$Ier1=x!nyLz z{{(!Y=<)HyqHd(eX@TndQV&(Wx7aAH(U2$I5#_@| zmkKH5??-N||M;Mqvh(|c*4)zJl*A16PbWWzG6#@+$F$n=F%qPYM?LN6CdsWiJ|f@E^bVkzYXa3*EK~2R?lEaPk?vHN!FIaM(~QM@s9~%bC5)pXI}xl zPqFtU8HgUe+M*`{&04TX+vaZYxKcc*{f|DV2{{G*)Qeu-O0w*Dm`&GZx$%Juh`jht7(7<#K`@pgHS!2@upW(`Olj&ru?}pQ{$yzX@oWHe4z^Bt=Uv!x&Xf3u>hj{0h6h7@{18@_ufX5j}iFbhIF0)0$??IJ8@8RQo zTn`j;69R=nP)^#`aQVpm+Tz65R!+Hz1711z&>%-%w-8bLlzekE5#hfv5vS2fVhY{C*%Rw-Kjt}A-0<3=?V#73j zF%WH(NH~7uB@Pax^;JB#80Tdg%Rc%U}Q(c zLQ<0ii7`rij8&GtL4*s8D+E89bP*CgPt;R7Hnh}rW7h8nvlH%gQ}VQaLl8PX<%!EV zns7A-t;4sa3Gi^e}v|k=D;&Ba?od^0@O`1gcRLR zdU!|0EI?QC3a_o)GB=ffmE6gP3_f`(YEU+`NN?T86c=v{tB)|Yqbqj6WDHZbs)pMj zokZiKG$V5rtkeFPg8jiCh~?=%2Brf>(f&6Uz)={8FvOCPSXnF0h6+?*<#<1!R&X_7 z^1ulj0B?3zkmRcUF1Cz?>?|_dp<229??aEukM?&X zx`f2MkmQa&>+F0i>}vU%$x`c#2`ap60Lj-OD{>Xz0q2MnAV{6U^5`{;Rxto|>P+;$ zQ+seR#+mr|cdBZTW-$nx^?BWb_}Yv7Tcr$tm8~uMB`1CtDnK_0E&~h7N?pf?5Txy> zg58cxGi?JC>Avd@0S!!d z8}}zSYew^pW7o1fsqi}FABXbu!G?Uh{C_mylPI{itW=;_`Yqpe=LF=D*!9w3M}3t; z)_&k1=Yth8Z$fJ$-cDvz`dnCciR9rt;4}0i-?8zYFZgmeXc4sk|Jz5CK)pBM5cPoT z(8O03z9p9YohyO>EvDvM==y}$2b-5s4_W1RYTy#)Pcc1R?5w~1$tki(q}=c~V!Nzc z-JT-0K^Y5yj4=Rn9eq%z#>!L`!FFQLwIvZbs2@B)GEw~2uo+q3@0%p$9998MZ6ZUB zRB`C1{gdKlQN?4!*JV&PgymMum9tOo+?H{6R;^50;^+#9HOL$*6o0; zCY`axA*Ta6Gq7mS13G zw^3pd4y*2Yke!Z$hPj^}=LNUUq>nfMs)6T#loe7s_%XvC5Dg!Dj^nk<%L_{(9#}j! zIT5JyyMdBAu*ejKzt~?dwg!L@VL|RMfNkIq`W$$SY%t_lqg=pPDTi|o9IBD)wQ4g; zystH3^A<@|iW7AK3=pp+csH`~Wav^6^(;wZw?Nq|`+RR3hpw&$rav?P-I)r;qL3s* z#E@LaZu;IK-~)guOZ8}mo09aH(bTVBdKg4n$oxF?+)Jlo2)l%uewd!T@fx!f){`f7$32BKYQ{^m_Aakr28VQ{j-aLQjH%u=%g?O)Y z?!QA*|H??bG)3Vc>LYC=Vs+bVHKIOg9#i`WV_ho4rl2`ew#zeqGE>m+w}!{H{FGS? zQ%8uu-`kLZkHa!6qvd0M=h@N+Cl}I+DA2Nm7zyHi)9)9lC`PnFuTGZvaJ})0UsUG@ z70BN#9Q4sU8oWKhbvd#zw5knP4fr^p)bL`J-qb@6LIAqlcaNV&IP@vs4pmk#ECZw{ zh;dF)(X5g(BEb<@|m(p9)1Hv_Nnb)d{vQhj4 z-yu#X=P$zJfqqp~Av~uuh^JQtM4|hNG<`N2Z^g@mUj5)LRajkPF;l ztOo%T625pgYz_1s332l^`Cr@vfM5w<^pYj&0?k!=nIQuRC-F8?i}{kXtq@AHdf^e$ zK7QXI%=rp6Nh#uJyhuS$qEnPjK*Gwc`Okm>ysdY=Q6R~On!H(z{{J;|Aznv( zq`mLwaWv|1!AXR}X34EO?9xo5 zY-n|HH|q%aJf<*NTkf}Hm>zB=06T(VDF`BkR8-)qc_sbKoRKi%Cmw^bF|cMCE7;}Z zmuODqy+ag$gdvPej%3rXnZO4hSpp9+#4LaSEj4st!&rn(AxY4f)Ss~c%t6Wlrb`Ep zEgN&xb>GHEc<7P2?e-3J#-gr67xoGFr;uw@C&GpDF-=L!k$k`7T$MVy_B1P+Nk7wh zTk{Qb>#9uGIcYVmTo^S1g>juWngw@aD&y){W)b2$&nQ7p(x{6RWN+=W>^U=usVt-1 ztev&`!HVQQ+q2z-ElzmMi8IBILf? zpKylFTgXbdXYp4t=?b`{^m|eZsTQFGc0#>{>~g`N;L zn~S0Fzl#rflvHn@+fw9Enf$Z#e>uf>>8GBv^QQr*llN&dq3xZk=y&E6oSE}72z5NxQW45VA;6lrte6>-OckJ+9MTTVtP@c;A|Ex zU~$1|A~6UQb#I5>fpNUJZqa7#r+EbXF-KQjNakJG zU?iK1+yxlg9h&7mq~bbo4Hc>W&oTuVng5e6J4hI<`L`8o!th`}YNJ?7U7un4PmKlt}hmX}H? zMMXcDLc1R|;HO#5orq`&meTcXdW2-~b=nK+O}fa*nRhq7Rl@tQV|nESQC-2VO+lyk z1wfJVBo?LrIFt3USF)HJT{Rg+11LfWjO^rf;oO9NzK={^;uTT!*w4Wdx8{6}{kxF_ z6|qTjejgM6NT?nlWfbkn?`uU!#t+azpBLVpQt=1M4YxIi|} z)`Fsz-%txBSH45494H32Aqp}Nf~|mZ14lNcliSm*k9DhZ+5=A$t^?fS3m?)j zm7A3Kz9|k5KA-7i_gK@0McOiRz%ZhxNu5+EK_0?}p4u?(*%%5|q8JoI)$?>fd8Uvi zM=GKgJ2H?o0qh{j$IODiDTpI~Rd6Hx_-d7WwIjldGIyI=FhK;72*a&qqpZ927uN7|29 z*d!krV?nIw`r*i6Mc9(3^8J?p-?yZoeiY-B!l|1!!ghkcp&9nDN^(S|W>y59wH4n| z-(OWSqu$&El>lhv!e%W@u#0fVFvZGdG=tJ?ao7AJ#mNe!7PxL%J&4=W%$n zC=!RhW0I*Lo99s5$r^Wgpni#wOeAr`KD(4XcX%iYeODiV;({PI%uY!Mjk#DrCAbn0 z89ZOR=7-(;?Mbts~=wdu3$e+@S6tC%A`Skz#78&S`N1lCmZL; z(czA%{?|`@1biQwg2u@UZ%4m77QK8q1?@0MPisPg>FWbtW;?>iPaXFXFhXl_<;Y(~ z^@D*UObM`2;}3^L533Nha(bwsZiI%t#SHCBTp&; z(=`w3%G8%5vDdf*45Ha3JU?j$xpmLO1|jodAB@gUCT{650P&X2cYlNutHrcj2J$Jp z3ay5ELtauMt_MwF5?3X%?@2+dU$Sws$MT7Pyzg@``zg7=3nD_7(noSe{qdc?)J6@} z=bl$@Vea(LB4Q^bcUE=aS*?N|&R}gaGrz$kc?W@+>&vf0_{bPywjU^+gaUlagW%&! z_W}dPIcd{JJEp%9cn<8Hb-LPtI9hl`U8yF_0EX5b)}G$$KN`BoSEzD+#O~LCdF##U z+oVuf%@#4IvE!x#o(mIS7i$(*^Zr$z)b@WK*sI=dJKk4wF?2cm^I&lINa5Pxa`o2p zk*N26An(H?1>f&JXVA7sq8-=2zQP|VbiS4LhQE6xy5_qvf_S8`&XeB}RP#vm|FBss z`5Q}Za4k!OiDe58fhVo#CDF*ofCE7O-h_X3gj-YHzWtpXy9sUw(d*EQdQsUwy@Pa1 z@H%W3`OWpTDJ-iW@{6Rh^Mga&K}sZb3aC7Esqn<+OZQ&z6vhc@tC(Fk@s`bpfZPj* z8#F1*GfkR01&9$H6DN#Drkgm#Ul04_zv_6P|BtHY_l_oZ!IVu_?ng&QCnGJ(qb{+3 z99A{6MM&9XfDqAt0s#~K7h^^P=L>KNVq9c^_77P)o){%(NSy&zDu!RA)f$45;u;NU$inab@Dsi5@L@U3c%vBL|FkG3QN#T;lP);)R=&q!|8vguxFQ=Z93;tyVwZcQ8Kt(pbO^p!3R0$FzQ*ZF0W>VoisN( z$Ygy%QV?QD|Ks>ZiWu22l6?*sbj}Ie1~T`5imbPmQ5X?@Zfe+Kc(Rl@zo8iBZsTG+ z(OMsWu>c*Fw)$9S4jb06qSptZ5&(1!y}z0fKz@{pPRWX*fK~NXE`4@=C6)6!Qv$Qn1E=eRYPZF1j^Z^RIw-=e$Ra@YZ4mIM zNJs%}O-!j0U@U`A{O9I4Ymt7{E@| z2W)I3{syM6cBg`HY~;u3viVe&(!+P-H)%~6U(}%hoQSHJV1l6^V86y>>vNU1xCcJQ zZ`OMzb@3o}hsl_>EDmUF3223V`( zpQE@w_z&j)n3wjup;pqhWWT%u-u&x2ABq(>=QK$f; zd1U}oHRy+KOWiYTl)b$}*%ZSBB)hiZ=N$mp&1iB#>NlL97M0QGwi$WUf+ir0dr=QV zvf_8bVl~U&e%{RQ{qj+k z#*YT4i6npk0C9D`(iqz2M5x4cHz)N#LQ9us%C|6s{gbakB=Yol-35jUJYT7ALYBYN zgRw1ygAf4G6$Ln#c?LB_BQDXplS(=jYfR}NBA6c#Y66PCL<#z z^k^To>oNu3+4(t?;fZ`W>tI6Co^zbd@PF*;{M((+GQ(op*!>M}&#I|VGs<31)cl$f z1+KX|;!oFil?HF@jQ_+WkvCBai2yZ(K)4aMw2YoZdYsdu@c z^a(8=!}D&TT|f}>jD%LGL6rIUH$Pc8y&LMJ+N66DE1b}^R%4jx;F|}b7XJQ^cEp~? z`@MfLtmL#|3_g+HEsoJWLr+`qT7QHD#ZqeD$UvWM9}6ZDQXn(dMQ~4?@iIgB67v-F?+1DjL!7iVF6ue<9!(cQ8*Nk$-`1gQE|bw{kE11<{rfDbX8JV85lSKjzs9x3p9*i< zp1|zO^n?=jxcT27W`HJgkWMGHK0;fZf9+jLN{-vYgqx=tfm-%=sXBxE;;|W-A4LM{ zxkW^EV0bB34U^;u)#M0V;o8YZ`C9)`!Y*J{w>~Y9)Wc3PzvY}{SLD%Ml5*JtV#Ctp zIO{(*nHx4!ATU;94$yl}7({zFoE)Mcsc}Z9uWiY>lTDs314x#m0c>CJ#h!oAFh0I- z*ftqgn#-S%GXIpdyl^qGFS?fFe0(5fB;# z8t5>mKi~P)%s=z0UcH&W-c(mnx4UoO+w?u>?7i1od)+6yT?`q;Jl^j`TPai^!Zty{ z2#W^Ck5;G<#eHX~DSSZ`0l1~nc&Rw9J`izNP2D^Ol*QNC^O0A7u&)7WpzeuXtv;e7 znc4g?#c%+n=LU;JVf%W$!#7Xt^@loFSOaFa6o3=uf2@IFtOOT56vtdba!yKLb?&`u znam%$^w}x?8*}G0jO*>X6P&FlETHh=W?r=3$%}##&Ki%*{rJ--|NiWT{oI|oo3B7# z8orxv!RM@c^+3F)zQW3w*`v~l&tY9CZ2PT~S^Bfn{hW@5ZyAoN4#g$W>A}ertA+W| zdb0S_&BkZlmr4<*!2uzcvOjNT#CSMf-kc(}9#@;A*xUO(HQ6`!|DNFgxu0m6AA6D~ z`IL3R*o3&!ihbzs-rU~VNO^`g)CT_4ZSOV?6lVUc|3M)%V$DyK+_#>@quAoM?@ji+ zsTZI+^sMHPj(rIqcEWLHBByQD)nGXCVZyG@cIlZJ{TXxEtLN)15M9mtWn<9+;uekI zviunAJdabEw_G8-d(NdJJDfQ8(U{+7g{pm;gH70|>TG0Rs&W6jNfjz~IN=%9vpVjW z@%?CTr7IaI*U3w_q*+F2w_GO^X3QKRXljBPGe*3}Jm9>KmLbF^cM4U3>>mM;KImH6 zWg)6S^ao}j+XVejbFIDctaBdvA-caL^{>-C$M@0OPeo=@{JuuliO0V2vEnt%o0+o| zS$X??Yc_tig^KqyXz|mUVz|%*)80|Ydh>iJT}_|!oz)lX=U%;JCWr@%V~roqz@n8& z5|e#3S}iuy3sVe?J+QQYKC|`XG}RRMhQ7vs%)&bQiwasxu{?)wYQ5_@6|s@lbS)j$je|OOWiDieG*gV`h;^v~F2fbKi%?P%8zt zqG>^5%jf%ju)?=gZA9(;2}i*vDz^#t$mS@ZYY@oBo93AKzcX zGrApW*djyYD;nuA^YAB81fli-&7~e@KHzSFPGGv4Uo)m;zGpJ@5%y(gAJ&xztPjoM zx2xTH)*D1?t{4w-R?~TVq1R`47TDUv=ap3B=NyN(xM!Wq*BH#z2dVKX;M5WumTC4~l~&}(h(JsDPG-6U#BU^8K;O6N3f%EAMV~iGF{{PiMHR|g z?j2e?Z3i-J9`!T7vNdKAnSyn`-^}fKdzmvz2X)s>N4?tn7h6mZ)iEmD2WMM?vht`3 ztdHNu%nGt$lqUFHshBTZtt(y+WHx!vHTUGseP)_VGf(^O=p~L6mpckSaLsR2RCYB% zbwg}|JMlP_f9EL9y+~(R9^^_yLey_W-J{k#EcpJy2Y&`{mgAqAGuHz?6lX_|2C~>q z1a%*qE?pdIz>5%M*u+=+01B9!nV%OQ9qWUz(hHka+rtD*C)FjW!$!>85}$C$k)Zz_ z)IT_3pXR=*X24#DluBdjSBlmZ7J7wr&W*hLi^mdPhCgaN*JZ_s_NZ2_I#s%m5X@YV z_58>(<{A+Qeb{$+_wuc09Us9h6l;2x-6E+70@|qqI4lMRgzl+_$SrwBFe8Iv#fqxH zy!3wasSFo1*@Nc{`e5Rjkh9la+&5=6 zN&FaomRvsmZx7Lc*;Aw;SjzBzB*%O}W)=bpO2Gk(OY``!H?(qRxm}JPEA&Pj9QW4* zH-sTeK9Bp7>=6{$2QQEek37rsb1f(~IO=U&<_@h6HthTxdRUEJL#O3~ z%yoCJ5hL&QKevAEhCfk)?Xk#>4SnscLdHV^n4%QPK|yEc^82`DQUsb~ndWNFnVgdz z|HlL0{LncgTzQ-|(6Q12dmk%2BjC^m5@?<(iQp&SCaW=m^(=kwJ~IfiQ%pgppcUpl z)QFu24n>~f*UhNuEem0Sw+r(dyRn2jZI}~5w!tI~(WrNsGM}QOe{~%k3{E>Hu4b=J zQVFM(KF)kj0-0zIZ$HweED{ZuzM=PJsNr80-j1%6K?RZ|I+?m|buHL&_W4>|ZbPUk zQv5UZwLWZDcCQx76wO-L6(^BFdB8|rdB66u{MFxeBe#66YZt1mQ2Z$q1ycQI^v{PO6YpddhLo!275n8Bzb`)_^Y1uKnu6XrAEcet8eV2{(MrPqp71vJ zR`jX>ii&)KI0orvqJ3Qis%piN{@!`c9XKn${bRgs>dHjqgwg}V@jN8>iPqlXARSI4 z5}w(qfBONcbqH@*J{Vs~5NSHKnXCM;iL7uhy~sA?mTb#){GX^8+QXH^=wF@&6TJ!1 z%xvrilx1lVTfM|vIVqWJR6B;=zeCF!yLxGd-&+|0LgtsVU8k4#HRU*|KQ9>omd}Z| z@f;~F8Ftgd=yTDcXkI#%bE1h(N1$u01$Lpe>X{&lf+PF}hLD;{p4>T|>gRP$*3;o+ zFy=CzA=FWYEMH$p3oaS@ULWZvZqO2{a8zv6u!X`CPG`7fW?!}tqFvgP+Cs+VL(u zv497mcefj$K_N(}&RBOzxN|bqKSDKjHp955}$L8sS9K9x~)<5?@Gi>)~Y z*VXO=0b%1onhDf%3l*yG*k2!EhGijV^$Dc z{dHr@BVR42qaT-ct6^u_AcNasrA`J~1Hk(1u~Mr@pD|3qR<@W_wLxRP*X_92uGq;a z{|C3@NcyKwd40WV<0bYbxio_xbWUr|qWQc@anzMJt z*yebG^Qyphf@;535+EF1&NCYTgIGe`tnpja9BflYrX|vHlCpRI<#y4U^--d7kDai2 z$K|5Ma>hc#t3)dow*&#lTL@+wB`#|=Q0-3mpVRYz6GOrAKJLmCP-%J;Mu6>EHfpp!a6I41M-uFDV^XJJyI~-V^v82>VhkNP-n&U>Int7cf55g&CW5w{0G#;${YU

*=9a+OZDkjqH0~yl?KPNi%|vVP`ylOEp8_jJBjGM@D++b3yZ;Xp(<4AjS!#QX&W$s=TELD!p(GeIKlJ2Ce zxDcq}FggwwU;pDLa$q`5256yN2t($;O%V?%dw5<<*SE?^x^fQSuw^CRBuHAPST%-U zU_JA`>6J&+;Pu{F#U8eT!lT=DP9N2NbatX&H9fu6^R12#Qk1P39XU|X+9Mq#46vEV z=$a!aSC5AZDfa*SCoC9vGXfFS$Ng!u=N2=Hgm_uAVe;1lcFwt{s47jC4B{SMf4|}1 zF%1i=`Wn1xjTn19M84O+Z#*;7MP&)^I$1mX5KzntB++EsObDu9^Cxnd zWw5%B4K*;}L*d0}WP3h{oB)hK=AUuL$Q-7b_UDYTom^&ZP1xl(DD@IUR&FAMPc@XT zwZvVw*j4>sIp5UsdMb;z+KF=D0`_%pqND~(Ene2oxnMG&YNrl**{y&aDNBK5yDU-~ ztR1S5X{JDuJG=4pDp5p?=Bq07(5AK|NV3+W9Elt`TX^~MW9;gcP%Zo$dg4+!HTF-X zVBZ?Rpw)x1FTSom&HuAwWe#zQMvh>Mue$u=78aZ@K17y-`UsqLLk!=ClGa!aXbB0N3ol5!M z);-30GgbjOEQ_&pf+jiD5hP&0`@c*t=wCsBWQI#6Z1LAZZ3nYxk;~K1F0XVm*^yKU zbFW){o(0Ir8#D&|t37KdmFNGvLY#nQ*-hu18drGo^h~G7{PRGB5JuY=Y0S|J8&%%& zTW=sQ2Z>H)J@}CVslK1y-#bLDCE`TcHtsd)+HdsSlkbl;ewo6UpPHWrxj+94cW<9S zAFlQi_ENkcH7d95j7th%m#B`r{Mey25By<;N;4bg zEG6nGn)#ge_Kp>ZSQ&n4X~-NJN7fvpzel2gWG7IYf|cA^A20O+xnA_a8{TwW#>~O< zs>M~lZS(bXeW&r>j1eDVr@9IhTx(7{lU~pmuk)jjT+Ip5{+HKp8hZ9NeV}2S?umKm z5|CRSWN$uBqQc{hE}2Bd3D-(ZER#O_rM)+4J@PW?dx3F`iW9v0++wAc}*WwGdMn|BG+fG0>i}zbeT*&(5!V{ixhK65R&fLYl z(doc-Y+Vbth4#S)@#}v}b4EAMM?XwA!9@SO#HyXR*sY#$F@~0Ft)?P)vPT!HHJ~kq zc@5rIVT^FWnaWQ(-Pp;1h59*_z`UnoYj8Iy$&(rCj~|=pFx^P+tP;HxdxLpQ&_3uNB=d^}#pTKZsGk_+7d}U7g0<985Sgb9mTgtBL zA7mp>^!73#o&`<{jnNL}0+rBI_jx3DO2c9BKM%qajjp)?&RWNP-Hxp7pWEqOJu?B? zqYB%%bNV7+(o*n7}B2=#392I+xBNL7gv-_p{?AX|M9Sl7k?jp8A}Y`O)C;pF*$-1J%-yd(BQA< z{p;=5JG|aL5P>G^P!8Te(Bc=JRz+PF<$l33q+V6S<6$%1*jCK$-$EKamaA6SNk-`d*Ozmid&c=SxpN@=~i=E z5=5uKjZ4w5KRp4d1^G9Hp5f7XmswK0lWhk?$&oW`XpzGe)CkW&2UH-ehJwSy{>_iz zRL|v$feyWJ>E?*flaHIPuNsY?v=Q2VU9X~+6g5tPI`XZ-v|%j)F-Cb)arXNG)B}PM z_`RQ9*!EzoGmADx%B7(ZrJ*`)w>#J2T7mCxR~H=LIRHVtd!@i$gBDxzDCA!|7 z&5MuDA&1e}9++Usw4te|t3K1wn0e1!ueae*T-xniksZfd-aC_glt~7TWNDSclB1C8 zF5DS|*`AZ_tuj@GZsirKPhPTU`mu5A*MU;~H_hu-Y~fXZ9L4Ko$g^%D!~pGU!!OfLS>1%V`POl7w5Mr9x()_G zZDHq|b{YeVSsPlpt}fDrXp)}%8g`wX9FRsuaal?P*2)rzf9|8v=sO8Eq^0^*r;b@*QU+uWB3f4t*FYR3^2AFc3a{3AbmxANi{pZy+c$-Eu8w~?tEDTZv# zi33gR@Qm7{m3DqmtR$%Cagr(9=~s zVCV8nv~;RB@BU?wxY`7b*9HtLbkuKsIplIac^d5q3x|oU4Avc@!!p;IF+;S79i6{I z(EAWRpZ^h3RRosq>(Li>^wCfn;uK$qfhK3;9CO2u#(V!NjSO}}>E^N37|I)Ar_^d3 zhpDjwg5Lq6AG-1syF<35nW#~A7;jI^eDfR2+Cbh=wJy8X=fUZ;?!&G}%4g2!^HuBe z{Lc}eLb|cC^RJyiP-;?3U&NEHBLhDhAIAFnEWA_WDiMYLHRtM6ciQ_aqW`tzq}63~$)jKrM3V6WFJ@5<3tEe(CZj zopEp`jkD_SrhcgKy4iH@WcaArH*k09VZ zktbOMmgTKjMMfG?J6dXDE93i6uE6?Ox(2P^qE0^mHBf(v%_7Kib@D=E9r9@E>Bye9 z(v}DI#^OP-v}Z9fY=kZ!ZD?*qRM1Gaw$dhWM@qOPLSv-fcxL*7+L#3>k11jQ@U3Xy z2c`Uum59Wtfz!nL#BsBwvq%{H?d1l^c=B`vQ8}$8H~&8k^%+Kh+We?M-N(ep(F*t+ zk{e8Z#jeIi7>QI(OWZJbx#(ORx|SVZNp!L?rs58hjFHkvu-U-VF|O^I#-I zt<_)U5A#akYaV1u|I?UFGdbg>WSGLPe8#|0NBLM~w+0H&zFzT-7w>LW8?-lBX6|1; zynl~Z+@r$psodGax-)))vES2_`erDy!&t9dr~&DgzE?LvhGA?MRikw(__+QL{B1I4 zS`Xj+Yh%=$zt?m{n}Gb;Gh`VLDjwRL`E;;oPWV4K!~fY|wgp?&$$qCVpO=2y!O~TY zr$&ZXoP?nuQ$N8X4t}hyrVcoS%A4(1a()qOd+OQ+w@~B?5v?Wa1p2a|nbN0CqtQTg z7oebIz#`A}bFP_c1h;F!4GiTGi+q1`Usn2EcgTS?xK?__E~Y3w85WZM%8{xsXZtx1 z&nCa$p&7A&kc6eNZ_K$4>hL$iLTW&#m|#G{9faTyXATyaSmodOX5+j$WBL3)mApX; zEb@OJOFrOxF=wo7d3r+834#1j&p%@daIhGzzsxDl;HcTLDNy(BiD-k~vqUARtFm~k zIW^9Wfi=Q|ua7DoOyI@WBknlNon6>h2)w*Q>>y_jmd3fe0b0D6s0vg#aJ*R1SKe>D z`*YR*=VH_)`jLu@KgpS79Fn41?N=t1;pE)47Pohg5(K4$AMBls%|>mi%(J`oK@f}> zo$CleHC78XD(&_Pl*7Y&rvt}*sXi@^p1azoD|kOHUkVXY>yeo=g79Dqjpes7Y6Tfj ziENKkE7n-XljhxJ;hPEc#@*bSVkb9r6y%eyUyC=}{!hC{N%52IwD1HApZy;(REX(Q z3@h^)u>#&l`Z_nAqzwQ#6#SQ)q<6w*1qM59c|V8PYz=jejuds z1MODV3`1eiPgUrWWEZN!kJVl3eoqS2=ZOCcX6R;a{XHpKFMVIT&4r#(F_iv?ZBE7a zXFF_oa&%}Ulct@B+& z8PBUP?xb&wJ&E$}N_gY?VW4VHYwKV!IU_yZhsV{T;!G&4=1X#w;p*z*-%V$Rpp8np zZvPl$AbHR##=|b|_h6U(B3;5d%_epF5%)9xu3m0qzO6$-4FK^?-g^LjA59HZK|u#5 zfWN(lYh?tFmxH32V%i;}*DFCM2E$u|WDsV#ct$HXjWJVXMYlXgI5UP?rg$Ys-DADB zQ=ZVnjSw%OgTarM9xUe;MMX^hjsCqFK4)54$-)Rf@*ofWC3G36$ zix^<2%u1NFxVU=KhVtaU~3Xgy;(D?>Hr@>Cby>d0oP&F$I z%2&m|o2g0vH@D=GA8pI^n4&&inx@l_3{Gi2wb<0jFyZeqWRRdQb*Niq zc&ATvkxR?WfpbYXjjP)dsVui{Z00H@=%fMEN|`U2r?>@2 zHnnr}24COfk}^dHM7citdASt@l<`*hq_yx^+V<>8O!|l4qK&vq815wfU%TvzF$+yo z{89V#I8vM7Wg7$C;lSllw1MwGOAa5A(wkE8i-o{vYddCKD(_&Py9=_NMV&3DNj>io5-C5xB`tp2Jp( z3sv!gOG7(du>39dN5w{rMIo@Z)F}|hnth>gK@6e52kf`FwpMwGSLw){ zPX~QD=lo9ZW*h1 zCKX+Ltv;tYHcZ^bhFxFfE^2D{PUovV)v^C=NJBZ#`>v?pi{-TS@eZ5kUyCIs2!*OI z0<+0df#R4YFqM~JSZ6bK%G*dllpLz#Hh4RMQiwg9#_c?)+UZFm90iLa8NzhXTCi=FK!3&xbv?d( zuSff6qUs!uvTqFVEtPEHE}EX6?~0xIdZ!i{OPjk%_zO=n#u7RHpkO=aC_gzq+-{NqniBO^Xo&OM%Su*b~O+389N$;A6p^C0bG(T zO_-GYwRZCIE#Pbb8`zC^i}|hP=+ClK`B2t`$;v7i=3!Qws?x7N2>o4z2-9-m%sWTQ z&%v!@;wR;>$15_QDtCkR>ey)N#O8mcFZus*yL}97u^$rexr@GIG3-(8gNQ|irn4A> zI+@)ygwbGlPBU0<9m;dPQy3sXFIn|JD3)Jth*mv$=X%Gh{Lgsj;{(ZF*t0h-I$QTu z?L_U&zJC{qQ~^MIANNR%7S#-9j;@NSvh?<}sNSid1o^5O94-Bnb@THB{gl1*cagLv zde=?@-By-@r~2G|@bmI@X%uFo?{x5={bVpjjJpnmiR~lVZu?|b9vSDd$Ri+%0vKy1 zDq02oTgn1D0JhU*V22(|^XZ(Wbo_yCyY~h04yf4Mu0j4z#3rNedbsv}nxb^_n9|v+ z-}?LP=i3O2=)gR272wRAS3l8IF9&jKapLljGeKhBm8Dx0fkwuPoYBDVOSBh6Q! zHJ|EvFD+o%kl3RgXkk~)`ohag43&VJJ@neO8)|ym8WuJD4~k|~r$5f7AJn?K#ekv5?pq0B9$q~u{k&D?q`0$k_xCajNv~by7^Y#N`_W1D z4Il5tu9GJ2K5nZniS25_!PL%A4qay{>40H%@J)e)p9~caSyx3XX;BMJ%;QSyug@N# zQ-X!z|8=p7*9(UR;o6;h!8lMLrw?*7`&%>X%C_meyoLDCy7{I3qVN#9K5p0lWkcSQ zV)&xSqsZ6$d5-aK@+_pY=^K`9Ch4hz#umx@s7(34Y2%u3wQ^bv=)hiwc22DGVEFNG z8-M;Rqi>&;W+a5T%LrIsPM-rDt3w$6F_so19|-ShpQOMN%8%%_e3$!U`!^IPgA+#; zMsofy$CPAs^H1qKb8ePPm@J3kns846)`B9feIx@EFdGf@}1d4D5<=%@gSj| zePLG|xf$BUY=GGL?%!4U-{Gw#$j4!wAF33MnX&_)?nWeI*C8`Kbpol7?Dd)J`(o$H zs#_ID+zOEAFaPW~PUgagSLDPkpfMcNHAPBOFdI z)kmGnfWA<`1tn2`z+&3Tlr12w&{dy7p@L@b577}nNM($UR%-WYqZBRi zj9X8ynW6&vXUi^gBuoq~(JMiM>TFaqpi8{b)c{1~%1w$|<&9~Tv-hJ62*DY?{y518 zg^gzDA$vCiCCXN;-gQaK^>O(T*!jl%3_F3COjJ&1=DRQ2x7@z_>f;mCa&^$&l)CFljH`bf|;!oA;?X=9NGq!ZZLn7{+0`YB&hc^m&-r-&NE^$P_?4`PGkb-LK znb-;Ii=wxq{tdRwKG#*J#zj{J`}nV!p+lqI*QG%8m}=pI!dfbbWfdl<>lc=-2WX{I z@QqpU0Y?7x2imIq^(8A!S>0w|y**?`kgdtPEKFliL}5PaF8#rpeuQ(s-_P92=o%$! zt2S>ZIhP>g!@QREadR&kU8A^N9>(S*6*#RH;y*ef$CHKH`m15kE8SrycWQsPPeuXk zPJImb?}Fqg(dNx%tcn>LaZl=_s(=?(TI$Z#$7`Bz5e_LT{~cSLr++*S&kjzpI657$e^7{Ks&1 z*6W7cN#6J#RD%Y;r8nztH-Y1P73DudTu}=^EHsasrg4Y@52% z_R2%I4Wr(56Iw_AL4_)HhMk*@hA-yk#$0c#%l4wgal8GI-ib?ZEN`{Q_A!g<^4zWH zHMr2R2|ZLU42OJM#&x{99HEta)j|&D3P|#Uwv8Whopgbg0NpG3U*=Qe0~h$O?I$p` zopf0sErzg5cmNrV))v*?_y`RYlOOfiQ@CVgO>W8T&2!^JW4nCS>w^>dD|EK8>{Ta2 z+umACeI7-XH@Ub*q^?X+z&}VYae+|fE)QyBCwCp*I7mGvj7X%r+dd)-&c7R&Xu}VT za595ZZnMe*Z>l`$8&mFszJc;orw!odd`RG3#|wm&lv~`|(AL0y~ILFnZ7U=OeVq zNXtxE80TTdTM#vMjTRL_@c~ngQk=&D@YxKZV>sIYYNw1g06YY3_Lc;J&~mW>S8|cp zLYxf#@U_}R^%s5L@1Aw3#Wo8p9PO-!6lg4a6=o4k5?0b~Pn@s-$nMd2{f^rl{$$~6 zBxhWNLrlm!DgJ}IUAMc3?MPkxJ>#%=w24+IW~e&Fo1)56P^8loti}BCc-~_Kiua+u zCW^JBTv#eZ*8;4%fUvlAX0amy#*0pq%f67UvrXbc%7V?1IeAPjH)?F-RW@YweU#7!$qFGIRXNRx| zCOH`hsz$9Qh`aY~>H%o0i7NcR_i2Tpnx};4e!~uF@{fJ)`khYJ#;7#NHP&tk?o4$9 zcFvPz(os#8G}T!Hnu`cE(0lqT%n{mPcEE&fo_W76j1s9w8=#=Qk_f>RkE+I(THxZi zk@9OzOK~OxMH8KT-DWl}W=ngu{0F_{5#5cmdD8gy`Kqb}g~py$`6(juYs!uw9NR=f z1zEV0y0`7L&hqPgL@B6~AHLj0V%1;O(cvnc?f@fpfuy1OXozph&egTgCZN);Z5M2j zxM-2mxPOXD;O_G)5M2~_aq+pqm3@s{^qv|H0JbVqu;c>zXcKhV=QU3}*}B`w8{YS! zX=>D(ZFnQy+IusbL&IOHhGycKg3`h~i7V0uo^6pdXqjN>eKYdhtmdgMs!>})AUHgr zlcaKt1MonkznfH@SxWwA2ZZ3znnLlG^O-xvf9ox`|HcWhF})y7pDRA!+aYGyjl;lbrw_!BrcAAB^32+9rCP4sN87oV+1Ldz+@duoFvi< zi@Pae+*YZc45@6u`FIFRirvCZ&>Q!SzJs~3lgq;V5;sw^Z&kVjlK=p;VP#=`rR8q- zh?9J#zyEnLdKn9i8})NKSj`AL{yeTEuDsq@vomY$9tr&T05H9i8A`5s@|jCqB38fY z$XG&&lI(+=?q}e61rY_n{kV#YT0^N~kJLhQbh^U}9O4_)`w(~#3fpLU32jWx+5c$f z&Wnd1F+)Wus0X?q=mA_Zn$xrVbmV7G=8KyrGG8V_FtA%A{RhpCJ1iKzoxPgKzv=&JqdsUK&vbz~AfC?4B zRct`>$Yu^ard)fm&FSWho3A9_A2_;i1Iw_AQ;`(0yV8YgWODg$9@vz=^R@kt%a@K0 znxXKj>V0nTXjU5(sNy)isIe2o={EzF{quW|9vi(fRCjZjvg&_fxJ(A!tmOYQx|fFH z_G`#D<#S-wmrKDC>UQfQGmB&sl8@-1ECsv_AT0sRyMn*w7ZZmyt~nv7@iYD?wV_r% zEU4a?36t1wpAYJwgnw_5txdXyNr_s3gQp)s@Ln9zVKO?`cmM-OAQycP85My+I+d`@ zc&g4daR=D3{7_3u+z|EaQ3@MQ4;j+DBe*Kx^aF;7GFt772r_kLb!(m8D1#Y)yK9!vf52%eFQ4C1WLvl0}O zdwCoh`{usP|9tFV#rBVOB2pR=4*9eW(>|(~V=X`vQ?XXL4UKvROV_d>eaI4Az)J}^3&M-5f8JnXSY5a z6K$XC71f&22ulvL|BWV!C3Xt8wTbrbmA_!jTpu?EMie>5!$%td2*KkLvkAyRZcC#* zE>&sjti~vFJPcn81g@f+3}*8X?etbBHNyy?`G)Q@tr#7qe!mdh$0ZK(LiYZB+y^U3 z>_BCmbejK-@9{Y=?yZ*-~q{oU@ubxioq#AA{`bE#GuJ8n?q(EQuqXSc>o?G_J;pkR+4isrdlX+yt*L1(Ep3ARZQ%r#A9=4NHQ1`1)u(#~L_ z?Ktqr$7g2Z%T z{`5k2d=d|f{a1V!;uozhLjGt?Q!E$;%Tc;|D@{z0(x<9^@4KYj=9fd3;=yLKjQb#U zAC3gK-^>OYpjfTx9^KtkIlcK{njC;jKks|`3<3${1<*xb9+5m=Xw~~4cm-d-IEG-# zx^AJ^=^-J_SPZw?eJtl(Om0bytm&y>L)EopNEhn?^yr%F8-q?wDke^P{f=^bx2LJM z!~JkaJU2W(Us{1;n^kp>bjcjKI$C4vtAr0O+xxoQOJ6-@rRbna8|T%A&N24$SFE)U z$HSt%9WzC38H{$Cmdjz|Jg5$#=mAHhaw?Ga<&LHr6n3Q#zAl&l1e9b}69RUK0so`N zQp^;IH@!ycn4$PL5xQ52*?if;r7FRmST{1)#52Ev&mTZjM%7jRDE<OOFTg&Nkq5*uDgF;W$=uEG#15HZ-bw zDTw@9D5R7B7rKH*b7OO*8@VTuXjcy5w8>BDD&bAtc!*h+?O1n-jY8tNpwS>BXrC5a z{;`V6$Ft4J?@Ni?!fEDxen)g^0w2T1Dj#-piw16A4V?`okyV@<4Yx~t@54UGUi)$E z6G9X>yut&zquRG6dT;SyC~ZgAIkpyaZchFwPzK|Up_uf>L<~P>^xEG^KzS?(rGPgLPcfreWbCU_n_aaO<==S7mxP{LS3Vw2*)J0!!en3qR1CgcJ+$;(;~aKeo#82Y)?&fP>~DNsor0qO=s9d&+( zwmifAF`<#;ibjhbWo+M0TKChR)>GK8j6@=9+^-n{wi8F<|;Z zw`+5z7J}fUqN5_|T0@++5-ntey=I-6KZY30^;2n*@fz17J=l?>Ye(Ua-GUmU#}4a{ zHUh&;QJiXNy#D%|&8^QX<5*ODPH&D1Q4aOR* zPOUglYtOH{{8T=lULey=>Z1<;=n^{DoTFMWYW+JRz{X`_-Nd*|+z+F*Y4&>S3}N+c z{mEw0kU@<~>zO~x8d8vQ42~wBU@4kbtL4fml8a9ZW!<_kjl3J($Nh{9ySjTLv-wuG zuo8q0D}NW2;B*oB><(&(-dEwlFuz))hFk|ydhh#~Bl5YDja8XCKVo(IdkE{32rRX%Tj3y#GtPw76p& zUCx`5FaE@ff%Z#J00WnP>3H!&v4tc;;(+T^e%}nUzl=_Cno*C)mGbmTMM|WExr)1T zx)&voRBQ2C18Sfe>5p-xUAhotcrlPOHPEK;tkBGz0KYCK(w9^`*x_N z=L6Qp=02o^^8>7qullhk+R#yf`rAsu6RoR{jeJGV_ycrAFa`?Off0kNtAZz*N(jiP z=UkjA*W~S?P8eh*bQu0e!u%X*o3!Ad{PW5wwG+n8=kdU`Iz^Q}In#-q_`!J4!!~}I z8g3enQ3pGF;{CkJ-#$>nrENS}VaE2ZA`0L|ywl*`I z^9si|aMprl9SlJU52s+-vF5s0#!ktP4<3#IgI#{M^-RgGG9I4Kkv9 zZHiT`J&!Z)qr!zLudhL!vyja@8IN+|iEM$dsf<8LGH`M;PulH{SmbmDLAM2mBX|iY zH56ALNd*B|MZFC8dhUFJjbGKY{_REQ{)Lz4dZN+5L*olr<|{`ZLx-K?GJ354_KE%7 zGC!K8{y9()({40jE;j8J@T4p}OH%6b*h_boPmnuqSa7Qw4@y?^KZb{}O0pX@FCH426THsEFLbb(iakYr*dyR zY2;iK*bMMouJ!8h7uiI==su9{W<>Eb0nI0a+Xo?x^@~{-Ltnpj@68W;s`b6xiNF8h zq<{hmmHMJzXj`IL8NW$gVdo(V#Exbi@1+6~D3gFcHE}#r`eOYnkZmavC!S#)4O*ZS zMz6g!fwV(NO%L5-EVHlAqRM~gMZ(0BXYKBH5&mml@A4l+(f<6 zMDJ}4VIF!fly)$DG<<&AOl}u#;*Eax@C}ml9_aa8Z+LGiQ59#W13oty5F7NN8P~?0 z!49lUk6u%HVY4XcHX8EYk#yeX0|p4-F@mo=Oz@53YAt4gC{jvTYj0@S5ZBQwcCbPU z{cR=aP1Y=5qD%hcvYg3bSs(7$XykJjBYA}U6a)Pu2@n4?Kdwu?J(}@5a!>&o+65V< ziZ2&9qwv_-**qM* zeR={WlyG3k+lKqdW5$WG0G@F}E#_9v{c3Grqxynh;Uo5YL5XKPeDKLFSJI@V`1PPt zu2U~DrxhSX(uxBf$~OjpNQFh{PJsYJ@$;!+oGVdT;0;U|FGA%NQ{RJrK_w6KwCw_t zSYgGHiI*9m@;1KV)A|a0%P?Cui+Lz@slohvjJBdI%e^IEdL5u(PYl=8|2H!V3x#3_ zuyjN{KjSh_z6cTYQG0Ei#%>wxXV~j5y!jn$_)m9l3GwUu{MwDK4C?cOh-32|wmQ%9 zr;*zkI{?at2f2XmT}d?swz%YDd2&{(r$9lH1R?}rXb7f^=L3Nnemso$HW;ux?p6O7 zlyJ*Y2{rEhq33>R%y}mCQgFpVn$!It?dDsEBr32`F>Q;4{K4JE%em*&~QO>)K6e@0Ch))Uu?iIJG*BB0jFmIub28f=L|8`R1RwNcI*OO zDS)Jju1mc}k1au0Ob;)nZm6u^X*jDMZ3AdJ~ z=>-~?eUh>}xM!`LV$Ci5bJ`SEvD$ z5zYXqz|-n0vtXQp4Hlgw(lN>3lNhdltTp>$cK+zI)?Y}@CCrJqaD4don>R1w3u9Wt zQqja?x?4-$v#X4x8R;Vt=Ze{_Vn`Feoow?==bycuR7DSS`|JL_lyPE$AB8Fv`)i@f z2=${x?R35$^P6F)!94}8-N+f7U+wqAGkui=X*}{o|GcceY;SY7QEGuU->AA|y&^xr zwqk!_azc#5;eSTl7cbnEui#^s7k|%Ju>0rij!Z@A8}nWQgak>+BZw)}AP)q6_mB0V zlVI;!^hRZx!V`ke0cI#;{Ad=S$hEun38_V!j3w+Z6v7mj;g@|XPXuV2;4a3k>PsxDsVE96iYVRW3>iZ1B(C! z4)@#2(>o2io{(7o0mi_E3$>srIyyS_bP|+I*YTs@malS0G)|t*DYQk3nrAN}8sx)=Mj_zdAOf}WX}>|_Q+$UP2Yxb5ix$032Su&c zjfSKEQ+jY~ljIUOa)Wg9R+MDu?=~P(LS0N>2=MX>)mb2)fUEI0(p4D+uWS9{C*jp92B{Di;z0>hlWu-(2pO|67G@$%Xp=_jvz3 z2*5gdg@6!-kQNhB{RMfh2b-%lINMb->vdI15=N6tBQAcfS1?p+Wqm_Egcn^WZX(M* zm~PBIlx%6N7^Q6D5N7;y5G9O+u5)?#?CN8itG?Z0s`XKjNM2<6CY7(9&(6Z)=(4Q2 zbh#Rw;zXG-ie<*;M-u@7?4rz=#KJa%P{x9^wUxebs4PL4EBa8}>o#;`&)*v-5E?YU zO9w$f8S}a*%Uk$CqNi7?LPCMY@f~ItkZ803d56PpBOtVI6DU%;iupJbC}C*O^-!R{ z<7LGU6|w>AP@p}!b+ip4v=d}|)G)KL(2@_08t7(NikxOq9Y-(WLi!c~68{2-o1}lf zwDXzcl6Wk-4E4A5O-Y_CBYvMVaHBu zByZjQXnE=jU&{`3jdmm@bvj;4ygyFXZ37NcpMLY7U`_W(o&Qq=uux7;ReYiKI*NWT>UvI>sSgLM=Ds&cA4Orry z{6G|T=^L|@wK!&fDu#P!RYIQc%#@at!efdTxE_TcB?Ne+OephD@o_XQm*;Ci+g%9P zDsxT43hD2nj%cis)4%bM!`!ig0}f$X+2RX$B1%S!=yC)pNXa72p!I>oTd(^{}bs>L=n~JaZd{oM{0N(l8@kF{(y(KRbWOHSH&m(a`L>Yx7{=;8* zoX<^LuE?$O<#OVaV$BC`OY9_5FrWw2R9-vx0N|2qOh)0SKHO@KdUhWd+l2UPgYGCc z@1Xb;t&U|;Nk_6+#rIYzN|l^^?2aJYXuF-6sQO~dL(_d(@8IL4c(jgu_GQkJ1`+(z zdl3@OHkYynEO1fbgA0z5t1}0T>F$63kWjrX_A%>{n9h@LTCzHDfK?wCGzBpUN?(A^ zN7Y@}5d#!6@wby~Y7n|u@u$h?FYL?0>!T=HAn_nzY3;W?Ww}$zT}(@dm4R;ul<c9O~@vv{*Hy<#D~KORleFh|G$Wlo=xw43M*8RIBKs!9-f} z-M!+G_7DVfMFW12vmG>ThBRO$tO&U>X@SOVO8IwD+4rjgDJZIF-|#SqzCXw#8t(7O zWHtY`h_EEWh5trK_3Z$aQ2t0`u4jpVixGVjX)@>C6hCb=_ZUpGQBlFC6c7F;$62px z@1Coz#yew)R$;Z{nC&r8_+SDAfPZMtKkcxINH5ns8Id36fR;8xt(gAA8IVjwJcWIP zKf0kX*qL0%p+bSE?PR73$xa_@>+s3i7CuIBi4g5krA~dXGWIR-g`{Iy2wL9dgu4Kr z*;71R+ek^Z2}6gJ#&9_rx9qnfq-3R$nHe2dk{A-=LZZ^ASbWUSUI-_C>~atU6al>M zeF!51W#4OnN_byrht+mz8;Y+TbZ-|UiKP=y{l5gJsC`9nrOua5(YA-cw&sd)A8(lL z_-glq4RP)h@bh(p&lwi%U5g%cENHS{wi)BtfzC^Ay(=t6s&P~d>wYvEq(ZpXTy0A} z?>FVmM5DJ1xXyVo=qgiz-i?)h#F(TZqAOX|8$P!-)pL+_Narz^eJu>JG>&Ja&~Q-vaJ#W=lXTk_Ya9gvE1RJ zVv}`5Pa@DLX97KX{4Bjlf8z^d-$N?Dh_b|?`;V5r1IZRldM_Yn9~Qt{s8m~XPkFG% zc)`M=E6@Y4lkt?tCTGexf97xlSYH^2!5y)4c2g{Do6$QeQ~Y$M_1R&GQSiFA#bkNo z2`nBWWVs{c`3Q+5ao>WpqX{jb8}=6u3d^b%3X$XD*A$6A0SFgi*cxBx-h9nukXd~-K9lODwdjf&8DgvJ7te;(j5ui!}Vt%fO7=YzMD=Yr0Gj9ns(YL#b z1ttZ(3L`->XzLC(dSd8O2~#533ytOh?~E6NKC)6U54qr24n`O#IBM)O2s3l_kUgUX)Hjp@^FRv^lO42-z&1Y9IePn zJdvaAEM8#94R_$F9N3xADMgc}%N9{JMoo|o#+ya0eLp#x3)jQyZKZLv@ia0TQ`Tg? zdFFn6x$=aCm4yU>ao6SRkLYd}fF}3v6N<(X4ER%A_WI%R<#N%GtZ}#Nzx84`T%)X` ze-BDNu?+xTuZ91GFXT*^T5S*=c0VHYYsm|4ii!pCr4Sd5T}ISJNy?P*G$M+3)`YFd zN~6{9i9A@CS<)sr_sbzDbAWmm(Z-2!g*HS%xwfN4f|1qWJ>%-H*bK za`-|W$!HsSXY8s_f5dCHj|q$Ps8%lW=vc-$Fi=iW?yUJL0Cx#WI2l{o!sxc>VS?20 z?D2&COvf)!vvTi=#0}`TJPW5u4+}syuFSd}%6p+r8hxofUx7ZW`WWIZ$C$GbHU@Fm zNihlBHBZ{uz90=zlNClbzX{Ku`_e?TvgsHXQz3Yrt&?Hr@hH+xa>15jF4n&L_=5p8 z#vgL;S8s_!s8C{o6hRTuc{8a)vD4AtHYDLwSWiZPsKZ0Mlua+@PGxKZgccZDc@Hb5 z$4MNUh8LiQdqXdL+A6De7`}R zR*rzEcM{XS0e?bUJKQLxRzw8$?44R}7MmF|A{oNK{+qLEqA2A!;_Yevmq%3M2j)hD zPSPKTSvozTcg@tJXI`8*f8DlX`G}*-z$)NjW2^D-|G+oIS>PB1huDwO6N3c37Ch9` zXVyz+D6Bg;CSvfnku8v>cD$E67oE0HJ>~$Jt!yO&+O+DdkauDtGn{C z3KR3)LG(SnMkfa=tC=;)NOQEL9*)Z>y^M7T zuLK#r+)`R3Gp_v|^-CdD30dz!o0tJsG)jSlvSfYfq=me(t*}u>K|IyA$c;Ctnn*i~hQv=KkzMhb(59ZSQA{-ZdORWR9X&%i)=?dBCt)X&K0=iDz=mu?7=5hrst1R4S9PuXIhj)i z&g#bBsbK%m>vCFW5Su;9{!|%%A4nJ#xKzlM{qy9d5fsbs0`Z@rg>y&Z%Gq%JM8t&= z(Ao}#apE>m+>0LOVavb?vsispBvx-MN=gv(_hMsS=-F!B246p4qyo#x3yt|qM?zn>bYL?#gv4ht$cb@k*Eld3ds5l_ZE zX8#3PG?5QGiRPwd5~?wC$I@?3V1oIXIPhaskkDunW7ruJ{YcIidYD`cj zI}rcD$WcSMi~S>oEAuFd3OeY)l0!r-Z0KBaG;fhm-#N6}Pb#QD_Pr90_8?z5d%0JO zRQ;VL*t!MPBAJ7Xo`5~l7;|L(KuR+22=)n6Dfu?=(K{OO)8sJJsHUuWD1G8 zHq`*phr)-*P}8ovaq9bS=u)wmfd5(z8xvtn+}!5wr)%$#sGgj+Rn z??nsx;bUyv2TMO z2}6qe32?7~K~&5l3Z>1gRv$C|lhnM7EW-sEBn6R$QX4EIH0dT3w$_5#!f+xHse-j- z^Ph4byf<@#jKM=mpEWB)RKeux^SAK2pgptZ@?|hKGMGfOSqx`7`s0IM+8cz1$%<1U zQHC{HOs3P#pOE(jvNO@|3FcJ(qC{tZ96~UPKOyp4O38=ErSzVp2fNkVh#-t=Lnnvs}qy zMkJm@!5}ho0leg3_2#R!7aERXviHyQ6{d7tBG27tr41F`YU@o{$KR)8Uw*JC0&HZ0}#l{-v2A2 zc)Y&|kYtoi5}!i%4$}<(MY;nTukn;OV2=W^^=;l~rFkKb6%KG$QG-32QEJTJ^(UsI zVR$-+tbtp3NtufAQ|6%(uzHk%G3$mtb>csW&vyOp%bv)VV>NcWaiK~EVxJpw@s~Tf zf3xpNOuav8ndEXKQ>YC~yho^h5DhG~EC8>Q+@z5d-)lbVfVC(xd(_dyrntly;cw)M zhtROBw9?9AM!h{CMvDQ0)J`;Dxjy(JsJuW!Y(03wX%8y8asZ>$d@eT7uqw z$Y%grtEm^hD*AXt-~I9l_|5;``l4PX$v*>cNYci{2Nl7~%%nEAbk+9gng)v&iatY-my+Mnv%k#kH3-&ZKB0-ooXvdFl8iO#;~R7p?eH*hPshN_1jVZ!)c1ru%e z=#6D!)iAd7iVF5*1UJqxJa3Au&EtD^)wmVnIo`-UW zatHME3A#tn<%y_8<{6PlE0FIxz~)NlD&wK4s=Q{wo@iX_{;&OUmJEd(I?5P5A=HwJ zsh&hZFsd>k-mxCy2~X+YLMQkoytbT%#ymEmAL+PINiptq? z=oK_?+=h_9E`0pmS^)1N5SYRdR25%i_7#$VhGHX$sboB=e#P0%rHi}kz1cN^*xJ0#oU`Y=Ck7@ zV(m>C>ZXcMCGOI_E)ZfGYA`v>;>ZB7vp|@{2!C}|7Mk)O+crBa*`WjzezZx2A1qWLhrEFZ#&RV-x3>6+X6>9QIHWQ^!QLi=R&a9%c;;z!qb5wlS{#-8-9Oi3NVhUs17PkzO!){ z6h6$mup!<_pMruZTWu^K^39sL zrH&fbt9P;*rik*y6>4LF)LBksE<6OIpD8|H3YI)HMtUz)KMu4P!46zt_I!gd5a*M5 ziVdX}RN?X9t#*4+V)cWxlG03Np}e_mQNWi*`s|RdwLg&>0Im_poO`+!L@>hqbT-(2 z_491>3cYFIp=}SkyXEsDDaARS`91LB7frxIX$eae!JhjCaEFlCbOP;{Meh6$g+?E7{+NC*S*(QcDZB%*eM_$SIW zA|bzW&mMC6;C{h$38_dan8NJ$&KeWK*AeIziy@JM#nN@7c)4EovNr$!^zX7+=1{Ce zfL`<+d$Vb4`k72&00>lVwn zZz6ouG&4^`r*mlq0)Q{O1pln0RpD~kVJ+hqfX&p+F$Jh9U@z*z>%gyj8fbkKdfmIR zBa}TdlozbsWC?Q`?3tCJ~)aM#QpT4PdX)k|Msf{ z)`#g!w9Y6+hPgTSh0;+*v7FqMktreM_hHSA^jO4wLSfDV`1-F1V>Iy*2DfNRA(Q6n zk22q;p8IhFBh#%}!a^nd8;vvK!aRDbxVZ7NE+pp8Z`DhfP zPkGg=&?lOaTD^_a#V{`@ zA%-y&t$kEBNgoszC-2- zlpfCkC{BO8K_W-8e44~U`Jcu`xBlFHg)|vOe%ueg(u9r*@A9$moMUu}aXJ3l^a0#h zf$RgoW&{LidfVgV>u-^eRy2Vw{Uia}k~m~(J$>qD0sSzIskx$u^=Fy)t3aygoLV6N z&7ywyYz$kI)Y~KLoj*AIjH1Rt^-5I-H z_Y^hg=Tf$B8!`_k0@bRNahgcb3p+z=BgvsoTLdU=WcYs!E5 zBF$cH;Dmk~4AQLQZNNJAROvzz(R^audPE8Jv`W&s=LPC#Nji~$F;XOXLCHE1(>9g(-{*u=vb@;w z5WPKx?!U|xakOCN_#o}bVrWF0L83tlKAG77Iq8d%9{{k8)C!60o}~8;wn2AG>K-5n zNYfxK2|`!F*eKG%Q0k}c`PH|J1F%A9gq0v+#m{D*NuZu%QY(U=S)+3P^jifJbf zEjSJ2kSR_c+U4&YYiA*_As8%qh=f%E_apz;H>6S>bPE9ORQPv6#vVO1NfUrYZrCp;}-!$-NtMf!SU0Cp-;RwI;HHYgj@2_abX9BxwQ~#$^}| zwV;Y1;eOp|_wTlXpVW3lEEoC`Iu$s7e3*$R^V`o=#C>hHe9yF0WJ-#vc2^6`v)_Xdw+1TlncWI5I5U zrkH%4I}mfDP&~T|_y^~wCu0_b4XQR6#Wpwbw+#@Z&5n$RMGxYiZ%LX235?Cei(FI&gjoY3oJ{0zdQ)(8)|4D%9UFP$|8t1paqnW)- zkT9^2A~LEYUvWTG-C>1vIbZ#lgoGt81QC^L6kS-z@jmxcA}CIH$5mbjn}I}2!?Rz1 z0l>J9QBJ?`!NEQa;zOty2f2Mejbp3X6*`b+e57Jo@;Rb(@$tWYkrl=_Rw-f^nak-5 z!IR5DbHOTmU!o7-a=(m1?cJS%Ah1Ht3rR7x$A`vl8d@V6fDEdImpUE`#3KN_E}HxU zIO+-Cv{VbL7{W_IidubOJareU9f|z;YGavMtQc$i?(~NbhwD8DsvPzRA^y!3cUAMV zVT`_46=_=lE3bqGE6wN-bd%qMad*JGG_oP^a8lXQC zsv55>8)vF0)v0Z>Un*}qk+msitSGC-CLl{7lbRRl2VvqWGf%MCjqDn1kG>GdSj(*7IGA0$}3?CQ$MVA~kLBRs!!yEj= zbjC68D0AP!$28Z63b_5=c&V1DqxS8KvBl6K+S$(m2j%7OF-shHI!)Iyg;VF2=VKux z7dsKn8Ok{jG`$#U9cTry7dX){fkSeU%_jd|9X8**erm^FY&Nv4w2h6H+{#Tm&I-yEik{24q2A1Qp$Z-xkeca z&^(=>Je*i2{8nj!M#fwXs%6)e`lO(q8kdb3BjbW{*nu_Y%ps-tBk^L9PrWg)sIsOy z)sB-tGNO&0`Qz|!^8I~U_-#4KRvPJ4fPg_5*eG}^p3E34^s?)K^YuykVtM4#4ohbH znKB-6O(1X^69P)sYwAx!&nAo~2zLo2(lf*WaYtMD2jJB>;0kKp=Nitw!Qj!m`)ln5 zNUI{tVy8pq-p>iF8~pKFrwXs&Fi#qC#j$QaoUvTxRH1HcQxIuurW0^)PyX4S*x6WX zu2s~&W^w^u^TQcNwI+=Mwuo3cZ;*A=RtH&INKggV#hh%qDh>ak7Ah;PdW`j7Yklo_ zW4?4E^&br%^n0E==&(z^PFSl^fe{)woKh^BSf|qM)=36G?-|#oO8kh%2uVVHlvacm z)eugk`n0L$t3g?BxiMk;B%&$ly7qHzq6((oM*x88-O)Q=CU;8`1x5T3$k_BkVo)@B z`oTuSy$^*^CNk0>$3TynwkJBD=C<>@4)VpEY2dv?8#n3mP_S2&guBDb$X$fB?oPax z3T{~nMwciy0I4ukS&eUb8cfE>BnfTZp2d8Id{;fGAPMp3sRi(QlGdUK03Th4M+;y& z5a0_9#$~*QKF?^?DQ}TX(I!%UkE3WsP}NH{BJmU3XSDUB!6J>DK!WmzjE6~bIncjw zoUx_Zfu#S47dgwmCq>2&GroZ6O#{lDy3RY7fg_|sAvKNJFTmZtL zy%*ObMfp>)kj&-AFz$QW!5W?VUaLb_ZnqdNvwk=o3&7dl;68kZ45y+89J4AHM_H0Z zVisgDM863S2)1X;l&2gBiH9Y?LylEBHlL2(Vxuy**{53 zs#U~?*l=Qk4G4f~Og@&l?LKziRSTy#3ED;F?C3`RN;q+EM>Gq7mSzMTIK5Nxc@SN-CBKped$(dG);*Xk3xBrXhK=FB+BpjW-+wk6#0dc4F{V3 zh|F04%4w%^Wl5yTLd`v~PR+2yx+iLTK%dgyTUPep+hkA$3Rn2d?(QE-EJ+XS*GkO7 zRi*pwp=ZiKG0@YtU4;K&-Z3|fN`@1&PsX|X@y@2k@jX?5o4Dau+8UGoZHfYxS%C|{5|JA&J z?imLwtI(Bl0czK)kNS6CR37U;&N0qB7)m?k$N=3~=tHwNj@WQ2O$r+8+EMPlZj2+Kz|=#fDsa@!+O5mcx9tt>e+3taeVT zc?#&LA;kfvRL(hNr%vV9h|}{&VuP3Kxpdf=%MQ2o<5eZ%xc#ci@vhF?!_GmDAIXao zdRcnf%8{_X|IR%#s0Iae_E`S`2wPFlB5I(%Ym4YP5wLkE(hA+Kn%y~e6#V56U`lkm z7D}z0uXt-$B2<+>*KHpQ!)(WXQ5GU91HEGAW9KFn+I8Sl|XDDAUP$xM| z)Km`@Z~!4ZeHQCHua{vPxIHZ^wth8HoBzDCOEerPx)Qk3;`Z^ppt|pV#jsn@9fvz{ zMsjxv&$(~%eX50|S5H*T;i2Xvm_;+GPfSfmi{_k7?fL5IKN$pnvXU~RF%8c6`9XX7 z=RLj+^ZJ&Fr|*(rB1pV`3&K2~q)EzFSkwI{ab(Lclz@04{JEPy0&6XP1<>PGl;)1h zQ;b*DU8nbI6%{+CzAtPNfLgXf*;V*J9ikt#UoVMb0qJUHDY7wmrGiJ#2OjcF4vP)H z976#emB;l-ZYx6g^~kZ`O}zh^4X!4HHVHZ`bISW{aoT#$_yU!Hm$&1qMsk}iA0^b7 zgDcyGm8RWw?RRJE1sabE-`AS;nv54Wo+B-QoZvDiO!{)`n}1Yd!Kb*UzFSWY^KF9( zkNS=B+eEdOdDK*y-VaxSX{}FA68r-WB6b7wmSqsFdAzH|a0Lg01JPicXB0 zGBJcVc`NQ#wYfiUcN0PzUUp&_bz0X~Z_gsu|3Z_nO@Hrxiy5`a^tWugvfy7G+78djQMbsYh+5V?G%6WTIU6ju?q=I-Hh{w0!vAj0u87^{ z2J_0Ldh2v}&yGYNG=E3p=5t+qwL|NqCiLoA?0$1XYki!xQ26+lpu{*rK;!3~_n^Wj zlib}mf)=kGWa-AJltyR+xh5J2-D;tFF$F`UYYas*C!Bi@SkE(AZMu+|WsL5d&{sSk zFmr->XeQQr8QUoT0+VLW(UW1s8^)xk;yYMD?*R0;=V@;cv*8#fcrlCr}7uLDqY}iHa(N* zM3R1j>jUXwz?SA5EbMR)h}&MZ4)*@&`RKH3AJl2KzV37&>bT18SA01+AeMWlI0bn8 zE|IvvQ3-S#$ru7!s-S98q|TI^-TFa=t8vW$={0O~ziok+KOS5&D32I5oo{$NIqU0M ze?WzgGb!23e87UQGafFq>5ojNJ0h-H@1t_Qj5T`BuU;+KcRa-(92LS$e-+~uWh9B& z^0uUbf)V3`LPYmkj!}(<2qjZrAs-%(u#{9b@9vV{)K*Jd%Z6h1@;sn-abrF?xx-{3 z;n9s>b$X3wV*X_`2z+ErXYY6i;}#S|5-`WDBGqBiQ#%H)h1`a2aj_x8Hgon@T%NpUl^f_k-WQ z=Moe-c;r~uj!7n{``a47mI)k3(5q}d3O;S5BRm$pek7(jblmP78G=v19t-HOmkR5J zs_JSvu0z<7II>8kkzb%i0v}X_CqldEK;*fistnqZZ2j)Xu?bKw%@<3ZVk0mEiZfUY zTy5SKkUtUGy_lXS>bCEPesT2xKdQOR%J?mz$aurYzVTix5YcITDYDp1I@Ur}GL8JF zXu9S?=nO*H6&D;NNuy~_DoYOv$s!sx_c+Uw9!8`0@)J)RjkNiA(C2BTG5>kt000CH zkEEzA?d3n`p)&BeX;$1yHXSCgh?uY#pu3m8SEgWD*z@GR$D=a!2JTFl9g~SE&yRoKGuIGgE7+~IOZSQuk&;UZoBXTwo!x;9eAEa=O?fX=f!zo~OS-v=*w!8A%f@kg4kM)Wudfd-n#;2dw!g0;&uWzA_*YBv(Jzg>pt{NAqR1%9=-G7F%w* zw9uM4ullNX?5nZ}2#%YAR$6db_t-?rf&=^gnOpoc`YLhH0-Huen!m03;cC9*(AQJpzA-`F0K9aHd!)MMEF9X^A`kkBe!@njuBSX$Jy+Rq9P^Wp@L$cK z%lN@>*C+{EMj`En^kJZ-kZp$ftgf=E9PX)rPSW2&@saZ$t~PMl4EAEirq2Kh?0I8_ z!~T)Zm5=MjXm?*Lr(MTWgpNb^C9YQtKE9;{%J#?1yQJXFLWSkYO4q&n`op5~!oreb zCi^#w$_GLY(^r2Tx03~6Z+_90LbgAqOYm-q$K%w9L1&wX{c8Sd-{81V7ROa9yo1_q zc|0xOMGK?CgY-}LA?n*hX5E0((w#ubbN4LoOjl?7=C8%o!NFM_{g2^Zt>Goh3f6zGS}aDY&k*f>k#m|*|xvJ zy_LGn>Rid|sO%VAhu%Vqi+e?_7zu;7o&c4V<-gB5PZGA?EmtFw)U^fq*XzAXKHn4R zeA(-!VVbJr;j-haY;aU^%$C{fzp;xOjwFX}q0O~!x139XHaDcY_b~#M)3eDU()j#N z|KeQmAkXu}`1X)XB5?G;paEYDL4;OH2_&pJ=!*?asBRn+Fq;P}9kQ+1#|U>Bm-~GS zM50dc<02!e!J!<9PNx(ZK}$+JcC_DHKgPXXoCGP6St>3^ShMq&YR0^~^~pIs9o^vR z#iHxy~oQ%-}V02JyfWrFhXdgK%GWZ z@A{e-XS-T$_;xAaw6((8s{vHsAZjE$u+nWeIxxmN+#pE&)uzxfQI!e zuP>4{LVVAuf7qfuAN&s9wm05-3Yg)v_J>%8Oi+luo`b1jNljw7?bf1Jxeu%vh5og; z*tfl$SG~BRnhIwVa$Qfze*7^#856xGjzZF9)MkGjb~|deiG1v6xp^CRFnDy39kxc9qh(PNgR%eeBHT5W0=v@p+GwRaCrgt1#y{?YMFGX#757 zS7ZO3(u=zBK=8LD^EDJKC}BW)`bf<8HWsOPvRF_*2OTEoGse@Mb2D3#H%L~c)AZ{t zyTYA_t%q~>?4M4FuDY_;e344~C*`fr1V4TIlofIBv-t%rH~9&VYd$5ywbRSfZ-%FG zWfB#m;^7VUm2Xh*Z_S-`h-)UR*9h*s>0PW+r3zG#|pg6d_ z^_aI*yz#{{zrK1^hzqs_NDC`rfegy`zpu599<(>3dy++bUn9^6z|TOOlgo^~!1$e) zpUd>-r+tiRy7xr<+r^I07QAU1g^~&EZFD(emR^aa-xT7f*IpsnbeP_mOh5D_{5Z6k zk~Wn_LtX9KX6zQyXu{+DXRD>PYUlegnD-hj^SRV)H-8!t3B22MAk2jc?~TB?Y#*K) zu@7g5Ozzfq!+B8RZQEeAE|dTq!|9L%$BBX2BOptD$1C(@@Fh~*8AF(kU)#z2Q*%J> zaohHXD_ZkP^~9U))~JPno<_;A6g-$O{Y#*AxX5UgI7MJDCFP*wUY)@@AFKCWa$+K( z=f6~6-N#5Ovz8pws=?LRXz+H8fDo5#0C5Qi*yHHHL;^)wl5!)M{%<$~eDbFI=1#HA zR4)UwW&@JWh#_}~U=;MP{Aq=`QwhD06wD3NuN(TBS4(+I9UlCmxM1wU@7DC7&VP_M1VX6RQ1m$_h z{CX16hTPy=GF5~dZT-^*!#teG4^g6#mcG2EszL}HoRrdC9EvvtS0}J6xAJ&#p+neN zwH^esDm?JFqbnGmj^_qkLVy3H2cs*jp~FkJdmnPEGL2N71MWppX)M*dSZ`|bBHkc; z+^x7eycdcYu5)skNRykVGTUr-M=C(PutaJRf52_whMy-Au3e64zvu4kH-H#$B za#?|##hpIAiNI8R-MqoBFv9py!qUe1i;Q#%s#jDsHkIC=41D~a_P0^ZcHaJduQhif z3s)9~uwcdP*w0tj7xIvG7Q4J13(M=aK=d?K1Q#sULL(+2n1Sd@qoY#KeOD zKP2{*HG^v&la|8sX*6X(y9GT51);AEJO5W7xHLcp9?H`Y_DkpI32x(eKZf@kXfbqz zJ~4PbVKhbrr~oeu{ZyjIxpT{JBuRut<96_f}DKVqR49jpEl~m-3@YqaRj)=RwRnxws$M zrg_jzBsU)S*>;X7%9(*M3Q&V#oMsqx?C|1npbzHdjHAE^KRmrBc!Y683{{tdf^XUo z2eGO#5?#qI?=_+j$~~|voi?y$%1^TstG~(XQihD;2T~Xf+?IW_ zKh`&6ccF_e_@3KC_eRheLcg=Tgazh*o{2vCb+fvB?zaC8*WkL@k>H1WdWwN7n>(?3 zz%F-N$E&}EefOhNM$2sQcgmK9!l3s|J{8w@ zJ5Jw9fLBuEFOi=}y%VKH`k@AwOO}ig*mmS^af@U<0$~X)Fs1xAgSN>fBG7BBwR>3LU!Kie`}|CRQ05GDFEJAzvRCH}pWRR;BG6;J zXnEsf=T0jRU1#|-L9owJFNFX3%@aGkEz5(wTgFO-+b`LPy}bh(tfsWKD$RsYgA zII_Tl_{Wkh);@!tTk3evp?^>nZCfnSD2jeD#yLnn|1Z7-k<|X1SY?6K3V3{T&2};U zSrdkW-3TiJZlld^G>FHZ${y%|%wz|)Zm<;{B|TYOJ!}YyU6PNeS#Q5s1~)?+n6lHt zJz~N0xm-A0PEnuzx;-l|OPHBPN1-Ix^}R3e`)hjsyU!&`U9?)?RBcG zTRbE{I}<%n!}m3k*wSJ6!EPvuh|RuF#~%+ac{_Hh>m~e%O7x>hWBXz1ws}W18iU;m zuvD)ztmT4}PrnSLhyEq@mrp-~{eWP*yX8+CjGoZzEEmwIT5E4NuterX2%%osDDLM2 z!F^5WuvJ*TR?=blP_E;z7!iLe52asXr}aFDu{_?))9#I%5vq_bJ$UhnAa4VHR6QAvmoT%afqxt$u83J7V4zgV(02 zWFw2L(G~J=o?D#Gj|Z$`^y|IiDWRDumqe+?i6$j_B{WVeqq`Xr?Vxz>V+}qYu^`bn z=OBb&A`a(g_AApDy7gd)IA0osy=|n5_ae^bH$(AV=7f`3fcr2S2`|!zn5a(d5$$3(AgoQ^*b}27J=Y9FDntw643<78G79!MKQ7wVezKSH$mF2?f2l ze{!g;$!~AYw2Q9LZf=Z9XogEh^mfVv_ZUgPch{*8?|?^$(Ht2mb?2**x^+rAOlSajgj>0_ry`k7c_kULh;ZB$m>bFea+jPAoxlG-)pJJU4& zc+C=DjY)ocU@?gJy9P(>kG6zhN{$T^x5fej5JA{f=R3LC8Kv`mBJxs5LV48wyB8rx5Q7rCAv zSI?jA3q^dWZkyYQ`=6ncP{%L2u#Y?!!5?e*tF`mnCi`e3y=E`pVeK#}cD^J=>%ggk zsn0`=4D;@{fN1$abn!!j-RFRfeZ*Lui(ZOyoYpnbx-dAk8an@Rb_`z5Sh0mI{4hYe zpHeF33082m-vl!%7bHy?Lb#Ltvi~FZlIQajM!5ILECkwk7MoS!PK&`!y1=?2gOXwZ zA$(VS0nkRdQM(9@u{)y$pZSBwTxVndICkkBE)W^A)l!UJH{GCN2vu^J6m5rQZ??Dx z{n^*+4a0%vt1F|@C=hN)WLGEcYZCgoLlihmu|rc8Q*t;E)*jhE_?(#Cu)>f#E;urI zcKy2V)hfz6bG6|;5Z2bs*PY*<03o_X^!L`uHo_wGTw}exc7gf1K-HVZ*3;jSpF?7s zAf%fWVmSFK9hD7*_!?<@ z5r#qnNHEI^&zQ#y7{`sQeoEQ)jES|;y`kR!J zi819LvuTH?4PiIIfA<8Idr2c*-iMWrk58gS zidK5fk{oVT)Iv{EYAtISN@PLaHbbY>Suo?~MJ5|)k-GeMlAl*97>l~l7|puH1;zm) zUst&*?(-Zy*s5RKe!giy=V(!qD16ADc=Xw^Y@-sb89Z^MAvc$j3A;Xk1RIrQmyYvp z?Mgosh!g))4Kn^g2`c|#@ADX3U9ZAIpfI*E3DF*Dqq0v+nr#8yrlq3Q~hmEsi2?B+>Q3E}_y7J#Ew*z$~^L$7h8%A^9tu0qz@P6IBJ zE6htsyt%v+9gjo+!F%O9$HO8L2@(#XN(av9br))ksRlNsir-qS92RS;J3CBMklk^g z9q$qNg{oC@*l7R%xr{R0)xCYm3(S0jAG>~ zcgw9K5MsO>;2C{Ih_vpt`!F<*^#|iM@oeD|Ly%XlNN}bj90oH9g5`oneCbEL0jQvMj}W(k{CNB zXK?G%-12%iAJP)ny+QtWHoBn{tyxp~(Z{p5H5u@S-#P5+zuc2+|4GLkgp+{IoJi+eIpdf zNzx^kH~u3D#F$?ag_D;%o*0pmINM094Tk1dSKs#cdme?OE<5l0p?UWaJO1!Hhu?h1 z|6q0Sf9NIR79quH{McTE4bF0OG>=Yg-M70KL3(AyE$)E@$vQkN5E?xK7P2l~zS6hw zvv05N-1|$`mG+_{o&ckhB&s#ErN;q!-Yi<%MHc1#&m+$ty8meH>(jIAR(-l*M;SWn!k!3f3H` z_3UDeA3|iNO7hV1S{gCm*#q~zxv*LirDscg(r-n_Zrl9I*H_8)jKA2=_m|*=z4Wrd6}5j}dEKecEx-4W4jR$EQ$LMZX}D}u>q8&X%@OQG zq0G47lJvX^krk`T ze|F)Yl^&A<^Ke42tOW7Q#kQO`B#CQ{6jd4QH$6a|!Q#EB8mD1sQNlb_i?EACepK?U zdUwO)`3slu`yPF5+qUk%bx$uTg9n^6yCsIH$Go72uK5dat87$8?mXzh$6wGcwEpSW zr+w?|`)l{)X`k$UHpA7n|NGXVJ-bJDw&Q(wuuzl|qF9;g zhv4>rsF6yq$`o2&O9MuYE06)<^#OD%e~=QYK!kyiD(Of?qiUj^>SZaY0)?tlp-3US z7ATkLfvPB2(HmG4d1(%|U{vKG5HUd@TY=zu&XN6iXwXRe2ed0WZUCMO6m}6oKpol= z+B472s=DR<@`mjnH>>&3N00U}X+fAmAC+xm2Fx7CGWM zl0{}KQ)a>(yt%{ENE-~sC!kv1u>B4DKkz~Yhmpc9a>iBK`;ZaB5R_BkaYycR{MYtT`}ZBQ^N&tA!gOYET^lN2n)qI5#O2p- zeSJTT96+2To&4e*lV7@H%1d_w;`}2F4#bMkXtA7-e^F7=z>ug$bZ+cX1278mK2iH) z-39!gpplp@s?XN{^nz>gL`vwz&H1=AT+CPAb9IR2p;M52>sKi-3j9r&sQYfV;WWr~ zSDYzgDC&=woo4S$q#ocXY{J}x&r?cnt$#*YA1%;}U=_RXnK>UGHAz6f7K43$m z@{gROR4deMKuGfch-ib-ga{(y&43-u?1U)n5EvL~Sz$Rr5)le@C9o(meokdIUbcvC z2m=fVWsB(o&l!$)H%h`K8~Y$mA?2!cWzQ6yvr8+trd4N9(F6i31gC)R4()aPqne)HxA^zvf z&zShs%`ZK2!#y|s64^F%?k-M%885xI=So66C zTi!glcsJqyBpD~$N0I3hw*#=4W{2ujp%R&tLls=|>8$yeU2~fiGIVhNvraiK;b>v5=S*8>@7;HL`LP?Wx%AZW4_y}yZpBeuq?+jR6f0#&TQ%U)YGzZ{p6 zqyiWaZ4lKk8wC$^pZ&rrx(X%Vnh9p_(4q8zt(wwJYxDmBeVgsmi`!4iR2YPFPMoff zmhrb3VFN@eOxPJ!3)|m-|H-a@>R7;{%%5-l&+{+8MZ-+NRfvK8``G7pv=f&&FaOgW z8b!_+!=)&W4efbPZ9Myzf6)=))|1Pwxjiu9{>tNz-1q8Vor=d=>@CoD{Od^`A?h## zH4tmRZD)_02n>pUpj4O;A`5}N@j~*ebY!-KqzhIIPde8jP zvBL)UCu1&eB7PFGvzgc5ntI8Vf3wZ~x9)Yqu?N}5CDD7upYOt><+NYoXT~RUEX%i! z-tVthoxT6qUD5lKpZ@{3Lxx^FdXLiuICB!dsL+Ko$j`p;CWezV81@JEvfGq+{=wE2 zPc)ithd%uHOO#1J&{VILL)&GDrEuMeOj!GDygnz<8 z=&hI5crZa0<+1qHDu^y#;gvq2XaJ}X)g`LkfT3C~QBg&id#6Q*Bn`+rZ+I*GETS7l zVf#-Vf*UU(rhxx^`=mwHcTBBlK%H}fxBsPi)%Smhcbluj1X77=iHSLVDDN($30jm! z8PN))D1%25uiC@i7wzff(-I5IebXvj+T3LRr>)F$4g$eZ*tdRk=`Fn4u$FSqf1f$> zgkMzG;?*D$U6c|liTi;n_=}->{ofwIa~+|Yi(q~J;j}drf6ko>_>;PmIqk zL|HOLnfkgqoc)XuWHNaE{oN@uI4K%G3KRa{H1S_d^Y9;k@RP!@|4!N?WPzR`CF_~~ zAi|nQ7&Vyl_>Kgt$0!(>`sQmNE^h zClv}K0Yr6ZT6}DO*-nB48r3XCXpgjDMA@PXC#>S@6P3ghMpd+s-kT)$Up8@dh2G5M zL`kn`7RioyWY@A`o#bK~*bi)qQ^S@hm*1^35Op z3h$9nXWEl}#6_=~U0xA=>B3=a)>P&7fBBl|I}+atmGY7?ymK?ChQZaEnOS{KXeterq5Af{pee`;Nj85`)s=h-Y{!LSTV86%|z> z{_*W{)+N{D0!tuJ1P>s>nZNzh9l?G@o*Do8xxcz0&z?@=2RE4?b<&0ZdT5-Targ)F zZ@u%8Bfk3!To>{tMc$yFD|jmvy3|*(vA`i{g$I28!JE|+*5dX_wkWf6EM(F13Hwo% zKeju1A3t0IRFp?f@V_8pjMJ4V>b%`yY!yw8j+Fw%Pb9m8GT=dXf)7?I3)>BmK{=8@ zW93^>p616xT3kA+H6@`~11z!N|Nf}*C*$1RJW;2Yf5xyD7wU;W-y$?dqM#4YsQkgR zD`_E%G1{Pc?LU8)mS^qUk`^XPj4^Z9hm}102^&!S7y}0+oWD!XsZ8yT%fX&k4*3JC zM`AJ!k3nO4W8UyW@EMb1+ATe9-RHA_op6D&uD%|(F5}TRJZfg|4RjMN<30D(Z=QSF zH-oIfdz`Rn>2lm=ylp>Rn(csdVIO}wmx)GIkj@w_Xpiolpbl9EPZ@kXb2eW^ZtV%8 zfi%v3qsuZI+{!n>VofvD3B5`CeHgDp{Ktf&60^t1Nz(axQBk+f?R$3bf;%oTL30-@ zLCRyFG0l_mv$UkRch9c4WgXuKcyH4OQ$Jp{TD~Maee-5b@D4qkn+5@n$ICGV>S`+m z_`3TaC#i4#{kcb&r_^!kdH2J9@-fZ{VAPc9%?9r6VY@9cy@tr3G25&^<8n$_b$P(= zXxL(%Zlt0eMiEN!pS++1DRLDzHUg{I%2Y`1zhfP*w zfH7E~I}x{u7&&4;WPMj3Sma2tT|@(*8ZO^Iz>A9N>#|qADF@LI$X=sj zZT8>r&YJ%l+@h`iR=8z!qZ5Id%mKLeP00ij^a9zEJ#JugoI{Wf4+_Yssn1^f77o)s z5C78?YlihhQBieu?aW#8@fV}X@jrV-kiY=w`Kqd#_b1PIKl8D3N`vt;citlWarz`U zh)}XZhz;#P(I^njh3(HzW97u;I~zHG&nx~#SAjSzG{(67^A|3&x1duXN&)+ExG?k6 zxj5hz8qrH1`GIFM*tFsWol@73-U$A`;^I@rjNB2~JiMQn8Z()D|NAVnWHi6t4D=PS zU*=uS22BCKIe}t-Y<^?$!)uF(PzEV?u!@G!f>f>`3uLbl1F~rP03;HeD1H=5(onHL zAe=~nfsuV2xPt+ahM-QZQiwy^t0(z0fNCvudF^V9*LB9w7UvGv$6%scDEvYne1@m(H52cTmF^7a1B z|1<^<>Pn9eGce{DaNFdH&u~V9R*5UbVmCHwSs(k!L}^a9wap3SM*}oIXRS4UL=M3g zYQLKfAA#aDI!4Rp@Bhuu{%yDJi93xFSrYF+xk_%a(6ICYBVnVFvx(z}`2yzzP%yU& z{n|ngP(IABP!Q5+3~+Umgo~M&KqB(x&Ge^)5xJu>%T1B%YyYhulvnD*Y~miV z5X(tS)luOU8`(^Cy|ayjm09@S_RasYS8KeNC|Rr=XMA?an!0P=C^bY*DpuYX8TKP@f8<_H~J$>7UGb)4#wGzdVDB8(WtBZUX$o;4`&fJ)kf+1c+U~ z=+!DcmoI)075JriTpB`1%=l#Pv>Bh>b^p`RnQmnvb5yKe^iu_wltXN<;@TDQd(2W= z8XDYbyF+m>=_%bOzTLSy5N(R+cdsQYICx8BaBTUYNO#VPsNTltGGgFm%DG_ImOmSY z+iPICxWrmxskOf-^Q+fZ-|#NaL+{hO$-M{R)iTN;9>pFx$NMKgxAMV{qYHLmTo3!V zZc{ShnD%A(bq24^8>|k`{t#MbAVIK-x2WB?ck9vJ1TidCOblc?Na?yBWa)!7SSd6@D%nS?LO>Est>y)VY>{Jmz<{6; zMMD8FAlj~qu6m>V4ZK%^o$^Nph7OBT0OAjA0?!)Oq8VROoYiLs+3pyXopoU*Tvk>8 z=*-G65NW67B6}U|pSiN`H?OZ2!7>P^)Tw@GM&&~@+zlK!8!-Os9Y%L6qZu?I4?WEY zW6Wk}udc%ta~-7^#(tDwc0ja!J?y76ag!om4nK4CYOkY-&FSVCKdJrj03syU4X3V^6_U2@++ZEo1BO*`79W+(z!uvFF7 zpY-&K75t`P0iD8v44y#9W5DtTQ9xjd!r&kkuJa6N)WQJDR7lJe3<@CasHQcl=@u}UCxOuH*u(2huA1!gtA@GouFXuLWNGOFqjqI+Qn>Ik^UCW_cxu&yhqP^mLCCU}aX_bMCADfK?A5ZUZ<}IX@8%eM z4{3byLjL=4rI>{FjbQ(6Qz{>tS#{*rO?T{Aio4*auc-as%&HkH>N+(qK5JO3leTYK zhHbqwNv-wv;M1GLCQpOU(_dKi?h<@SsCi$E|HVr6BOu~|eitY`9&EPpliHugkHkR! zHSUuLV;eDy-)!OsDvD?l)TlshNvCvY;urmRjLpqD3^^aZQV~e`9Thhb9#l# z@v2go?m^#*j6RFF9U;G9_$wcTHaV#eI~B|&>;O1 z$iJq?&Bh%&k=Yy3ei%3wKbteK=|O#($=yF$Uqg67!*-mdaRvmo7JT>I8w&DeQwyZ z6|XNBC{NF>{&Z#C&YkeG47D~upN7cDzUSxYui{Wt+z;z%Ki&&GqHEcqTQ$MUin=!E z>zbH`odbMAr1rIz>~G#EzxJK*u7u6S|G9G%_ZtIjbB>?Xe!Hd+^)gu-E&dVKHn&(^ z8W>xI|DzqH9e`#5`%I3V~p^vLpRMy|NU)z>EwsuK~l)}DnP5pT#Z_`S zsi8i0-*Mpig8eU}Q~LpB1Rib?@srvw9`Nh#b6>xkzyFyy@+C4!aGGY|J->vx0(9y6 zW#E>%3^W2+#bx6}4GhcF$&u@xzb!x$V|XiaItc#%{59>(Z@S7j4_Fv|roOQYJK&tBBfc zAe+4#%!x&@TFKlo_=<}9lgF*T>T$Uh-R*>hmGeoayFm%N$uCAMd+jN^IX51 zx&Lt$#GTNRL}v1r_f{;C#dd|!V$h7H+e@3^G!6J<5DAZ_B#}(b4wUodmyw;YSZcEi zFO!|RtZwfIm*EDWG2P2LG%Z?GnSEhy&G@-I=ZMn3tsALG@W;GH0=@&@S;Q=ffwXUu z`Qsj~59#agC9w;Tktc%xMbq z#j~0CUq+zAjOKa^il5Ye#Ml?Oem7_T^BkpL^gcD}f|PYAUC|hq#O6nP+1s1FV5~ERjgVk~SQG0q;^)s_@q=>tS&ob(Ho32NNT^Eu*fIsNrgI{Zc-DWD6aEljw$uO zbLYm3|NQ6d;u9k~Bb^UD7RDwtUW`31KXE~8@racv^p3=24!f>7QWZ-vwxX?GtkV+z zu)5sNE}QMy0w)SJ%2Z;UM{2me5n3-8-XfMI^ALt66__Jd-Q}PVD+!JFJ56VdbmUz1 zo>N|T*30G3&6cN+{H`B+U1q~A)Bp6sW^aFKNeSN7m^XgZ{*vNiy#CUCC&|QeSUy65 zBdl2s@#zsOQ|Q+c|M>kGyn)dL7CwYWY%t2a0f|qmE5yI>GJZ-+OYy=`I|P-jmToNj zt;ri@{RaELkoYfY(P`U+my=apmVg0q6xJ2S)w+^3M}Q^Y!gh^$X!6SpngfzFvVpqi zIdNdKer;uwXq7~Bh}O)_lPedjsk?RWR(N-Yk4=dmGrq#c8zW*WOuGnU(B~H4h=c!I zKB)ZuxYZwGdyC@jj}XUebr}0TU;lS#*Ls&>gLWS=q*cr2_*H1UMmIUK&D(x!?P2@x zcIkOP!21{Q-tVa0*x#dDr$1kQ=FUR~j+-#arz?g{EL1kY|3CQlVMiaf*Q9r+?ziWz zu%x02Z+oOA#spC&p@JVB`{@sl!C4o)GLo%@N}Rbz_s)I@Z+f%A`Gv(#A*-YIxc$x- z?EYNiUv`u(VR_XwV(pHT?LFu?X<{-C<)p|gO0%iPB;xy)qX$$DGkC*n0*+t4_?C3! zddw&lLt{;jY4bFAhY$@#5oyjs>uo;mQ-|kgnwQE&YR^a(WpwN&`&*iXTs4~N3ei(= zR7Ci5b83D$am`ewNVN=~vV{ahF`_mH|Nr1Shaa%l2=5(QdhdhJ{P)rEF&A&f_Q!pj z>CnCn9Ne*e8<~_m{>M+4;ho<4D_Wbv|Gj#49ni0L>(;HjC2eUT{3{Wgi2rU~I^y>s z-km%{X=2f5D{=p~Y>pu`#Sk`y{W9+iQ##P-&1-*x#+tkt#*h98S8`zPYOJO{rZa!I z7aKg+ze{$C$fVlE9CH>O;}VWV3em^f!5}t@i2RAe>dI?B-#4xn#?;G+~6dmsW_$AyT|p45=IejCbu#(89WYj#ak6G&66FOxSXRv*H1u$BZ8K&7<~Px^(6ESKgvCHgEf#Z!vxJFvH;P|2#eJ#W&@> zhjQ?mWB*4pW}SN8A8M-kYoLt$O%eZU$^B1Z(>ibdVrLr~^Ta=0%#il>6VYgd8^?zLNKMVXZ~_4ok^ z7j4#HXSuT=8i*N_IK+!h@P^%`ZI=+q)@PrZU32{hmAHSiVf*O}s@p95UsPQDwS&jN znmhjeGij!j2S(&Nm=QHSKoc;i5HhHTgg$g zaPUJ1Z+rYv`{8$5@UvF<376aNd2IQr@`Lvsb+|D<%4{q6_T|900SFnRcp0Vf=H@YBz~ z{>alW4;|d^_@nmk-LngRLE(d`GjG4=(d8@gQzd!I$l-&JJ#wFipP2CMi*KBFy13=- zDPu;7TV8wbp~qfWx&m)hk>HcCA2eXAADwU{(BJ*(j&5B#;8%Qm_v$KI#6{GiYb4XI zb<2y-K8ZuP>0dfWQ>J~ob>HrA={p}xhmjvl`B(%ydgwUppgs24V>rYvUB2qAcc=a1 zzQ>VgL&C~qy=&6NA`n1~WshBP>#}}@_X~@Ec`_$wU>p~|ZKPcX2-r|jQiY%kh~?20 zu2Lz~E%@-=3|eXSM;Okvsu>d5N!h%Gh|tl8BXXxzX!6{kHD4 zeC4Wl-k*NNL1Vsi%mMg;olj@a-?~rtAAj#ioY{P7;=4#^J-c`6(QVAaMN2!hYjf~^ zqww>#cm4ZG=bUU7zZHJL&rU$~?f0kk=-O$w5j*1N%&xrduFjp>Ly`V?00N^&*N!l+ zb*q**i+9Z7`>ZZs^VWOQ25+~`H;>*AiER8UZ$lA&nCu&0+k3@|)i?>X>#*(m^z3^6 zWq<9|p)DA3a0>r_D@x8C``s>&+32=>48 z-n8!BI*s-&@^h4d$HEXWN5Jv7Z2791+PdLG2lndG<@EFa-~t@hWHMuS8}{9A9EhKZ zL~iZVyW0VK?+Sn71PfzWgj)O2z^X77S_4V|2rAMydi^@7c|RzOvRny>Z{VjItP4;K zT_^i-szU6w5`BXkFdQNSDy%E!6LFkd=J*K)IznBiLR2V5;$^bI`J9bB+;KG0nlmsZ zAVjhB(x5;ED5P*;q{pRYbH|VR)wqvOTH^Xf=bPK57&z zt%RbYRn_%R&aVCM$5ptZ&6_rr%ZLG5!2UvWEeu}H=i~p*9dK7_2APhgs=1j=Rdx0J zg-fvTunW^>>_N`I^oHfDR-ba>QTvS9dG9@jBT?d-2~I!!`uDd@o&MSAU3NI@$H(oz z_pV5kDo4+9?lNpU?8`>%H0aKMKM~@m-&Q^0`_JYq_`_fC!%tlwy5DI0@?!9nT4Lh| zDF*(1di?9yFb?e3XUO1wm(T-`KELa*LFb?bBKFT_VdCVFn*~^aZ5dDbqhy?b{9LOKDzau#^hydW164h+;)Z>K6EV ztzQbuC(;`u@;6cZ<9DTfA~T~men6IC)|0C*w30&=SkNH!igSd@0J_W_nG%Cy>9q1= zGG$W*&yz|?1**Zy9ZSS(v%j8+FsCqxU=1O5*Q)5gkE(`rC^>mhQ?|RA7DS0TUB2DQ z)5csHX`i~he*Cs zdyXBw)0AnSz?e7R{!kS`6%I4Ox2k;2gje4=`mnKE_wH6vSxt4>Y{w35+q7mD#3xd{TRDyv_7?H!z-#pX^ECCCy5JF?2E8XEsc z8vn*yQ=WV2P4J4rZhg6NgaL5MTnTQ$7dhMlMh+yC88Li&n1QnjPmOyGriqqnoa<06 zaMANGPg1vln2{qZiXjQr;{1tkPpPe~!>@7R7<|{Awrkg>)u)l#%zIO&*fq?u#7#@D3HcGX4mej>~NkU8WlK~VgXT@b~tY+tjm{%R^U!)iaUiQ zVx=$y8gGKyrUD3q{S`=*Ci0{&24Dr=f?g@Vlyl*0Yu;K^`}1MV+->U07}x`11;lCQ zm)GI;^>>!kObII#7IE_?-LZ8n4UYu~FJHadam0sT$W!|}cWQ?uh-2#6a~I;CArN2%s{iY1IY!(YSXRPKJ=zy6S|%ex zI$K&*RUP7_x{Bv&YijE1aVjJpKge8{Tye|6`;Hzmxc`oWw!zSbMCVl zY>tQ&WpJtyN2z;`9yXv~9~>I*Hgd4Vq0;soNPf~rU*Me zeBk}clx*W)qLed`MZVx2BXza-!6InO0zs889Ay>1h|SB&iU6W`<3|PKN5l9lZ-J-~ z|4JEykRgi$PDa>Q)zo;mh;)!5>WYeR7UHpIUioC!yrs)l{eOGk0cdAY?0-v=n@$3P zlt3CGkOVNHN=Leg&k8n>@{orOD?EHE#fHjz$OA>*1I31*;9sAJD2mcUuOUDPgr3k6 zNJwt#{r_fuyYtO{=X@vUCO43>UvBoy?#|54{&sfGer?a!&$D$&Sld-D{&mMO!ZNUcScme#Cg2&_yo#0AiO0O}oF89? zPW5i@^_k;82m*J&@jNpK^nd3`<9>J5^_>eB?z!vRP=Z@Jh>x$&??ib0WLTVbi8Rv)QH_e(IR_J@xDhS6=lGFn}2*;WwsnQF7NE$4`6WX>9Sg z)1+-sg~CO+(Si46(&M)L3*Yc-pZ-v=!-)X`ZPyEm+-?(7tdV|mkt~OloQ)%TiX=OMtS=cF7 zDUu>ZlaM75tBPhxfnnlyW5I*#Bv^0mv&Y2k#*LQgE^=@5{H2i$BPBzD1x!ux5f&{B z=&oe^L7)r`uqC8upwzHgKq6+9O9xSvFdHnU9@;7bakIe)#-XmTQe}@B+vmh_ z9l-z-RpN8o_QkL3{OU2Rsvsp6l2H4EqeR54a)nBOz^rIcN_!d=YCArm4yNAHg2$Jp zeGfIbHuu#p9E}Cb)?1#8lj1R>Hy=E(KknOH`19Y>0Hm93{FbxN_%v>z;I=GO-*(p& ze5>7j+x=LioO#Oe_dYlczu=5J6}Xpr*L{z&Z6d%FB}RbWV%S^%?dbO)vfOaXJ&4gL zd2Z%Qot=xGd}bEtefF62)(zK3)WH1{6_gU(wtn`x7w^9R5lna#!cH9-kS(}=H+saT z>kS@=i>fdi_r9_ARMgOB*G;$Ik86KlIq9eeAASNMc<7Kph^mGfh+FP>5SLtW`yOZ0 zxEqeUQgdFN_rOEb=gsfL`m6tdzCZZZN%$_Au+1oGOpQO`==Z?d)OcNt$FU=$$KpQ#aIY*M}|>_-!#Hf;+JxcuP4%W+1AAcA`c z{rh%2JnhNvo%c)JCPqKOj{}cG@Az7plQGehK=;(WZbQ|{I1C`kKv+&vXf%-{_BfPHpGi!+{@dCre7N5$kj zAH3zZ`|+UFxUnN~R~P5<)bR7muX%9l;|Rou9lXaz8w|mXm)~D=1FCQ!#S=ecx7-5P z`<{93#s9kC*R$umhH_}*FQZ=a>#Oj<6H3s~pn?4n7JqTYHHd(?M2H*25WoMS$9{P6 zmCwKM^7h+~*<#qnk3I41x6b()7TQaeEV=M!f525*T-w`a%}at8o@H{LU0?^4{fRsOn@CJn$y45U;O6JFTONqvyC^zGh0(0 znIV>mAS{v;hZv@*5^AUFvaAsG{6}<%tfV-=#?%Nc5Lpna6749#j5J%P+_&3P^B3*3 zZZPKHxU>q_mDS#2&87Ve-Jt2sr|2TZA>#FPx7@b~bYL*?CDEk)r|;13QxojW*Q<Jj@77}opJcMNE-l0|>s_t&{M&Xm`PrFGXakDD3RB+*m$`{ru# z$IFX&k#W{bvoTlAemq3^q4yo|v&*jj^WSboFq-q~+)x#D;mj0*n3j0-B$kyw>)H=N z1YcZXWe2WRm(qknDg>xgAgWaW4ybKrpfug;yfR4;m>@;}e~46d&J+ZVrwo8Vx? znLGGl9yFW%;$g_QwhkK555Y(-XjUBP1zrzFwDR>LIIurr+~URIQV#8DYi-5rJ&Ttv z#VSuny0BAhh-QN}`@LG>X~O^UlxP+2ff#7mKuw)>*n5 z3O5ODkQXjmvTzZ8{hS6Wo`S0Uh1*bqGIZ4Of2f^1Z$8pm`)yq2nlb9bs!aO*>TB|l zW=VG0vSm*iHXmwhWhLMYAgPSka_Xd0=q%YojnA*e_u{^+}oy zwpC((+Sbb3Pdk0zyKBGR^;T3o5C%}G0bedcIx7l-lSG|Rhe&D)9?dZclPYA83vAv7 z2wG>bgjP>c&d5w@OTDbO)5;&6vqTW*VT8$p=xW(<0bH_J`5uAZqHz8iR{0hZyk(&r!w+9A~K@=njn=;1{)tBF9rAEy|E!S~dB- zY!#2!Bc{g=1R_Qp=~&HS_D`K%Z$;^~wRGX`_K1P4Gv)?P-)@y{zQMXY_!)?u87Ms~ zyFi202HYrbY(J#eApCK?@{z}$g3X5i>SI41HRlBAmjA3i_OF=#XZtyQ8sJcO{tACf zi~Ptns&OFCl!}-_wLf%5gFm>L3W2eh463O}tcu#0LLoSqvPGt-^#_&Lwk2MI1F7n1 z-u_n>E_-rrTvSqtZ!glgo@gCFnb`(|wOPRfh0WW)$0oh|$s@YyAMi#AQYCPe?txeI zw|dkJ-_&tt#a5iBoBppYLMZ!B_vSD0Z|}dsmK*?eVu^s|fw7_MNdgl|B82{tKw5b; zV_P#kZ(G0Dc>JPHa(GKn(S^M;E`2PyFrTo8jF(!D{hyuCcl!-WjcV_qDt7wbmzUzD zZ;7011^#$>4)eGkZar1j4g3Y>coG7F*;+f6%Y#ngesQhf3YEMG_OFngRYL2W{S1^G z{;COYpa8Xk$9&F+Do|N&Y)XVBNDLJ*U?eK)x0|rYY@|sjE^MpfS+Aq*kj>?H8q*_b zeb|7u-#oq;50Ixk#rH?I?C;yF<-d39*H`zqEaodzGbqZsKn(xw^hMV{FK?toKz!r% z`=f}V#wfL?C++{& zkG-4Q-{PLLwhkMl-M)Q0zW(Lo@CecEy2(%j+qB9O!T((c?smq%e*!m)9-aOq$EZpB zale?2-g&R=Yy>G4>;e3-NfP!t!rPFq&E*zE(#P#5Z1tJr568WAJoOnnum<>#np*fq zw49%-h5eYpcoIs6j|5WvgNY#I&?-qwJecF)k(nL|%0dwcq(-n@ScP8>5-b!!1< z_Lj>VZ$I`i!z1E@2M$y~V0N${JMM11Z5BX}V8TM{2r-Nd@=5zQ7&-*eN4h53 zFnsF8nl{ECd*lKcG-_^dgtfKJR>QykrDG3z$F5p}HbuXh%{17rR8JJl^LS(r{(v*TxGST*I@h=WMbHQscbrX=kqgPA(|flH`{+;3;v36q}jBvG5){0 z;+pGjx&xh}d+T_cD&)LQDesp5uQzDmgl$I8es%6&u9NrB<3YQkb+Od76+Az3nE#kD zQj=<-VH_#ckP}=cLWl33{e@`>m9|_|`L?8K@L``=vNI} zQIK{gj1H>3uqcfKhboHTQ}1aoY)QRlMRSF0nO_-&-yPNV|Neo z@si(s?6W)d@k=iI6P`Rj<+Dd(`^cMbyAQt%gGa|7oI3ro-(8E%(;$n7w2%DYA<&7< zdvMeI&ws!5&U>e#X>3n<>_^|V%~m5j`t-uyZkJy1mnn}tfzwyKL5TMV@eU$3gTPPC z;Q4ku;7w%q-E+sCCT@cm^w6U-uK2&dPka2CfEc0f>KecG$af#I*Z8p`5uvbI(a$fx zx_I=x2LD}l9Iu_a>2EjO9*r5%(WlRo&pc0KpnTj`BmU)`d*R(`Jm&q-qfcOixR+m% zw|x{kK@VFJVH4j8TaUs^me@DskAJ!8hFkB!dy^mg;6bRxCT8b;`wKt#@nugu^&GbB z+ry_1;H~&2c@ds-wZ(yWZh|~zyL?_cggAwr~6lGb4R0u}f zkYQ2ei=Wnn5v-|d%h-&Fnu<@WG9v1{@(H{ap;ke^G!auXRm)D;ren+?xh{c!YPNiI z%l;vKTE4n{hYC>^()!d9Qu))Yq%F84s>)qrW*foEDomWH8myK|vny_YF~rs=TTzp+ z1LMh`{TTMVz@}R$e$PAiHWRQdE+W@}0sV#y8TggYAB}A+@ESFq2FDIJpE&A0VB6>| zL+}px;s3H9-nYY<6!sZ9`Ljo2lUiKp{P&ZN!VB8{`gPzf>21f2{^BP;GGXh{=*3AV z9FDf;y!sm6n8fShPz(OOcboXJ4<3xI&k#-48$95^eRp~PVf>V+-NF9UXYUd*ZfZ zPX5fth7QJ=xE^Xh=Itpb9fe&purUX=+J)?AKXF80z=y#eiO{M3@@CgbpP(^wr$6zG z86*4neohqPDSBLp!2a!D{lYP_moP^`KTbU6z1XE3Co|8@d=UnG`d>dVeDh6^#lzuH zivLSW@LY(@yqu;yxF56)^KJxy9aQzpXY~qpiohFSNJ(AZD z3agDm?Uo~k!4gK|;zS_J{4QD~;Kgr=~n#Di?zgGy9C@WKq?B*wY`R}FY^J2XTa ze7jIc13&;HJ8>p~RLY~Nl5vOE)7mm~!Lnv1ok zM1M2(e{bi$8}^f@uf!~lYarMbEEHneYs+qx^X^2yWbFVK?LrAfnHI*zj4{3G2^-Y_t_b5mm|o&GUv75UG>lVrcA?rk=Oy~OP~7aHe*JJay0-uZhYsQ zpY`eJ{hhC#h>g2%Hgy?{D!fa@JQri9Lr9T=1@UP}bFg zhwx`S_54+Txe0H+pY(|%_Skj%v7pySDjW2v9;MXJ0!!JyIk?X?qBopZsZlkb{>`)2sI&{;#cwO`RxMBY=*I3LSKtO46kA)9lH@w&9lje-#h3)G9=o1UOB#nGhv~uCIuZ zRIkE9p9a=KZ*0KZqiRD^qw>P3iw+)!ZwSw%G&IO4vb+`V9iFj6-~YI?c=g)^mTUhp zTXno|c<&e=V@u%zi)N!$|1(uK0hFU@`x8z|MQuS3v#K?AhnbKr*M8*_IA~Ta{=E`|LjSkDKo{*dYQ9SU}x4`Cgoz zVS)2H-UY-C7k52~CYHA2=+czhiT%(K4ssVAR){(-5}@stxo001t=NklI`Ux8;33$H2+tuMe)M)Y$SBVf~8e@{I^QlM;8Re zs<0N*i5*v+QW|<+rN43c4Ko+t^U~6Zwa+#R4BD&sal+U>&%VCwhY#t7rVfY3_8+== z@9*psci(6@Z1~xnUq2Bxl326wZ`ZYkTo9BY!alpYD0WT&2w!?-&ey(k;XC)=ZQJo% zVz)8;$yT*z{SRTrE;}+zEiIJAmX0{>gVd76*nUEW9y?7Si}P`?{pXJ^J@mlcagvTx zJcLyI`s0^RKOg6zIFSS!e$3#7mtMgQU+{xjJ&f!RA@+aM z2`qb|qf&mzOb;GtfPXU=-LbJ1ek@L0WHtl@scAXG!N z6Zr@!$^*98J5}vR6*eIue=ryWC+%VfVzC@cV_YXe>tnYZ4q1f7Yp$D&K>7Z6?Tck2 zVkq=rr=@r9|MpV%an25L`~W8w%V7R5% zos9iqs?si@xhg1asVXJ332j(RM_;tlAlVa9K}st`WQFYiZm~s>Q&(s&nmKb)$CtL% zON~++k81l5AKCljJ^S{;;*qus+%g-=e_D9%l+Mn@g0^-fz`jP{|J2hnfdys@t^>k& z+>r>-NKcjQ>cUkvEHvtTVoM5lV4Fhld%Zvk=*B5{TZtdt=K;WU(#onymv|`je%>d)^gW!tV!?vhJ88SE!SKzqe}Zs)^W^(L z;}GTAz+Sse1ku`t-M{4u-Hd0%U^&!0ges8t-2ce;&->Nc=l}Ai$@k)Z#dFWU$YBx% zItx@2^@%95Fb2Q#gH0Y!KI8mPe(Afxn6V>q9twuH@3b8(Z)G@NUr3v!VZKUntQG#!ia&&|^Ae>slD*pV}|`*%O^2*8Bd zH%`6}Q58eR7Ln2^F#0=ex3&CenobKfTZ~a+wf2Ag%^WdPy8?n8LLkt1C&d_m`0c7} zTBW9iB(bpN z$$85zo+i(=uYmnfDClTPL*HQhAAW2Gu3_O~*4bx#=H=OQwjMJg^wE86vH2z^9Q|J0 zLdC7p4b~rw8pIdOL*k1?8N#UTn~S@#uXirQ55wYm;#pt+6o`ova1WscKWNjdcdw&A zd?+q~?Y{H&*hdzpfheE;_|sTLa!=W(o|%Q)1=g|x)3VR*lfa4J@PR_ufJx4=6E^J2 zd)s|`?>_0nA2eu;lM=yQ8~Qx+uru^4;_lD zdx#yfQKK63*yB&*r*ih$eTR1)v^$>STyMQW*vA)_J8^L!ctLB+i!aG8ndtB5Px#QF zK?CsIyBZ>DpMDN^{N+;Msh|7!C6``>YY_+SyURCD{RFN9U^`^oSOIa@eGg+tOZ;*= zE=@l2*ps+UieM@l@Q-cKyF_0Lt{a|x=1D*sJAooQ`EJ=SnXnsy)8UfAIIq9|p=nTt zAGyT$5Cjn=aXsWQy=R4GCZ04vqaQy)yT7w^e^b}}y?5W?!|yu~Tf6s(c;Z^BIXCD2 zksmx5mo;y{YYKuX^sn&z6%srK=0=>d;f4&>IJmrs zXJ0rFV1Nkr&Bad_;12A6U2rMx%wU(aZ~o^`a0yKBw2nmy9!$Yv3b7S}h(vfo1*&lC z_EYz3;Qm{W+F9-!^^1P_2e<)#gY1HzUWudWyG2h`d@`vrC)UxR;X z3PX_7Pza?~Y5Zy3V9Nt&qBulsn?&Ps>tO>T)rn{o?{q7{2-#d)1s9bxFPqvR;IdyZ z`>Wgc`8*y7w|;xpieBM@;^bM&?tXd6v-2_Q+BWLfvfcW<_T0FwKYvuJ7P1OG|M%3A zV{cqg^h%Lu781>rS13-3eTVRcvnue%LJxk5`IEL);EYRyy3Z~)kqtX_pZ$%K@Sxub zUpPx{gSO%~tt77b1K02HGi3Pr>{Msh!2bQw3rKk%=+JX{LxQ>cF)TI=M?iq}ruTOUx7dq9utyf#;0s*Zkt?4^p$GR22 z!HsxFwi3TIp@%m(mW5AtmiCnV2V)BR@ z+i2=^K$XW6V$7m^zJ|!42>EzGVQpcoY{8-yOG`5V#oBd`IA2y(>62I}(68ySCd13>{T4xhG63lm8SlC+k6WP@M z<>Fs!5=;DeRq^=p#D>&==uXPq+TMTt(Xfe`2xAxr;Q{f}M0;88iZTUq-wYjM|FA9IuMTGl*K;JEIM6;4qhcqfeb-YcwDga z<-!+_L{BWlr*w>QIn^TwL??Fa_4)VRr2Vpe`WIWhQy|gcGQg_t%F&nmCOxf!SWEt7 zT38bFi2Z1xDclIi>W6=}pTpNI4$h;p|1jvk0si0KZ-Y@fcVx05+8`Ao{u2Vlnp!B( zm9-l#30M-SB*mvYXEl+60Wl{%3Z7IJjfQw9`iqsO8YA`z0aZ-H=Ze@rV{X^8^Sch& zEL~N4v!oU6n-R=fYRq+^AtKuAa~@iJ?$kx-P7m52j9O#y&-T+n#NXZA5f9-Pw0Pet z^#6>1Gx)dnTYr>{M|#-AuN*wgbe-);amavb8q~z7qLEBu7!G3tffA-dNS&repa_5T zk{a8JD)3kEbl3g|W-p!D*>d2f?GRjb$9Yp1oq4a^<+KUVQ}(av_-Ffr-^J-rILRC0 z{~7<~;@{qH=qL$X%2>h?>*?EWKY{KeTO~L(=`(RY`=Jy^QdGTSH1sd{Fj6O23zj0bG8UUI$p)yLybCM zVbqCHPMBg1Xqlyf#SeB;k?_PCe7cC_{3J4HvtB10$X%BVE!UW^g7&{{{kBW?=)3s< z-OPfPiRuaytI8qb4s4eG$y*m*^ZZf^WUJ18X|@W)H39!@zd2pioLZ&+pYgB4A1lg{ zf=;7@u}V^CVXB`Jj5I(>nTwRnl2aJLri{w3tDcnKM_iHOK^cmw4irP1H3X?953xZ7 zmv8^f1@iLs#(i5S3@skTZ?c)XY}e0PdieF7cfW$iK4` zY=1<6V<%Ha$C4i)@mGJom|zW|3Pf=E{vTy2lKrQ^U$0SW0Men5alygKrK1A6YGABN zB@|UZsuA??2_~+jl-mbc&&UxH1Cn;3mkXs(&kqQOycrDTsz0PyS3!Z?4KZELvk|YfiMAT-~xC zW*xeD`-xli*>yv=x-^#+V`BtN&6xUH*M$!+zI?{wMe&Ek)A|+9b<6&yn^{fq&-OQs zK(mb5f9fICl>c`xP}XZ^?JpZMxb;0-^xl7yHeA8%C5tsib9B52>j;?-%~^Wg%w>Og zddWR6_jG_<`!UWv%m}R32-FXhezoYog?wm;OFsmT%A;COA<_&_!QC8Ht+NPiD`0;| zTgx^>+C~p*+o+>uU~d4^I)8E33k#P$Ij?KVoTYOY7pJ$)nOVuTitMM_QmfT%l^DB` zt=73x_-Ff@9D|HMfvU!#0J0e#aQi^nJtr=jDpCG~)Gxn^Yl3Bg7_-El7>hx7*Pib3 zm&}OSY=5}&+g&`n%O3$ReP;YK{z)7t`N#6ItHyt1`)I6;v~i!JfLX+=;!0dH72^FB zusW8SRaI2unn^zH+ImBnJQ9)rL zqtBAIlyjY_Fvp5)f5bE6AABML5?w?;v;XKU1{M<1If2r7m;I-X|HyCV$byj3iZ9q` z0kG5J5a{S$$!bFD33?I_B{1ThYdxq%#~}%1q8#HPJS32UPQHy@W2&F#_;^c0HO=-5 z>~hF>X8Zv}_8)Z!^@p&YaFC!xCRCokO~xPDkCG@RAS#LsWWZAhK~-0LF~CJRQxFCN z(^UDTJWgItVasYwOxOz3#AsBR4PI-KbYzYMMR*2(UU<_ zL>VAaK|)y4!#4sA7#Y<=MyCE$-LXKYDph{3RLsow1Cflsx<&S%u%eKO!6^&Zkcr1M z&tC;Y;)$MmYt-RagsmoogV88Z0w0%MTrH}ng~SIz&m;K9z~sw=X142jlsN=el*_d? z9X5%Q2$gCBc=T;NX`2>i``yajI0C%jl8Jk?Q!<-hb%G5Dy)4A{>HL zAdp2pgC|n?)3_z6+&t6Z72;N*NCKNOORAAr*`-*stktXrL}jLsRMkCD4O5UBk?qg; zGa$lYjHv58%8>m>r;pA+&d2OO<~Bx@{HML|(2=NPwJ|YP#Z)2^c{D;HEXsirW~tH1 zid-!}*=p0+N~mF$8knjoDwF}%Mngz88`wHil@e`Hvk0k@U~sly(1}MxDdd?&#voPb zGX640=R4+O zi=lxfOyL9);6kRj`z+_{5Dm-ST% zKMj@%4lUV!kCQ^816@0g8UO76etso$$~jTO<8HdH1^$EQC_}K5&}IS7cU}t_q!<>N zf}i~duRM~jdCCQ^a!Q0J)o(lrh)!oPMgy@h%Pk{4*icQdE9ob%%ITeV)syWPl!#&k zDC2Mb99%9U!%vS-_Mdguf8_C?u!8U_HTTt^0Z79fSuxQlAdxEs*${1Of|o2%BtnJB zX@qw^(Pu%>IHEDZHXlG?Mx5==%ygycDzg0!)G!bk{}2Qfn&4y6<)kY64;L1B{;H!O zhTx|lMua#9Xb^yq3L()emPcVDomH)KS)G|g+=lduWb!ojRk%&Qklg=QNhK|V@R z#22!pUU_0eX_2b>8YGEqw%-E|+GjKV_1q}?Pe8@HiqS@p$+AXhd4l1ZIe+mhjKkw} zac*M$1mVARlZ@3t&@;L6mC*xq!wNA~U)7c~znK4K3k;N_!p^GlY(GY!SqzD=rYnP6 z(Oz^+XlvF6H@?|_Rt&cS>x@6oP$g^>y9b4+#LIsSD7LuM9>- zTLq=DO=YzihOjz;%u9HKs>(f&r?dS)EPNRWmGPG^S0bTsEGNnSvzGV|0wu;$jx>-l z8*5lEX)*A1rcY1>XzDlnjYEy z2u#L5`%j*~j4efsSjqU)I&|dTg|dQzTF$qkNGTSI5r}4Xrb~iBt*Tt7vFTjCo>Ifz z%x3!qFh4FC|L(X@_MhxOiNE+C7b_~bl;g@SMobBgy4+jFCw~|gnQB!K_vLBO6n(W% zOs0`u7nX$~fl_}Al7~c{4(qi=IUDC;gwso@AnTVpoI}c4gfs@_?%3v5P&FzhFOu`UHK{y@1g^KL|1kB^iiTtL=pW@Wp8x)3I$D1C& zVfH*pXEfL`OeW>-=8W?hC8IOJIUqV@DNF zGv%(SY`;ArP#{-O2|R|q5LG%W5pCxD2o6mm3sI9>L+1Z4n>d+ zhY`*3$Yc(-`BkdEvBQeON^$yNbg(W;XZsCyBQpNke^MVP7BOi#aLV&n&XQ%DHGCkr z*xhPCgBrQ*#gz#Ox!i>k;geJZVsO%&B_b}BlBRm<^CuCmLMN3;Dtf~|eE8G+Y(IxU z(8>4{3rp1*1sL-l@#E}20!Zn|{!@zyN^mo03#q`L1VK%KoMjWol5+kt0$C|x4I%e> z{2&OIYL3i7mKss(7=+X5(Wot<9K3$@T8Kc_@k=?gQMtCohNTF9_D}Pgl~$u5#7l=W z&qPcKb&3SpKsbA{{QydUiFg^I<*`QKOK@XBNHhM~|KB|SX&o|r5BhN8ls@Aoyf2oB zz$%n_9#`(jgeA#TF;$g&Vaz_cL_0v2KYG`@;RyKYo;a?T!oc7_DmUhYK&nL{^}K8uRe>3sNKMG>mABpv;4x zH38bhL5NA3f}jD^Y39-LY(G&oY%NQ;EcMs|G&wT<(e1PUXaBK@qjRGs{?tA72CLnD zmN3Pos)kDjCY^ru7qDKIN_gGUW^4o5*b*1EMZ?YMEL!mV+5U{b_lIhq%>E-QlIF0g zKlj(-`71k|(?RU|Z1k5{9(y0ytlB+l86RH@A}SI2;%7Pl91p3}a!HMmgtrGY4yHBy zLC*7QQMNxI!;g%A_8)kH&Lr^&OgK&%DGZOFmgk`G#bX`!AKg7DfG6LzM3RK237|0o z3ZRNI7M=vMkX0U(67(X~b&c+|A$7W9RUXl!+Tw_WPahD|whBfq4*^tkWcvjhfivTu z{U^^~KMPhe{^*-I8hN~9u|QJ>L<>eNlXQi?k&2ZgVr9_9Nv#3b>tqvwr-_ejKPQGl z6|r@=mGH^V%e~Liq{l>4Y#{(yShy2L;I19-b%LZ_pV~DL%y*Rh2`~lz1ML zBU^iTo{YaiXF#R+Wc&@7K&1G{ew15i+yPR0ml+@OFmiD-$CtxbrP>yQg>n{(!%BTj zJ-^<(<{kFr4eZSJa}+ZE(Tmh6<%&f1A5O6dO!#D>pp@q? zFesse65PxQg$$QWQDj6aLlpr~$c70iD?Gi{sDga5hk|wWh*XrN5}KjK!A!Z1sZVb} zj|FQrOq(jq4hClX`TYePGXCNx*?+>!@E>{p3K}aAf7<)5w>jn&{zN{8Qp|5aom{16`|E(3@rQ?G{~-c({9}1V*?-J`LZBpKC|w?O_3Q0> z%82@W^-9?&4MKglY%p0W?v|{##mqLs*EXij1|u5t``VI7wm;+FEg#7K!`WnRV}ze& zjcLQsh`Rg|DC<^Op|x#&GJ7@ zM2KvE#=nlwHu9qEKLMRJfBv>{ETuoZOo-G7?ImXiQX@j=-ACdO&Y4U^2;3{uR8FZ$ z$VYP6bw(pXwQE2~4gOXZc+6}gv?8$~_9BSH4^w3XYZBRPKhSYsyCtj&x5@ZNzsmj- zkT73aWBmv3dTWS;bK_zTQ@b(|Lms=SeRe}H^FgDki)z$X=%n&mw4@|zwRx4=pe&K6 zh|=QgnN5-GuF4=#=cchzB$=oUVP0X&v{=)~()iGoW@j4v(jS}`cV!%%U z4wW?_NbYGvLd(6V4@@3(uaydsiek~vWug*l1uDF4i5^;_sprY|6PS#@`c?LyFyVB5 z<@p=X;74=uM{eeXITfR$h>LItQa&yf^9~HigjAoPl)A)#poiKdRiz>Yf?4?#C6+pZ z?boqQ*_l-GXj9aj?T-j&{GID4nf*s+ah|_13c)KFj$-_?_~SOk;A)aJF17e3uH|{$ zDiZ5SorNNXD}-1Q@;aek*d;vBB+o^c56G(?DM!1Eabzl(9aE)k*?xW-Ijl1N!P^`r z*?)KznD|DXzv?LE__N*?Zx1U$hA0Lk=S;!9n*LDG8AK8FgwT~|41u-!n`k%DSuv0y z#MbI>+Hx``ZDsovlmwHEe?yN-u*&|kw)jt*UJyiF#Q365no1J>X`ZFpV}>E5Lg@8M zr1Ga({YY3CBb8qol0~8%!mX(hS}dxxTy>H{m zhExxqD|D8*0yEmvfT-;zZAnS6ClaxRD4Tv3`~@YF*?!=G!;edWT*g0QQFu*3W&c^; z99+x%rbFQ3XUX)FwcpQee*&Ull?~`9FAnJ>9oxAH^C&0KYTn$pwn<8S1=XEBJmT< z2Q`J8@r%wEBt4I+EmRrhgBR>UmQJh;WRle3H4B>$O#Uu57?n*`6?#-e%SuV59(R`e{hcMKQ>%IBk=0Q1l>cCpONAB#QeRLGCe>bn+KAB(m9lE-N$s`fZu`hV!e!d&D7&XMVH) z>l_tOWO0+{ulEg{dKZ4TQ}9#iilx$4wqG%bgO>5{hAU!jVQKA%qgS;AWa@G6<@iND9F~1#{i{%0I-aI_8*-$I&*dYtcCvL10{Ql)Cf)q%Yx|cgfS5#prLeFl@a8z zEcih)H9`xDa@IEHF@+6mfCk@36EUKJY>CIR&Dhoqk11?mL$;qN5l;?fGuSr6lkqof z0hb*HixKos%YqX$QzNvXC}(YB9#i-x_`e3q+I~p~=w#`|KP#55OP}Q;l`=DB?<-Oh z7Qu*}5iGQ9Y`>0eA{-9dSck{8+ycUPz9_d1)g$ICXu@AX&F-PP&&z0PLr z@v_bEd;L{(cXfJxud^9@ylgZ4UVjzcU7eoa>ukmzFWU^i*Iz|nwvOyyH3 zq(aC7QPR=^1!Y0R2rZ~g_|$-?Q~{=FMG2%rNTsBOa;ih?IMfDVfJu5(@v920M3U_% zhKjR*Z-kZ$o;nIbNTq^T8q7I`e5(YaZ*Ekq;Qn~5TYn{R_^I3Rw%921Dja7}3k-ecprXZ*-=*;$K z{Imb)G)m@Xp1&cqNL*TO{L%d=Re;e;QwU1%VKG!#Kx7nS%0gLygm}f5WhKM~Nv#OF zP*!?UKQzi%7i2w;7yuE~=aa-XjoEP@y`rX|U|Y6dLG;)Po*94mM)sdPf7J`>#k3^; zxD};~E(}j0NHJP*W#=N6#9&)dk|5|+Q|K6{*1r+ zSa8zpKLjGrU(Sn_i$8MLn`?{0ui`0kDA&aYgQkl`!5jG@!AlpRl7*s}N+A|>k`GH% z>d}Ibc%7z)Y{qO^BPg86>S?+(p6wU71*(ic{lwhHi0nT^!0_Qzl5C#88mt5fqN9ZV zT&?C2c2U9g9Erf^R&Bf>7*BPh8vQh-T<#%dwGjz-6Pfx!5Q46PjWW&l8{h!MU^k)( z$P*R^v&MDvH(2eFE|u#vNre+u2F{TgWl=U&vf8*i#j@m&JZQV zA0lG`i^kQUlM>W4dDT7=|OL$*JT zf{Zb-$@mk8>_2n~KX-tMog_rh32u}9Cx#XSL<41rpmH%W?si@gG{Xs)o>p^JCn=7a z5#Ur!f^4c&P6<_(tp^(_R+lCH^<}gD@tst5$?Xy0(K|Et>?FUCbau+hGJHRQlWgFp=9pSmYhu#OWKP?S-+!V3 zt!MjXU^4#V4HaL;*?;o<)p(=9N-0a4j6dyt25+h{Fa{jVIEkI%A3j*1DI76em$haB zq4QKi7LFozg-U*ivx>MJC}Ap9jvqB_LRqwJ3OgQYh1pcLC^zdxfoO_pwqIZ;@=P=S z!EFG9`BnBGLcysQQ#j1JJb!y3=rAhzAjt0@rW%%j7J`#ZGk$4H-UUtbF+mlXn0g+s zQ~B$hwX0-EoEjfXy*Xkzn}AGJTV5dBA66CuO2$9?kIWhzdHyO4A@cB}6o1+~25rjF zCP7C+q5KJUWHgEuBj?Xlle0tEHjH zClc9yAV5GAb{UY2fA;@+KETP!w07{=TLWRIPEK5j z5IGT02qkP0LSZZOtKNEvjz9>hMc^}WC8!OY#$4N07(Hzw+fT&Yc-4bGVnx`I%lH$f z>_6FmB-98B5*6`twS(~EViI&*3=)o*rQh^OYR+ebwyM!N*n?J!U|rRynP)_{zX~f~ zZm=7X@%Mgi?qmf0%44`Pw83sf#(#~$UsjYWGx)1cmJw3}s5n*=0d&>bvLc$Z{VRf6 m&&g!`dk)JL(X^J~|NjA(i~jkOo;A?`0000&2 + exit 1 +fi + +swift build -c release --product Knotchy +bin="$(swift build -c release --product Knotchy --show-bin-path)/Knotchy" + +app="$root/build/Knotchy.app" +rm -rf "$app" +mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources" +cp "$bin" "$app/Contents/MacOS/Knotchy" +cp Sources/Knotchy/Info.plist "$app/Contents/Info.plist" +cp assets/AppIcon.icns "$app/Contents/Resources/AppIcon.icns" +# The image is built from these on the user's Mac. +cp -R vm "$app/Contents/Resources/vm" +find "$app" -name .DS_Store -delete + +if [[ -z $identity ]]; then + codesign --force --sign - "$app" +elif (( release )); then + codesign --force --options runtime --timestamp --sign "$identity" "$app" +else + codesign --force --options runtime --timestamp=none --sign "$identity" "$app" +fi +if (( release )); then + exit 0 +fi + +dest="$HOME/Applications/Knotchy.app" +mkdir -p "$HOME/Applications" +pkill -x Knotchy 2>/dev/null || true +rm -rf "$dest" +cp -R "$app" "$dest" +/System/Library/CoreServices/pbs -update +echo "Installed $dest" diff --git a/scripts/icons/frame.swift b/scripts/icons/frame.swift new file mode 100644 index 0000000..05e6043 --- /dev/null +++ b/scripts/icons/frame.swift @@ -0,0 +1,28 @@ +// swift scripts/icons/frame.swift : fits a screenshot into the 1280×800 frame the +// Chrome Web Store requires, on the icon's navy, as a 24-bit PNG. Window captures keep their own shadow. +import AppKit +import ImageIO +import UniformTypeIdentifiers + +let args = CommandLine.arguments +guard args.count == 3, let shot = NSImage(contentsOfFile: args[1]), + let image = shot.cgImage(forProposedRect: nil, context: nil, hints: nil) else { + print("usage: swift scripts/icons/frame.swift ") + exit(1) +} +let width = 1280, height = 800, margin: CGFloat = 40 +let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 0, + space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue)! +let colors = [CGColor(srgbRed: 0x1C / 255, green: 0x35 / 255, blue: 0x63 / 255, alpha: 1), + CGColor(srgbRed: 0x0D / 255, green: 0x1A / 255, blue: 0x33 / 255, alpha: 1)] as CFArray +let gradient = CGGradient(colorsSpace: CGColorSpace(name: CGColorSpace.sRGB), colors: colors, locations: [0, 1])! +ctx.drawLinearGradient(gradient, start: CGPoint(x: 0, y: CGFloat(height)), end: .zero, options: []) +let scale = min((CGFloat(width) - 2 * margin) / CGFloat(image.width), (CGFloat(height) - 2 * margin) / CGFloat(image.height)) +let size = CGSize(width: CGFloat(image.width) * scale, height: CGFloat(image.height) * scale) +ctx.interpolationQuality = .high +ctx.draw(image, in: CGRect(x: (CGFloat(width) - size.width) / 2, y: (CGFloat(height) - size.height) / 2, + width: size.width, height: size.height)) +let dest = CGImageDestinationCreateWithURL(URL(fileURLWithPath: args[2]) as CFURL, UTType.png.identifier as CFString, 1, nil)! +CGImageDestinationAddImage(dest, ctx.makeImage()!, nil) +CGImageDestinationFinalize(dest) +print("wrote \(args[2])") diff --git a/scripts/icons/knot.json b/scripts/icons/knot.json new file mode 100644 index 0000000..ec10948 --- /dev/null +++ b/scripts/icons/knot.json @@ -0,0 +1 @@ +{"icon": [[[43.147358872228004, 35.597261122187355], [42.61532507762965, 36.304681642555074], [42.09814885584686, 37.02497342074467], [41.59628295059067, 37.75754407139881], [41.11016314262925, 38.501788171305485], [40.64020783440553, 39.25708782787771], [40.186817652259094, 40.02281326017288], [39.7503750666409, 40.798323391885674], [39.331244030691536, 41.5829664557369], [38.92976963753638, 42.37608060866765], [38.54627779663111, 43.17699455723982], [38.18107492947324, 43.985028192631795], [37.83444768497557, 44.79949323460966], [37.506662674778056, 45.61969388384524], [37.19796622875481, 46.444927481943765], [36.90858417095287, 47.274485178536395], [36.638721616179424, 48.107652604785535], [36.38856278743295, 48.94371055264535], [36.158270854354186, 49.78193565921238], [35.94798779285065, 50.6216010954986], [35.757834266028794, 51.46197725895178], [35.58790952654568, 52.30233246904732], [35.43829134047141, 53.14193366527007], [35.3090359327321, 53.980047106803596], [35.20017795418167, 54.81593907324261], [35.11173047032945, 55.64887656564209], [35.04368497172906, 56.47812800721761], [34.99601140601252, 57.30296394301044], [34.96865823153224, 58.12265773783302], [34.961552492551895, 58.93648627181062], [34.974599915906126, 59.743730632839], [35.00768502902749, 60.54367680527923], [35.06067129921806, 61.335616354215425], [35.13340129402195, 62.11884710460527], [35.225696862534214, 62.89267381465754], [35.337359337460796, 63.65640884277764], [35.46816975772352, 64.40937280742695], [35.617889111384144, 65.15089523925003], [35.786258598640785, 65.88031522483004], [35.97299991463079, 66.59698204144215], [36.17781555175401, 67.30025578218226], [36.40038912121131, 67.98950797085851], [36.64038569343427, 68.66412216604321], [36.89745215706282, 69.32349455369211], [37.17121759611, 69.96703452775148], [37.46129368493396, 70.5941652581821], [37.767275100620964, 71.20432424584489], [38.08873995236454, 71.79696386370284], [38.425250227410274, 72.37155188380925], [38.776352253118695, 72.92757198956546], [39.14157717468284, 73.46452427274542], [39.52044144802173, 73.98192571480013], [39.91244734735576, 74.47931065196943], [40.31708348695592, 74.95623122374559], [40.73382535654387, 75.41225780424828], [41.1621358698074, 75.84697941608812], [41.60146592548216, 76.26000412631285], [42.051254980438095, 76.65095942404778], [42.510931634197796, 77.01949257945994], [42.97991422430148, 77.36527098369383], [43.4576114319241, 77.68798246944569], [43.94342289713845, 77.98733561186113], [44.43673984320961, 78.26306000946163], [44.9369457092966, 78.5149065448237], [45.443416790929255, 78.74264762475569], [45.95552288761989, 78.94607739973573], [46.47262795696309, 79.1250119623963], [46.99409077457018, 79.27928952486037], [47.51926559917878, 79.408770574755], [48.04750284227361, 79.5133380097496], [48.578149741549126, 79.59289725048669], [49.1105510375421, 79.64737633179448], [49.644049652757644, 79.67672597209153], [50.177987372610914, 79.68091962091582], [50.711705527504435, 79.65995348453107], [51.24454567535968, 79.61384652958573], [51.77585028392153, 79.54264046482056], [52.304963412154024, 79.44639970084314], [52.83123139004737, 79.32521128800849], [53.354003496156544, 79.1791848324669], [53.87263263219571, 79.00845239046137], [54.38647599401408, 78.81316834097822], [54.89489573828321, 78.59350923687589], [55.3972596442302, 78.34967363463801], [55.89294176975501, 78.0818819029179], [56.38132310127728, 77.79037601006189], [56.86179219666286, 77.47541929082043], [57.33374582058782, 77.13729619247562], [57.79658957170505, 76.7763120006342], [58.24973850098674, 76.39279254495501], [58.69261772062432, 75.98708388509974], [59.12466300287774, 75.55955197721482], [59.545321368274685, 75.11058232127185], [59.954051662571985, 74.64057958961236], [60.3503251219019, 74.14996723706122], [60.733625925537865, 73.63918709299146], [61.10345173572674, 73.10869893573992], [61.459314224046835, 72.55898004979225], [61.800739583765235, 71.99052476617032], [62.127269027680796, 71.40384398647382], [62.438459270954354, 70.79946469104226], [62.7338829984421, 70.17792943171975], [63.013129316063434, 69.53979580972054], [63.27580418575093, 68.88563593910635], [63.521530843545385, 68.21603589640245], [63.74995020041658, 67.53159515689163], [63.96072122540671, 66.83292601813906], [64.15352131071153, 66.1206530113129], [64.32804661833163, 65.39541230087809], [64.48401240794503, 64.65785107325169], [64.57754692112395, 64.1596231815178]], [[62.165552315024435, 44.799493234609656], [61.818925070526774, 43.98502819263181], [61.4537222033689, 43.17699455723984], [61.07023036246362, 42.376080608667664], [60.66875596930847, 41.582966455736916], [60.24962493335912, 40.79832339188571], [59.81318234774089, 40.022813260172846], [59.35979216559447, 39.257087827877704], [58.88983685737078, 38.50178817130552], [58.40371704940935, 37.75754407139884], [57.901851144153156, 37.024973420744686], [57.38467492237035, 36.30468164255507], [56.85264112777201, 35.59726112218736], [56.30621903443227, 34.9032906517588], [55.74589399743413, 34.22333488839543], [55.172166987180994, 33.55794382664324], [54.58555410783025, 32.907652285554676], [53.986586100320984, 32.27297941094935], [53.375807830483375, 31.654428193332976], [52.753777762731595, 31.05248500194228], [52.12106741985713, 30.467619135368725], [51.47826082945333, 29.900282389197127], [50.82595395751379, 29.350908641077307], [50.1647541297622, 28.81991345363178], [49.495279441281866, 28.307693695582955], [48.81815815502556, 27.814627181466406], [48.134028089796494, 27.341072330277534], [47.44353599830385, 26.887367843381604], [46.74733693590314, 26.45383240199623], [46.04609362064407, 26.040764384537795], [45.34047578525469, 25.64844160410232], [44.6311595217006, 25.277121066332374], [43.91882661896497, 24.927038747901083], [43.204163894700294, 24.59840939582345], [42.48786252141196, 24.291426347785666], [41.77061734783661, 24.006261373661463], [41.05312621618472, 23.743064538364063], [40.33608927591963, 23.501964086160815], [39.62020829475091, 23.28306634655721], [38.9061859675205, 23.086455661834638], [38.194725223663454, 22.912194336305582], [37.486528533927164, 22.760322607328465], [36.782297217032124, 22.63085863810238], [36.08273074695987, 22.52379853224128], [35.388526061550124, 22.439116370104653], [34.70037687309133, 22.376764266841334], [34.01897298158442, 22.336672452080812], [33.3449995913586, 22.318749371185568], [32.679136631713384, 22.322881807956453], [32.02205808225935, 22.348935028662023], [31.374431303623368, 22.39675294724177], [30.736916374179863, 22.46615831151222], [30.110165433464502, 22.556952910184215], [29.49482203291886, 22.66891780047913], [28.891520494608617, 22.80181355611157], [28.300885278549444, 22.95538053538582], [27.723530359266707, 23.129339169133825], [27.16005861220617, 23.32339026820241], [26.611061210602607, 23.53721535017884], [26.07711703340416, 23.77047698502431], [25.558792084838696, 24.022819159266472], [25.056638926197046, 24.293867658384112], [24.571196120396298, 24.583230466998845], [24.10298768987413, 24.890498186471135], [23.65252258835141, 25.215244469481203], [23.220294186987243, 25.55702647115816], [22.806779775436514, 25.9153853163048], [22.412440078305128, 26.28984658224953], [22.03771878748339, 26.679920796841316], [21.683042110822115, 27.085103951089152], [21.3488183376002, 27.504878025932673], [21.03543742121634, 27.938711532616452], [20.743270579519386, 28.38606006612839], [20.47266991317637, 28.846366871147918], [20.223968042457997, 29.319063419939067], [19.997477762804195, 29.80357000161061], [19.79349171951343, 30.29929632215548], [19.61228210188124, 30.805642114669403], [19.454100357093697, 31.32199775914065], [19.319176924162726, 31.847744911191885], [19.207720988170085, 32.38225713914731], [19.119920255067218, 32.92490056878961], [19.055940747257797, 33.47503453516345], [19.01592662016937, 34.03201224077684], [19.0, 34.59518141954324], [19.008260842805047, 35.16388500580364], [19.040786815067964, 35.737461807761576], [19.097633195878394, 36.31524718466111], [19.17883280081904, 36.89657372703226], [19.28439592764196, 37.480771939327504], [19.414310323793167, 38.06717092426952], [19.568541175823217, 38.655099068228616], [19.747031120699727, 39.243884726948316], [19.949700279016277, 39.83285691093582], [20.17644631007104, 40.42134596983662], [20.427144488766167, 41.008684275111264], [20.701647804258357, 41.59420690033578], [20.99978708026906, 42.17725229844872], [21.32137111694193, 42.75716297527204], [21.666186854113388, 43.333286158634806], [22.033999555841714, 43.90497446243537], [22.424553016018862, 44.47158654498168], [22.83756978486848, 45.03248776095462], [23.272751416112964, 45.587050806346845], [23.729778734572456, 46.134656355735835], [24.208312123938228, 46.674693691257794], [24.707991834443124, 47.20656132265726], [25.228438310132095, 47.729667597796116], [25.769252535417213, 48.24343130301578], [26.33001640058141, 48.74728225275548], [26.7147271551075, 49.077399463166024]], [[44.68708881274753, 56.66861583394685], [45.56574985184354, 56.775660355557], [46.44812894078427, 56.86340221275936], [47.33348668694572, 56.931745510677416], [48.22108088806224, 56.98061556370149], [49.11016723223537, 57.00995897098046], [50.0, 57.01974367039814], [50.88983276776463, 57.00995897098046], [51.77891911193771, 56.98061556370149], [52.66651331305429, 56.931745510677416], [53.55187105921574, 56.86340221275936], [54.43425014815642, 56.77566035555701], [55.31291118725243, 56.66861583394686], [56.187118290789684, 56.54238565513985], [57.05613977381108, 56.39710782040469], [57.91924884186615, 56.23294118556428], [58.775724275990356, 56.05006530040367], [59.624851112246056, 55.848680227149174], [60.46592131516243, 55.629006338198494], [61.298234444417794, 55.39128409330302], [62.121098314114086, 55.13577379642336], [62.933829644000994, 54.86275533249942], [63.735754702014816, 54.572527884396514], [64.52620993750571, 54.265409630308504], [65.30454260453645, 53.94173742191829], [66.07011137464502, 53.60186644363538], [66.82228693847448, 53.24616985324875], [67.56045259568367, 52.875038404351834], [68.28400483256462, 52.48888005091462], [68.99235388680404, 52.08811953439546], [69.68492429883923, 51.67319795380258], [70.3611554492719, 51.24457231913228], [71.02050208181697, 50.80271508862735], [71.66243481127778, 50.34811369031516], [72.28644061605385, 49.88127002830067], [72.89202331470258, 49.40269997430477], [73.47870402609178, 48.91293284495286], [74.04602161269625, 48.41251086533305], [74.5935331066083, 47.90198861935663], [75.12081411784871, 47.38193248746709], [75.62745922458254, 46.85292007225605], [76.11308234486157, 46.31553961255689], [76.57731708953362, 45.77038938659829], [77.01981709597732, 45.21807710481046], [77.4402563423399, 44.65921929288774], [77.83832944197474, 44.09444066572044], [78.2137519177946, 43.52437349281817], [78.5662604562769, 42.949656955855474], [78.89561314087638, 42.370936498978175], [79.20158966462195, 41.7888631725164], [79.48399152169378, 41.20409297075668], [79.74264217779842, 40.61728616443153], [79.97738721917975, 40.029106628590235], [80.18809448012523, 39.440221166519144], [80.37465414884753, 38.851298830384025], [80.53697885164317, 38.26301023926993], [80.67500371525114, 37.6760268952972], [80.78868640735571, 37.09102049849368], [80.87800715519963, 36.5086622611051], [80.94296874229438, 35.92962222202573], [80.98359648323719, 35.354568562031716], [80.9999381766645, 34.784166920498635], [80.99206403639411, 34.21907971428342], [80.96006660082928, 33.659965459449026], [80.90406062071935, 33.10747809650698], [80.8241829253929, 32.56226631984999], [80.72059226760041, 32.02497291204277], [80.5934691471247, 31.49623408363398], [80.4430156133378, 30.976678819147573], [80.2694550469043, 30.46692822990511], [80.07303192085067, 29.967594914324515], [79.85401154124156, 29.47928232633295], [79.61267976772298, 29.00258415252394], [79.34934271421272, 28.538083698680133], [79.06432643003757, 28.086353286273756], [78.75797656183613, 27.64795365954753], [78.43065799656506, 27.223433403767814], [78.08275448596473, 26.81332837523132], [77.71466825285893, 26.418161143594748], [77.32681957968073, 26.0384404470851], [76.9196463796342, 25.674660661135167], [76.49360375091872, 25.327301280976048], [76.049163514459, 24.99682641870455], [75.58681373560043, 24.68368431532901], [75.10705823024499, 24.38830686828272], [74.61041605591768, 24.11110917487837], [74.09742098826918, 23.852489092161864], [73.56862098353379, 23.61282681360712], [73.0245776274759, 23.39248446307742], [72.46586557137132, 23.191805706461363], [71.89307195558251, 23.011115381374623], [71.30679582129903, 22.850719145300406], [70.7076475110256, 22.710903142523705], [70.09624805841176, 22.591933690195702], [69.47322856802707, 22.494056983846015], [68.83922958569595, 22.41749882264116], [68.1949004600149, 22.3624643546682], [67.54089869568409, 22.329137842502895], [66.87788929929283, 22.31768244930149], [66.20654411820583, 22.328240045635255], [65.52754117320394, 22.360931037266276], [64.84156398553904, 22.415854214042454], [64.14930089906808, 22.493086620068716], [63.45144439813611, 22.592683445290675], [62.74869042188215, 22.714677938605586], [62.04173767564518, 22.859081342594436], [61.33128694015019, 23.02588284994759], [60.6180403791564, 23.21504958163489], [59.90270084625114, 23.42652658684998], [59.18597119147354, 23.66023686473668], [58.707725923768564, 23.82834754606007]]], "iconStroke": 8.5, "glyph": [[[7.268804586047381, 5.9889671601204615], [7.151512068695885, 6.154889856987612], [7.037759813448099, 6.3235789166785805], [6.9276463117984415, 6.494894711173865], [6.8212660442238064, 6.6686948006094475], [6.718709390443457, 6.844834067277683], [6.620062543828681, 7.023164852326628], [6.525407430045709, 7.2035370950246325], [6.434821630011341, 7.38579847445423], [6.348378307236283, 7.569794553497245], [6.266146139627067, 7.755368924970324], [6.188189255812729, 7.942363359768516], [6.114567176058159, 8.130617956872005], [6.0453347578213865, 8.31997129506956], [5.9805421460074175, 8.510260586250556], [5.920234727966726, 8.701321830115532], [5.864453093281597, 8.892989970154366], [5.813232998378965, 9.085099050739347], [5.7666053360034715, 9.277482375179671], [5.724596109579711, 9.46997266458273], [5.687226412487755, 9.662402217366875], [5.654512412271216, 9.854603069269537], [5.626465339792161, 10.046407153694162], [5.6030914833423475, 10.237646462239173], [5.584392187715297, 10.428153205251597], [5.570363858238807, 10.617759972248544], [5.56099796976264, 10.806299892049134], [5.5562810805911305, 10.993606792460424], [5.556194851345619, 11.179515359360748], [5.560716068736735, 11.363861295024765], [5.569816674221631, 11.546481475535211], [5.583463797516497, 11.727214107127065], [5.601619794929846, 11.905898881311062], [5.624242292477249, 12.08237712862455], [5.651284233733566, 12.256491970859123], [5.682693932373881, 12.428088471615709], [5.718415129349853, 12.59701378503982], [5.7583870546434905, 12.763117302591015], [5.802544493535883, 12.92625079770281], [5.850817857323946, 13.086268568191308], [5.903133258413799, 13.243027576272818], [5.95941258971511, 13.396387586053388], [6.019573608256437, 13.546211298355345], [6.083530022937456, 13.692364482748715], [6.151191586329807, 13.83471610665806], [6.222464190434399, 13.973138461418198], [6.297249966298933, 14.10750728515514], [6.375447387395724, 14.23770188237177], [6.456951376656134, 14.363605240121078], [6.541653417054225, 14.485104140652876], [6.629441665628921, 14.6020892704237], [6.720201070830288, 14.714455325362827], [6.813813493072572, 14.822101112291294], [6.910157828373164, 14.924929646394363], [7.009110134953784, 15.022848244651875], [7.110543762677231, 15.115768615134858], [7.214329485190131, 15.203606942080734], [7.320335634639566, 15.28628396666377], [7.428428238828825, 15.36372506338145], [7.538471160675202, 15.435860311981916], [7.650326239830303, 15.502624564861843], [7.763853436321468, 15.563957509868708], [7.878910976070619, 15.619803728445767], [7.995355498145162, 15.670112749062753], [8.113042203593885, 15.714839095879864], [8.231825005718985, 15.753942332597257], [8.351556681634312, 15.787387101447106], [8.472089024958267, 15.815143157289882], [8.593272999488944, 15.837185396781484], [8.714958893707943, 15.853493882582537], [8.83699647595866, 15.86405386258615], [8.959235150143954, 15.868855784145236], [9.081524111787765, 15.867895303285481], [9.203712504304873, 15.861173288894864], [9.325649575322615, 15.848695821885663], [9.447184832898625, 15.830474189329747], [9.568168201478404, 15.806524873572918], [9.68845017743715, 15.776869536338989], [9.80788198405039, 15.741534997839153], [9.926315725738672, 15.700553210907216], [10.04360454143233, 15.653961230185981], [10.159602756903036, 15.601801176395108], [10.274166035910062, 15.544120195715402], [10.387151530010222, 15.480970414329487], [10.498418026881899, 15.41240888816337], [10.607826097014822, 15.338497547878266], [10.715238238619214, 15.25930313916663], [10.820519020609337, 15.174897158410946], [10.923535223518602, 15.085355783768431], [11.024155978205403, 14.990759801749212], [11.122252902210917, 14.891194529360096], [11.217700233632675, 14.786749731890122], [11.310374962379807, 14.677519536418806], [11.400156958678897, 14.563602341131642], [11.486929098701673, 14.445100720532018], [11.570577387188942, 14.322121326642417], [11.65099107694797, 14.194774786291799], [11.728062785103605, 14.063175594590007], [11.801688605986811, 13.927442004693509], [11.87176822054738, 13.78769591397074], [11.938205002181261, 13.644062746678564], [12.000906118866256, 13.496671333264977], [12.059782631503765, 13.345653786416353], [12.114749588367733, 13.191145373970826], [12.165726115566018, 13.033284388822388], [12.212635503423272, 12.872212015943198], [12.2554052886984, 12.70807219665463], [12.281584870650146, 12.597013785039822]], [[11.679503582469131, 7.631485089223647], [11.594456570593497, 7.44694544108984], [11.505243687184501, 7.264088568462001], [11.411936607906908, 7.083069581486111], [11.314611449063003, 6.904041522587298], [11.213348696414519, 6.727155221752264], [11.108233129592698, 6.552559154056167], [10.999353742170143, 6.3803992995730106], [10.886803657472498, 6.210819005806306], [10.77068004021205, 6.043958852774065], [10.65108400402964, 5.879956520879686], [10.528120515035264, 5.718946661697501], [10.40189829144172, 5.561060771798851], [10.272529699389633, 5.406427069741595], [10.140130645065751, 5.255170376342668], [10.004820463220366, 5.107411998350284], [9.866721802193188, 4.963269615628922], [9.725960505560238, 4.822857171966504], [9.582665490518163, 4.686284769610084], [9.43696862312517, 4.5536585676322385], [9.289004590521207, 4.425080684226811], [9.138910770252766, 4.30064910302855], [8.986827096830948, 4.1804575835475895], [8.8328959256537, 4.064595575805131], [8.677261894426229, 3.9531481392530123], [8.520071782215693, 3.8461958660552513], [8.361474366278976, 3.7438148088055776], [8.201620276804432, 3.6460764127505687], [8.040661849710354, 3.5530474525832503], [7.878752977645344, 3.464789973867979], [7.716048959337141, 3.38136123915249], [7.552706347438359, 3.3028136788185023], [7.388882795018861, 3.22919484671751], [7.224736900856312, 3.1605473806338606], [7.060428053677098, 3.096908967612194], [6.896116275501223, 3.0383123141816277], [6.731962064245717, 2.9847851215043546], [6.5681262357416035, 2.9363500654712196], [6.404769765320444, 2.8930247817621915], [6.2420536291264295, 2.854821855884534], [6.080138645310866, 2.821748818196764], [5.919185315265597, 2.7938081439214137], [5.759353665052156, 2.7709972581447717], [5.600803087183003, 2.7533085457968225], [5.443692182911358, 2.7407293665996963], [5.288178605185118, 2.733242074968075], [5.13441890242003, 2.73082404484002], [4.982568363246557, 2.733447699411954], [4.832780862383687, 2.7410805457465894], [4.6852087077922215, 2.753685214217814], [4.540002489258323, 2.77121950275183], [4.3973109285573075, 2.7936364258190167], [4.257280731345748, 2.820884268126398], [4.120056440928293, 2.852906642955899], [3.9857802940440132, 2.8896425550890257], [3.8545920788149584, 2.931026468254055], [3.7266289949974567, 2.9769883770274106], [3.602025516674523, 3.027453883116471], [3.480913257525449, 3.082344275946748], [3.3634208388059568, 3.1415766174721425], [3.249673760169702, 3.205063831122815], [3.13979427345926, 3.2727147948011597], [3.0339012595916506, 3.3444344378323336], [2.9321101086605514, 3.420123841771936], [2.8345326033741056, 3.499680344969664], [2.7412768059440955, 3.5829976507840033], [2.6524469485387607, 3.669965939339444], [2.5681433274080803, 3.760471982714268], [2.4884622007867145, 3.8543992634435957], [2.41349569067624, 3.951628096218954], [2.3433316886043025, 4.052035752662697], [2.2780537654545086, 4.155496589052545], [2.217741085456997, 4.261882176868385], [2.1624683244253218, 4.371061436031097], [2.1123055923212988, 4.482900770700133], [2.0673183602251477, 4.5972642074943915], [2.027567391783922, 4.7140135359983155], [1.9931086792069284, 4.833008451412944], [1.9639933838721673, 4.954106699209794], [1.9402677816035645, 5.077164221643061], [1.921973212673957, 5.202035305974182], [1.9091460365842643, 5.328572734260904], [1.9018175916645754, 5.456627934561682], [1.9000141595381308, 5.58605113340452], [1.9037569344843845, 5.716691509368557], [1.9130619977325782, 5.848397347625249], [1.9279402967123431, 5.9810161952853536], [1.948397629283046, 6.114395017396939], [1.9744346329586113, 6.2483803534390345], [2.0060467791397896, 6.3828184741553695], [2.0432243723607266, 6.517555538571822], [2.085952554551967, 6.652437751041486], [2.1342113143168966, 6.787311518160985], [2.18797550121391, 6.922023605402057], [2.247214845031422, 7.056421293302339], [2.3118939800381977, 7.190352533060208], [2.3819724741863793, 7.323666101378873], [2.4574048632398053, 7.456211754405601], [2.5381406897954317, 7.587840380613193], [2.624124547160627, 7.718404152471454], [2.7152961280447316, 7.8477566767581575], [2.8115902780180955, 7.975753143359812], [2.9129370536874637, 8.10225047241445], [3.0192617855316994, 8.227107459650123], [3.130485145337543, 8.350184919774767], [3.246523218170135, 8.471345827774723], [3.3672875788090693, 8.59045545798168], [3.450377193595556, 8.668656846819744]], [[8.051691831483492, 10.630003504019816], [8.254031360710616, 10.648620455286464], [8.456996499367397, 10.662788268223332], [8.66041708029465, 10.672491460703954], [8.864122506713203, 10.677719430167183], [9.067941913142025, 10.678466464333974], [9.271704326578623, 10.674731746981124], [9.475238827784148, 10.666519358766282], [9.678374712516167, 10.653838273103382], [9.880941652551671, 10.636702347092614], [10.082769856343297, 10.615130307513914], [10.283690229152013, 10.589145731897908], [10.48353453250012, 10.558777024693066], [10.682135542788982, 10.524057388552762], [10.87932720892684, 10.485024790770705], [11.07494480881291, 10.441721924898102], [11.268825104525218, 10.394196167580631], [11.460806496060801, 10.342499530658069], [11.650729173478368, 10.286688608574169], [11.838435267295115, 10.226824521148949], [12.023768996991041, 10.162972851770238], [12.206576817476023, 10.095203581065842], [12.386707563376898, 10.023591016122172], [12.564012591003952, 9.948213715319618], [12.738345917858476, 9.869154408859304], [12.909564359545511, 9.786499915060132], [13.077527663958385, 9.700341052509208], [13.242098642604441, 9.610772548152926], [13.40314329894403, 9.517892941419928], [13.560530953617924, 9.42180448447118], [13.714134366441224, 9.322613038676216], [13.86382985504515, 9.220427967418356], [14.0094974100513, 9.115362025335356], [14.151020806666438, 9.007531244105504], [14.288287712589334, 8.897054814892606], [14.421189792124899, 8.784054967566584], [14.549622806404443, 8.668656846819749], [14.67348670961491, 8.55098838530169], [14.792685741143675, 8.431180173898918], [14.90712851354963, 8.309365329288081], [15.01672809627534, 8.18567935889434], [15.121402095019288, 8.06026002338912], [15.221072726691412, 7.933247196863803], [15.31566688987955, 7.804782724818387], [15.405116230758836, 7.675010280106166], [15.489357204380482, 7.544075216977649], [15.568331131281038, 7.412124423368765], [15.64198424935773, 7.279306171580196], [15.710267760960186, 7.145769967496257], [15.773137875153555, 7.011666398493231], [15.830555845112762, 6.877146980188391], [15.882488000612401, 6.742364002182077], [15.928905775581677, 6.607470372946229], [15.96978573069855, 6.472619464013659], [16.005109571002208, 6.33796495362302], [16.034864158507812, 6.20366066997501], [16.059041519812414, 6.0698604342557765], [16.077638848685915, 5.936717903583681], [16.090658503645724, 5.804386414035722], [16.09810800051884, 5.673018823909864], [16.1, 5.542767357379262], [16.096352290219276, 5.413783448694053], [16.087187764337735, 5.286217587085826], [16.072534393194285, 5.160219162529235], [16.052425193032015, 5.035936312514391], [16.026898188336922, 4.9135157699826575], [15.995996369826926, 4.793102712577375], [15.959767647633655, 4.674840613359772], [15.918264799724344, 4.5588710931388405], [15.871545415615815, 4.445333774562434], [15.819671835437038, 4.334366138115074], [15.762711084401541, 4.22610338016614], [15.700734802755239, 4.120678273210053], [15.633819171269808, 4.0182210284379645], [15.562044832356083, 3.91885916077813], [15.485496806876231, 3.822717356539778], [15.404264406737674, 3.729917343792687], [15.318441143355923, 3.6405777656119884], [15.228124632077446, 3.5548140563149735], [15.133416492657766, 3.4727383208136464], [15.034422245893714, 3.394459217203763], [14.931251206512702, 3.320081842707908], [14.824016372425366, 3.2497076230868327], [14.712834310451644, 3.1834342056299105], [14.597825038633719, 3.1213553558319997], [14.4791119052526, 3.063560857860409], [14.356821464668439, 3.010136418911931], [14.231083350107617, 2.9611635775560368], [14.102030143522786, 2.916719616156458], [13.96979724265481, 2.8768774774593355], [13.834522725428354, 2.8417056854320064], [13.696347211815361, 2.8112682704323193], [13.555413723303293, 2.7856246987841278], [13.411867540107192, 2.7648298068302193], [13.265856056266909, 2.7489337395295665], [13.11752863277286, 2.7379818936613027], [12.967036448865652, 2.7320148656932535], [12.814532351656585, 2.7310684043683082], [12.66017070421776, 2.735173368057219], [12.504107232291991, 2.7443556869217254], [12.346498869774006, 2.758636329927196], [12.187503603115646, 2.7780312767391315], [12.027280314808776, 2.802551494533126], [11.865988626100563, 2.8322029197429694], [11.703788739096435, 2.8669864447667637], [11.540841278406596, 2.9068979096459984], [11.377307132492534, 2.9519280987276204], [11.268037935754293, 2.984785121504352]]], "glyphStroke": 1.9, "navy": "#132441", "coral": "#FF8A5B"} \ No newline at end of file diff --git a/scripts/icons/knot.py b/scripts/icons/knot.py new file mode 100755 index 0000000..7f9ce0f --- /dev/null +++ b/scripts/icons/knot.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Generates the knot icons' geometry; run via scripts/make-icons.sh.""" +import json +import math +import os + +ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +NAVY = "#132441" +CORAL = "#FF8A5B" + + +def trefoil(cx, cy, size, gap): + n = 1200 + pts = [] + for i in range(n): + t = 2 * math.pi * i / n + pts.append((math.sin(t) + 2 * math.sin(2 * t), -(math.cos(t) - 2 * math.cos(2 * t)))) + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + mx, my = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 + span = max(max(xs) - min(xs), max(ys) - min(ys)) + p = [(cx + (x - mx) / span * size, cy + (y - my) / span * size) for x, y in pts] + + # Self-crossings: points far apart along the curve that land on each other. + cands = [] + for i in range(n): + for j in range(i + n // 6, n): + if j - i > n - n // 6: + continue + d = (p[i][0] - p[j][0]) ** 2 + (p[i][1] - p[j][1]) ** 2 + if d < (size * 0.004) ** 2: + cands.append((d, i, j)) + cands.sort() + crossings = [] + for _, i, j in cands: + far = lambda a, b: min(abs(a - b) % n, n - abs(a - b) % n) > 40 + if all(far(i, a) and far(j, b) for a, b in crossings): + crossings.append((i, j)) + if len(crossings) == 3: + break + assert len(crossings) == 3, crossings + + # Alternate crossings go under; hide points within `gap` (straight-line) of each. + visits = sorted([i for i, _ in crossings] + [j for _, j in crossings]) + under = visits[1::2] + hidden = set() + for u in under: + for d in range(-n // 10, n // 10 + 1): + k = (u + d) % n + if math.dist(p[k], p[u]) < gap: + hidden.add(k) + + strands, cur = [], [] + start = next(i for i in range(n) if i in hidden) + for k in range(1, n + 1): + i = (start + k) % n + if i in hidden: + if len(cur) > 1: + strands.append(cur) + cur = [] + else: + cur.append(p[i]) + if len(cur) > 1: + strands.append(cur) + assert len(strands) == 3 + return [s[::3] + [s[-1]] for s in strands] + + +def path(strand): + return "M" + " L".join(f"{x:.2f},{y:.2f}" for x, y in strand) + + +# Each gap is half the crossing strand's width, plus the round cap, plus clear space. +ICON_STROKE, GLYPH_STROKE = 8.5, 1.9 +icon = trefoil(50, 51, 62, ICON_STROKE + 1.3) +glyph = trefoil(9, 9.3, 14.2, GLYPH_STROKE + 0.6) + +os.makedirs(os.path.join(ROOT, "assets"), exist_ok=True) +json.dump({"icon": icon, "iconStroke": ICON_STROKE, "glyph": glyph, "glyphStroke": GLYPH_STROKE, + "navy": NAVY, "coral": CORAL}, + open(os.path.join(ROOT, "scripts/icons/knot.json"), "w")) + +strokes = "".join(f'' for s in icon) +open(os.path.join(ROOT, "assets/knotchy-icon.svg"), "w").write( + f'' + f'' + f'{strokes}\n') +open(os.path.join(ROOT, "assets/knotchy-mark.svg"), "w").write( + f'' + f'{strokes}\n') + +points = ",\n".join( + " [" + ", ".join(f"CGPoint(x: {x:.2f}, y: {y:.2f})" for x, y in s) + "]" for s in glyph) +open(os.path.join(ROOT, "Sources/Knotchy/KnotGlyph.swift"), "w").write(f'''// Generated by scripts/icons/knot.py; run scripts/make-icons.sh to change it. +import AppKit + +enum KnotGlyph {{ + static let strands: [[CGPoint]] = [ +{points}, + ] + + static let menuBarImage: NSImage = {{ + let image = NSImage(size: NSSize(width: 18, height: 18), flipped: true) {{ _ in + NSColor.black.setStroke() + for strand in strands {{ + let path = NSBezierPath() + path.move(to: strand[0]) + for point in strand.dropFirst() {{ path.line(to: point) }} + path.lineWidth = 1.9 + path.lineCapStyle = .round + path.lineJoinStyle = .round + path.stroke() + }} + return true + }} + image.isTemplate = true + image.accessibilityDescription = "Knotchy" + return image + }}() +}} +''') +print("knot: 3 strands; wrote knot.json, assets/*.svg, Sources/Knotchy/KnotGlyph.swift") diff --git a/scripts/icons/render.swift b/scripts/icons/render.swift new file mode 100644 index 0000000..11394c6 --- /dev/null +++ b/scripts/icons/render.swift @@ -0,0 +1,183 @@ +// swift scripts/icons/render.swift ; run via scripts/make-icons.sh. +import AppKit +import CoreGraphics +import ImageIO +import UniformTypeIdentifiers + +struct Knot: Decodable { + let icon: [[[Double]]] + let iconStroke: Double +} + +let args = CommandLine.arguments +let here = URL(fileURLWithPath: #filePath).deletingLastPathComponent() +let knot = try JSONDecoder().decode(Knot.self, from: Data(contentsOf: here.appendingPathComponent("knot.json"))) +let out = URL(fileURLWithPath: args.count > 1 ? args[1] : ".") + +func color(_ hex: UInt32, _ alpha: CGFloat = 1) -> CGColor { + CGColor(srgbRed: CGFloat(hex >> 16 & 0xFF) / 255, green: CGFloat(hex >> 8 & 0xFF) / 255, + blue: CGFloat(hex & 0xFF) / 255, alpha: alpha) +} + +/// Apple's icon shape is close to a superellipse with exponent 5. +func squircle(_ r: CGRect) -> CGPath { + let path = CGMutablePath() + let steps = 720 + for i in 0...steps { + let t = Double(i) / Double(steps) * 2 * .pi + let c = cos(t), s = sin(t) + let x = r.midX + r.width / 2 * CGFloat(copysign(pow(abs(c), 2.0 / 5), c)) + let y = r.midY + r.height / 2 * CGFloat(copysign(pow(abs(s), 2.0 / 5), s)) + if i == 0 { path.move(to: CGPoint(x: x, y: y)) } else { path.addLine(to: CGPoint(x: x, y: y)) } + } + path.closeSubpath() + return path +} + +func verticalGradient(_ ctx: CGContext, in rect: CGRect, top: CGColor, bottom: CGColor) { + let gradient = CGGradient(colorsSpace: CGColorSpace(name: CGColorSpace.sRGB), colors: [top, bottom] as CFArray, locations: [0, 1])! + ctx.drawLinearGradient(gradient, start: CGPoint(x: rect.midX, y: rect.minY), end: CGPoint(x: rect.midX, y: rect.maxY), options: []) +} + +/// The knot as drawn on the icon: `box` is the icon's body, and the knot keeps the icon's margins inside it. +func drawKnot(_ ctx: CGContext, in box: CGRect) { + let scale = box.width / 100 + let width = knot.iconStroke * scale + for strand in knot.icon { + let line = CGMutablePath() + for (i, p) in strand.enumerated() { + let point = CGPoint(x: box.minX + p[0] * scale, y: box.minY + p[1] * scale) + if i == 0 { line.move(to: point) } else { line.addLine(to: point) } + } + let stroked = line.copy(strokingWithWidth: width, lineCap: .round, lineJoin: .round, miterLimit: 4) + ctx.saveGState() + ctx.addPath(stroked) + ctx.clip() + verticalGradient(ctx, in: box, top: color(0xFFA37A), bottom: color(0xFF7345)) + ctx.restoreGState() + } +} + +func render(size: Int, body: CGRect, shadow: Bool, to url: URL) { + let s = CGFloat(size) + let ctx = CGContext(data: nil, width: size, height: size, bitsPerComponent: 8, bytesPerRow: 0, + space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)! + ctx.translateBy(x: 0, y: s) + ctx.scaleBy(x: 1, y: -1) + ctx.setShouldAntialias(true) + ctx.interpolationQuality = .high + let shape = squircle(body) + + if shadow { + ctx.saveGState() + ctx.setShadow(offset: CGSize(width: 0, height: s * 0.012), blur: s * 0.028, color: color(0x000000, 0.32)) + ctx.addPath(shape) + ctx.setFillColor(color(0x132441)) + ctx.fillPath() + ctx.restoreGState() + } + + ctx.saveGState() + ctx.addPath(shape) + ctx.clip() + verticalGradient(ctx, in: body, top: color(0x1C3563), bottom: color(0x0D1A33)) + drawKnot(ctx, in: body) + ctx.restoreGState() + + let image = ctx.makeImage()! + let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil)! + CGImageDestinationAddImage(dest, image, nil) + CGImageDestinationFinalize(dest) +} + +// Apple's grid: an 824-point body on a 1024-point canvas. +let iconset = out.appendingPathComponent("AppIcon.iconset") +try FileManager.default.createDirectory(at: iconset, withIntermediateDirectories: true) +for (points, scale) in [(16, 1), (16, 2), (32, 1), (32, 2), (128, 1), (128, 2), (256, 1), (256, 2), (512, 1), (512, 2)] { + let size = points * scale + let inset = CGFloat(size) * 100 / 1024 + let body = CGRect(x: inset, y: inset, width: CGFloat(size) - 2 * inset, height: CGFloat(size) - 2 * inset) + let name = scale == 1 ? "icon_\(points)x\(points).png" : "icon_\(points)x\(points)@2x.png" + render(size: size, body: body, shadow: size >= 64, to: iconset.appendingPathComponent(name)) +} + +// The Chrome Web Store wants 96 points of art with 16 of padding at 128. +for (size, pad) in [(128, 16.0), (48, 2.0), (32, 1.0), (16, 0.0)] { + let body = CGRect(x: pad, y: pad, width: Double(size) - 2 * pad, height: Double(size) - 2 * pad) + render(size: size, body: body, shadow: false, to: out.appendingPathComponent("chrome-\(size).png")) +} +// The disk image window: Finder puts Knotchy at (180, 175) and Applications at (460, 175). +// It's drawn taller than the 400-point window so a slightly taller window never shows an edge, and it's light +// because Finder always draws the file names in black over a background picture, even in dark mode. +func renderBackground(scale: Int, to url: URL) { + let width = 640, height = 440 + let ctx = CGContext(data: nil, width: width * scale, height: height * scale, bitsPerComponent: 8, bytesPerRow: 0, + space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)! + ctx.scaleBy(x: CGFloat(scale), y: CGFloat(scale)) + ctx.translateBy(x: 0, y: CGFloat(height)) + ctx.scaleBy(x: 1, y: -1) + ctx.setFillColor(color(0xF5F5F7)) + ctx.fill(CGRect(x: 0, y: 0, width: width, height: height)) + + let arrow = CGMutablePath() + arrow.move(to: CGPoint(x: 272, y: 175)) + arrow.addLine(to: CGPoint(x: 366, y: 175)) + arrow.move(to: CGPoint(x: 356, y: 165)) + arrow.addLine(to: CGPoint(x: 368, y: 175)) + arrow.addLine(to: CGPoint(x: 356, y: 185)) + ctx.addPath(arrow) + ctx.setStrokeColor(color(0xFF8456)) + ctx.setLineWidth(3) + ctx.setLineCap(.round) + ctx.setLineJoin(.round) + ctx.strokePath() + + NSGraphicsContext.current = NSGraphicsContext(cgContext: ctx, flipped: true) + let caption = NSAttributedString(string: "Drag Knotchy to Applications", attributes: [ + .font: NSFont.systemFont(ofSize: 12, weight: .medium), + .foregroundColor: NSColor(cgColor: color(0x86868B))!, + ]) + let size = caption.size() + caption.draw(at: CGPoint(x: (CGFloat(width) - size.width) / 2, y: 300)) + NSGraphicsContext.current = nil + + let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil)! + CGImageDestinationAddImage(dest, ctx.makeImage()!, [kCGImagePropertyDPIWidth: 72 * scale, kCGImagePropertyDPIHeight: 72 * scale] as CFDictionary) + CGImageDestinationFinalize(dest) +} +renderBackground(scale: 1, to: out.appendingPathComponent("dmg-background.png")) +renderBackground(scale: 2, to: out.appendingPathComponent("dmg-background@2x.png")) +// Chrome Web Store promo images. The store takes 24-bit PNGs only, hence no alpha. +func renderPromo(width: Int, height: Int, to url: URL) { + let w = CGFloat(width), h = CGFloat(height), unit = h / 280 + let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 0, + space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue)! + ctx.translateBy(x: 0, y: h) + ctx.scaleBy(x: 1, y: -1) + verticalGradient(ctx, in: CGRect(x: 0, y: 0, width: w, height: h), top: color(0x1C3563), bottom: color(0x0D1A33)) + + let title = NSAttributedString(string: "Open in Knotchy", attributes: [ + .font: NSFont.systemFont(ofSize: 28 * unit, weight: .bold), + .foregroundColor: NSColor.white, + ]) + let subtitle = NSAttributedString(string: "Open suspicious links in a\ndisposable, isolated browser.", attributes: [ + .font: NSFont.systemFont(ofSize: 14 * unit, weight: .medium), + .foregroundColor: NSColor(white: 1, alpha: 0.72), + ]) + let knotSide = 140 * unit, gap = 14 * unit, lineGap = 8 * unit + let left = (w - knotSide - gap - max(title.size().width, subtitle.size().width)) / 2 + drawKnot(ctx, in: CGRect(x: left, y: (h - knotSide) / 2, width: knotSide, height: knotSide)) + + let top = (h - title.size().height - lineGap - subtitle.size().height) / 2 + NSGraphicsContext.current = NSGraphicsContext(cgContext: ctx, flipped: true) + title.draw(at: CGPoint(x: left + knotSide + gap, y: top)) + subtitle.draw(at: CGPoint(x: left + knotSide + gap, y: top + title.size().height + lineGap)) + NSGraphicsContext.current = nil + + let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil)! + CGImageDestinationAddImage(dest, ctx.makeImage()!, nil) + CGImageDestinationFinalize(dest) +} +renderPromo(width: 440, height: 280, to: out.appendingPathComponent("promo-440x280.png")) +renderPromo(width: 1400, height: 560, to: out.appendingPathComponent("marquee-1400x560.png")) +print("rendered AppIcon.iconset, chrome-{16,32,48,128}.png, dmg-background{,@2x}.png and the store promos into \(out.path)") diff --git a/scripts/make-icons.sh b/scripts/make-icons.sh new file mode 100755 index 0000000..dfa89d9 --- /dev/null +++ b/scripts/make-icons.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Regenerates every icon (app, SVGs, Chrome extension, menu bar glyph) from scripts/icons/knot.py, +# the disk image background and the Chrome Web Store promo images. +set -euo pipefail +root="$(cd "$(dirname "$0")/.." && pwd)" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +python3 "$root/scripts/icons/knot.py" +swift "$root/scripts/icons/render.swift" "$work" +iconutil -c icns "$work/AppIcon.iconset" -o "$root/assets/AppIcon.icns" +for size in 16 32 48 128; do + cp "$work/chrome-$size.png" "$root/extensions/chrome/icons/icon-$size.png" +done +mkdir -p "$root/extensions/chrome/store" +cp "$work/promo-440x280.png" "$work/marquee-1400x560.png" "$root/extensions/chrome/store/" +tiffutil -cathidpicheck "$work/dmg-background.png" "$work/dmg-background@2x.png" -out "$root/assets/dmg-background.tiff" +echo "Wrote assets/AppIcon.icns, assets/dmg-background.tiff, extensions/chrome/icons/ and extensions/chrome/store/" diff --git a/scripts/package-extension.sh b/scripts/package-extension.sh new file mode 100755 index 0000000..a1de7df --- /dev/null +++ b/scripts/package-extension.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Zips extensions/chrome for the Chrome Web Store: build/open-in-knotchy-.zip. +set -euo pipefail +root="$(cd "$(dirname "$0")/.." && pwd)" +cd "$root/extensions/chrome" +version="$(plutil -extract version raw manifest.json)" +out="$root/build/open-in-knotchy-$version.zip" +mkdir -p "$root/build" +rm -f "$out" +zip -q -r -X "$out" manifest.json background.js icons -x '*.DS_Store' +echo "$out" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..ed79c24 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Builds a signed, notarized build/Knotchy-.dmg for a GitHub release. +# Needs a Developer ID Application certificate and notarization credentials saved once with +# xcrun notarytool store-credentials knotchy --apple-id --team-id +set -euo pipefail +root="$(cd "$(dirname "$0")/.." && pwd)" +cd "$root" +profile="${KNOTCHY_NOTARY_PROFILE:-knotchy}" +identity="${KNOTCHY_SIGN_IDENTITY:-$(security find-identity -v -p codesigning | awk '/"Developer ID Application: / && !id { id = $2 } END { print id }')}" +if [[ -z $identity ]]; then + echo "A release needs a Developer ID Application certificate in the keychain." >&2 + exit 1 +fi +if [[ -e /Volumes/Knotchy ]]; then + echo "Eject the mounted Knotchy disk first; Finder finds the new one by name." >&2 + exit 1 +fi + +notarize() { + local result status + result="$(xcrun notarytool submit "$1" --keychain-profile "$profile" --wait --output-format json)" + status="$(plutil -extract status raw - <<<"$result")" + if [[ $status != Accepted ]]; then + xcrun notarytool log "$(plutil -extract id raw - <<<"$result")" --keychain-profile "$profile" >&2 || true + echo "Notarization of $1 failed: $status" >&2 + exit 1 + fi +} + +KNOTCHY_SIGN_IDENTITY="$identity" scripts/bundle-app.sh --release +app="build/Knotchy.app" +version="$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' "$app/Contents/Info.plist")" + +# The app gets its own ticket so it still opens offline once it's copied out of the disk image. +ditto -c -k --keepParent "$app" build/Knotchy.zip +notarize build/Knotchy.zip +rm build/Knotchy.zip +xcrun stapler staple -q "$app" + +dmg="build/Knotchy-$version.dmg" +rw="build/Knotchy-rw.dmg" +stage="build/dmg" +rm -rf "$stage" "$dmg" "$rw" +mkdir -p "$stage/.background" +ditto "$app" "$stage/Knotchy.app" +ln -s /Applications "$stage/Applications" +cp assets/dmg-background.tiff "$stage/.background/background.tiff" +hdiutil create -quiet -volname Knotchy -fs HFS+ -format UDRW -size "$(( $(du -sm "$stage" | cut -f1) + 20 ))m" \ + -srcfolder "$stage" "$rw" +rm -rf "$stage" + +mnt="$(hdiutil attach -readwrite -noverify -noautoopen "$rw" | awk -F'\t' '/\/Volumes\// { print $NF }')" +trap 'hdiutil detach -quiet -force "$mnt" 2>/dev/null || true' EXIT +# Finder saves the window layout into the disk's .DS_Store; macOS asks once to let this script control Finder. +osascript - <<'EOF' +tell application "Finder" + tell disk "Knotchy" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set bounds of container window to {200, 120, 840, 548} + set viewOptions to icon view options of container window + set arrangement of viewOptions to not arranged + set icon size of viewOptions to 128 + set text size of viewOptions to 13 + set background picture of viewOptions to file ".background:background.tiff" + set position of item "Knotchy.app" of container window to {180, 175} + set position of item "Applications" of container window to {460, 175} + close + open + update without registering applications + delay 1 + close + end tell +end tell +EOF +for _ in {1..20}; do + [[ -f $mnt/.DS_Store ]] && break + sleep 0.5 +done +# Added after Finder is done, since laying out the window deletes the icon and clears the custom-icon flag. +cp assets/AppIcon.icns "$mnt/.VolumeIcon.icns" +xattr -wx com.apple.FinderInfo 0000000000000000040000000000000000000000000000000000000000000000 "$mnt" +rm -rf "$mnt/.fseventsd" +sync +hdiutil detach -quiet "$mnt" || { sleep 2; hdiutil detach -quiet -force "$mnt"; } +trap - EXIT +hdiutil convert -quiet "$rw" -format UDZO -imagekey zlib-level=9 -o "$dmg" +rm "$rw" +codesign --sign "$identity" --timestamp "$dmg" +notarize "$dmg" +xcrun stapler staple -q "$dmg" + +spctl --assess --type open --context context:primary-signature --verbose "$dmg" +shasum -a 256 "$dmg" diff --git a/scripts/route-server/Containerfile b/scripts/route-server/Containerfile new file mode 100644 index 0000000..be3e986 --- /dev/null +++ b/scripts/route-server/Containerfile @@ -0,0 +1,8 @@ +# Test fixture for scripts/test-routes.sh: HTTP and SOCKS5 proxies and a WireGuard server. +FROM debian:trixie-slim +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + tinyproxy microsocks wireguard-go wireguard-tools iproute2 nftables && \ + rm -rf /var/lib/apt/lists/* +COPY --chmod=755 serve.sh /usr/local/bin/serve.sh +ENTRYPOINT ["/usr/local/bin/serve.sh"] diff --git a/scripts/route-server/serve.sh b/scripts/route-server/serve.sh new file mode 100644 index 0000000..8c8f312 --- /dev/null +++ b/scripts/route-server/serve.sh @@ -0,0 +1,49 @@ +#!/bin/sh +set -eu +me=$(ip -4 -o address show dev eth0 | awk '{ split($4, a, "/"); print a[1]; exit }') + +cat > /tmp/tinyproxy.conf < /tmp/microsocks.log 2>&1 & + +umask 077 +wg genkey > /tmp/server.key +wg pubkey < /tmp/server.key > /tmp/server.pub +wg genkey > /tmp/client.key +wg pubkey < /tmp/client.key > /tmp/client.pub +WG_I_PREFER_BUGGY_USERSPACE_TO_POLISHED_KMOD=1 wireguard-go wg0 +wg set wg0 listen-port 51820 private-key /tmp/server.key \ + peer "$(cat /tmp/client.pub)" allowed-ips 10.99.0.2/32 +ip address add 10.99.0.1/24 dev wg0 +ip link set dev wg0 up +echo 1 > /proc/sys/net/ipv4/ip_forward +nft -f - < /tmp/client.conf < so the app's orphan sweep ignores them. +set -euo pipefail +name="${1:?usage: run-vm.sh NAME URL}" +url="${2:?usage: run-vm.sh NAME URL}" +if [[ ! "$name" =~ ^[a-z0-9][a-z0-9-]{0,40}$ ]]; then + echo "run-vm.sh: invalid VM name '$name' (lowercase letters, digits, dashes)" >&2; exit 2 +fi +if [[ "$name" =~ ^knotchy-[0-9a-f]{8}$ ]]; then + echo "run-vm.sh: '$name' uses the app's naming; use knotchyctl-<8 hex>" >&2; exit 2 +fi + +tmp="${TMPDIR:-/tmp}" +run_dir="${tmp%/}/knotchy" +mkdir -p -m 700 "$run_dir" +if [[ ! -d "$run_dir" || -L "$run_dir" || ! -O "$run_dir" ]]; then + echo "run-vm.sh: refusing unsafe socket directory $run_dir" >&2; exit 1 +fi +chmod 700 "$run_dir" +sock="$run_dir/$name.sock" +rm -f "$sock" + +# The Mac's networks may be public addresses, so the VM blocks them explicitly. +block=$(python3 - <<'PY' +import ipaddress, re, subprocess +out = subprocess.run(["ifconfig"], capture_output=True, text=True).stdout +nets = set() +for ip, mask in re.findall(r"inet (\d+\.\d+\.\d+\.\d+)(?: --> \S+)? netmask 0x([0-9a-f]{8})", out): + if ip.startswith("127."): + continue + net = ipaddress.ip_network(f"{ip}/{bin(int(mask, 16)).count('1')}", strict=False) + if net.prefixlen >= 8: + nets.add(str(net)) +print(" ".join(sorted(nets))) +PY +) +if [[ -z "$block" ]]; then + echo "run-vm.sh: warning: found no Mac IPv4 networks to block" >&2 +fi + +container run -d --rm --name "$name" \ + --cpus 4 --memory 2g --shm-size 512m \ + --dns 1.1.1.1 \ + --cap-add NET_ADMIN \ + --masked-path NONE --read-only-path NONE \ + --publish-socket "$sock:/tmp/knotchy/knotchy.sock" \ + -e "KNOTCHY_URL=$url" \ + -e "KNOTCHY_SIZE=1024x640" \ + -e "KNOTCHY_BLOCK_CIDRS=$block" \ + -e "KNOTCHY_DEBUG=${KNOTCHY_DEBUG:-0}" \ + -e "KNOTCHY_FIRST_VIEWER_TIMEOUT=${KNOTCHY_FIRST_VIEWER_TIMEOUT:-60}" \ + ${KNOTCHY_ENV_FILE:+--env-file "$KNOTCHY_ENV_FILE"} \ + ${KNOTCHY_SECRETS_DIR:+--cap-add SYS_ADMIN --mount "type=bind,source=$KNOTCHY_SECRETS_DIR,target=/run/knotchy-secrets"} \ + local/knotchy-vm:latest >/dev/null +echo "$sock" diff --git a/scripts/test-egress.sh b/scripts/test-egress.sh new file mode 100755 index 0000000..484f916 --- /dev/null +++ b/scripts/test-egress.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env bash +# Isolation regression test: from inside a VM, as the browser's user, the Mac, the +# LAN, link-local and vmnet DNS are rejected and the internet isn't; IPv6 stays shut; +# PID 1 is unprivileged. Skips fail unless KNOTCHY_EGRESS_ALLOW_SKIP=1. Bash 3.2-safe. +set -euo pipefail +root="$(cd "$(dirname "$0")/.." && pwd)" +name="knotchyctl-$(openssl rand -hex 4)" +work="$(mktemp -d)" +n_ok=0 n_fail=0 n_skip=0 skipped="" +http_pid="" udp_pid="" + +# macOS has no timeout(1); rc 142 on timeout. The command's stderr goes via fd 3, +# so only bash's "Alarm clock" is discarded. +deadline() { + { ( perl -e 'alarm shift; exec @ARGV or die "exec $ARGV[0]: $!\n"' "$@" 2>&3 3>&-; exit $? ) 2>/dev/null; } 3>&2 +} +cx() { deadline 20 container exec "$@"; } + +cleanup() { + trap '' INT TERM + for pid in $http_pid $udp_pid; do + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + done + deadline 30 container stop "$name" >/dev/null 2>&1 || true + deadline 30 container delete --force "$name" >/dev/null 2>&1 || true + rm -rf "$work" +} +trap cleanup EXIT +# `container exec` swallows ^C and exits 0; don't carry on to "all checks passed". +trap 'exit 130' INT +trap 'exit 143' TERM + +ok() { printf " ok %s\n" "$1"; n_ok=$((n_ok + 1)); } +bad() { printf " FAIL %s\n" "$1"; n_fail=$((n_fail + 1)); } +skip() { printf " skip %s\n" "$1"; n_skip=$((n_skip + 1)); skipped="$skipped - $1"$'\n'; } +check() { + if [[ "$2" == "$3" ]]; then ok "$1: $2"; else bad "$1: got '$2', want '$3'"; fi +} +summary() { + printf "\ntest-egress: %d ok, %d fail, %d skip\n" "$n_ok" "$n_fail" "$n_skip" + if (( n_skip )); then printf "test-egress: skipped:\n%s" "$skipped"; fi + if (( n_skip )) && [[ "${KNOTCHY_EGRESS_ALLOW_SKIP:-}" != 1 ]]; then + echo "test-egress: skipped checks count as failures (KNOTCHY_EGRESS_ALLOW_SKIP=1 allows them)" + exit 1 + fi + if (( n_fail )); then exit 1; fi + echo "test-egress: all checks passed" +} +poll() { + local i + for ((i = 0; i < $1 * 2; i++)); do "${@:2}" && return 0; sleep 0.5; done + return 1 +} +# Blocked means rc 1 plus the nft reject's error: TCP/IPv4 "No route to host", TCP/IPv6 +# "Permission denied", UDP "Operation not permitted". With IPv6 disabled nft never sees +# the packet: blocked-no-ipv6. Anything else (timeout, refused, exec error) fails, so a +# Mac firewall that drops SYNs can't pass for the VM's. +signature() { + [[ "$1" == 1 && "$2" == *"$3"* ]] && why=$3 +} +classify_tcp() { + cls=inconclusive why="" + if [[ "$1" == 0 ]]; then cls=reachable + elif [[ "$3" != *:* ]]; then + if signature "$1" "$2" "connect: No route to host"; then cls=blocked; fi + elif signature "$1" "$2" "connect: Permission denied"; then cls=blocked + elif signature "$1" "$2" "connect: Cannot assign requested address" || + signature "$1" "$2" "connect: Network is unreachable"; then cls=blocked-no-ipv6 + fi +} +classify_udp() { + cls=inconclusive why="" + if [[ "$1" == 0 ]]; then cls=reachable + elif signature "$1" "$2" "write error: Operation not permitted"; then cls=blocked + fi +} +verdict() { # verdict EXPECT LABEL RC OUTPUT (EXPECT may be "a|b") + case "|$1|" in + *"|$cls|"*) ok "$cls${why:+ ($why)}: $2" ;; + *) local err; err=$(printf '%s' "$4" | tr '\n' ' ') + bad "$cls (expected ${1//|/ or }): $2 [rc $3${err:+: ${err:0:240}}]" ;; + esac +} +tcp() { + local rc=0 out + if [[ -z "$3" ]]; then skip "$2 (address unknown)"; return; fi + out=$(cx -u knotchy "$name" timeout 3 bash -c \ + "exec 3<>/dev/tcp/$3/$4 && printf 'GET / HTTP/1.0\r\n\r\n' >&3" 2>&1) || rc=$? + classify_tcp "$rc" "$out" "$3" + verdict "$1" "$2" "$rc" "$out" +} +udp() { + local rc=0 out + if [[ -z "$3" ]]; then skip "$2 (address unknown)"; return; fi + out=$(cx -u knotchy "$name" timeout 3 bash -c "printf x > /dev/udp/$3/$4" 2>&1) || rc=$? + classify_udp "$rc" "$out" + verdict "$1" "$2" "$rc" "$out" +} + +# Append mode, so truncating a log doesn't leave the writer at a stale offset. +hport=$(python3 -c 'import socket; s = socket.socket(socket.AF_INET6); s.bind(("::", 0)); print(s.getsockname()[1])') +echo "MAC-REACHED" > "$work/index.html" +(cd "$work" && exec python3 -u -m http.server "$hport" --bind :: >> "$work/http.log" 2>&1) & +http_pid=$! +python3 -u -c ' +import socket +s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) +s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) +s.bind(("::", 0)) +print("READY", s.getsockname()[1]) +while True: + data, addr = s.recvfrom(2048) + print("DGRAM", addr[0], data[:64]) +' >> "$work/udp.log" 2>&1 & +udp_pid=$! +http_up() { curl -fsS --noproxy '*' -o /dev/null --max-time 2 "http://127.0.0.1:$hport/" 2>/dev/null; } +udp_up() { uport=$(awk '$1 == "READY" {print $2}' "$work/udp.log"); [[ -n "$uport" ]]; } +if ! poll 10 http_up || ! poll 10 udp_up; then bad "Mac-side HTTP server / UDP listener did not start"; summary; fi + +# Before the address lookups: the vmnet bridge only exists while a VM runs. +if ! out=$(KNOTCHY_FIRST_VIEWER_TIMEOUT=300 "$root/scripts/run-vm.sh" "$name" about:blank 2>&1 >/dev/null); then + printf '%s\n' "$out"; bad "could not start $name"; summary +fi +streaming() { [[ "$(deadline 10 container logs "$name" 2>/dev/null)" == *"streaming on"* ]]; } +if ! poll 30 streaming; then bad "$name did not log 'streaming on' within 30 s"; summary; fi + +gw4=$(container network inspect default | python3 -c 'import json, sys; print(json.load(sys.stdin)[0]["status"]["ipv4Gateway"])') +bridge="" +for i in $(ifconfig -l); do + if ifconfig "$i" | grep -F "inet $gw4 " >/dev/null; then bridge=$i; break; fi +done +# The bridge's fd… address appears ~15 s after the VM starts, tentative at first. +bridge_v6() { + gw6=$(ifconfig "$bridge" 2>/dev/null | + awk '$1 == "inet6" && $2 ~ /^fd/ && $0 !~ / tentative/ && !n++ {print $2}') + [[ -n "$gw6" ]] +} +gw6="" +if [[ -n "$bridge" ]]; then poll 30 bridge_v6 || true; fi +mac4=$( (echo "$gw4"; ifconfig | awk '$1 == "inet" && $2 !~ /^127\./ {print $2}') | sort -u) +router=$(route -n get default 2>/dev/null | awk '/gateway:/ {print $2}' || true) +echo "Targets:" +echo " Mac IPv4:$(ifconfig | awk '/^[^ \t]/ {i = substr($1, 1, length($1) - 1)} + $1 == "inet" && $2 !~ /^127\./ {printf " %s (%s)", $2, i}')" +echo " VM gateway $gw4 on ${bridge:-?}, bridge IPv6 ${gw6:-none}, router ${router:-none}" +echo " HTTP port $hport, UDP port $uport" + +# Otherwise "blocked" below could just mean nothing was listening. +echo "Control (from the Mac, each target must answer):" +udp_seen() { grep -F "'ctl-$1'" "$work/udp.log" >/dev/null; } +for ip in $mac4; do + if curl -fsS --noproxy '*' -o /dev/null --max-time 3 "http://$ip:$hport/" 2>/dev/null; then + ok "HTTP answers on $ip:$hport" + else bad "HTTP does not answer on $ip:$hport"; fi + { printf "ctl-%s" "$ip" > "/dev/udp/$ip/$uport"; } 2>/dev/null || true + if poll 2 udp_seen "$ip"; then ok "UDP listener receives on $ip:$uport" + else bad "UDP listener got nothing sent to $ip:$uport"; fi +done +if [[ -z "$gw6" ]]; then + skip "HTTP on the bridge IPv6 (no settled fd… address on ${bridge:-the vmnet bridge} within 30 s)" +elif curl -g -fsS --noproxy '*' -o /dev/null --max-time 3 "http://[$gw6]:$hport/" 2>/dev/null; then + ok "HTTP answers on [$gw6]:$hport" +else bad "HTTP does not answer on [$gw6]:$hport"; fi +if (( n_fail )); then echo "test-egress: control checks failed; the probes would be meaningless"; summary; fi +: > "$work/http.log" +: > "$work/udp.log" + +echo "Phase A, IPv6 disabled; from inside $name as the browser user:" +for ip in $mac4; do tcp blocked "TCP Mac $ip:$hport" "$ip" "$hport"; done +tcp blocked "TCP LAN router ${router:-?}:80" "$router" 80 +tcp blocked "TCP cloud metadata 169.254.169.254:80" 169.254.169.254 80 +tcp reachable "TCP internet 1.1.1.1:443" 1.1.1.1 443 +tcp reachable "TCP internet example.com:443" example.com 443 +for ip in $mac4; do udp blocked "UDP Mac $ip:$uport" "$ip" "$uport"; done +udp blocked "UDP vmnet DNS $gw4:53" "$gw4" 53 +udp blocked "UDP LAN router ${router:-?}:53" "$router" 53 +udp blocked "UDP cloud metadata 169.254.169.254:53" 169.254.169.254 53 +udp reachable "UDP internet DNS 1.1.1.1:53" 1.1.1.1 53 +tcp "blocked|blocked-no-ipv6" "TCP Mac IPv6 [${gw6:-?}]:$hport" "$gw6" "$hport" +tcp "blocked|blocked-no-ipv6" "TCP internet IPv6 [2606:4700:4700::1111]:443" 2606:4700:4700::1111 443 + +echo "DNS and IPv6 state:" +check "resolv.conf nameservers" \ + "$(cx "$name" awk '$1 == "nameserver" {print $2}' /etc/resolv.conf 2>&1 | paste -sd ' ' -)" "1.1.1.1" +if cx -u knotchy "$name" getent ahostsv4 example.com >/dev/null 2>&1; then + ok "browser user resolves example.com" +else bad "browser user cannot resolve example.com"; fi +check "all/disable_ipv6" "$(cx "$name" cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>&1)" 1 + +echo "Privileges of the browser process (PID 1):" +status=$(cx "$name" cat /proc/1/status 2>&1 || true) +field() { awk -v f="$1:" '$1 == f {$1 = ""; print substr($0, 2)}' <<<"$status"; } +check "Name" "$(field Name)" knotchy-agent +check "Uid (real effective saved fs)" "$(field Uid)" "1000 1000 1000 1000" +check "Gid (real effective saved fs)" "$(field Gid)" "1000 1000 1000 1000" +for c in CapInh CapPrm CapEff CapBnd CapAmb; do check "$c" "$(field $c)" 0000000000000000; done +check "NoNewPrivs" "$(field NoNewPrivs)" 1 +rc=0 +out=$(cx -u knotchy "$name" nft flush ruleset 2>&1) || rc=$? +if (( rc != 0 )) && [[ "$out" == *"Operation not permitted"* ]]; then + ok "browser user cannot flush the firewall (Operation not permitted)" +else bad "nft flush ruleset as the browser user: rc $rc: $(printf '%s' "$out" | tr '\n' ' ')"; fi + +# IPv6 back on so probes reach nft, and NDP let through so SLAAC can assign an address. +echo "Phase B, IPv6 re-enabled by root (proves the nft IPv6 rule):" +gw6_a=$gw6 +if [[ -n "$bridge" ]]; then poll 30 bridge_v6 || true; fi +if [[ "$gw6" != "$gw6_a" ]]; then echo " bridge IPv6 is now ${gw6:-none}"; fi +vm_v6() { # not link-local (scope 20), not tentative (0x40) + local addr idx plen scope flags dev + while read -r addr idx plen scope flags dev; do + if [[ "$dev" == eth0 && "$scope" != 20 ]] && (( (0x$flags & 0x40) == 0 )); then + vm6=$addr; return 0 + fi + done <<<"$(cx "$name" cat /proc/net/if_inet6 2>/dev/null || true)" + return 1 +} +vm6="" rc=0 +out=$(cx "$name" sh -c ' + nft insert rule inet knotchy output icmpv6 type { nd-router-solicit, nd-neighbor-solicit, nd-neighbor-advert } accept && + nft insert rule inet knotchy input icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept && + echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6 && echo 0 > /proc/sys/net/ipv6/conf/eth0/disable_ipv6' 2>&1) || rc=$? +if (( rc != 0 )); then + bad "root could not open NDP and re-enable IPv6: rc $rc: $(printf '%s' "$out" | tr '\n' ' ')" +elif ! poll 15 vm_v6; then + skip "no routable IPv6 address on eth0 within 15 s; the nft IPv6 rule is unproven" +else + ok "eth0 has IPv6 $(sed 's/..../&:/g; s/:$//' <<<"$vm6")" + tcp blocked "TCP Mac IPv6 [${gw6:-?}]:$hport" "$gw6" "$hport" + tcp blocked "TCP internet IPv6 [2606:4700:4700::1111]:443" 2606:4700:4700::1111 443 + udp blocked "UDP Mac IPv6 [${gw6:-?}]:$uport" "$gw6" "$uport" + udp blocked "UDP internet IPv6 [2606:4700:4700::1111]:53" 2606:4700:4700::1111 53 +fi + +echo "Mac-side evidence:" +evidence() { + local hits + hits=$(grep -cF "$3" "$2" || true) + if [[ "$hits" == 0 ]]; then ok "$1 received nothing from the VM"; return; fi + bad "$1 received $hits from the VM:" + grep -F "$3" "$2" | sed 's/^/ /' +} +evidence "HTTP server" "$work/http.log" '"GET ' +evidence "UDP listener" "$work/udp.log" 'DGRAM ' + +summary diff --git a/scripts/test-routes.sh b/scripts/test-routes.sh new file mode 100755 index 0000000..4cc9ab5 --- /dev/null +++ b/scripts/test-routes.sh @@ -0,0 +1,189 @@ +#!/usr/bin/env bash +# Proxy and VPN route test (vm/knotchy-init): each route lets out only what it should, +# secrets stay out of the environment and `container inspect`, and pages load through +# knotchyctl. Bash 3.2-safe. Needs local/knotchy-vm:latest and the fixture: +# container build --dns 1.1.1.1 -t local/knotchy-route-server scripts/route-server +set -euo pipefail +root="$(cd "$(dirname "$0")/.." && pwd)" +server="knotchyctl-$(openssl rand -hex 4)" +vm="" +work="$(mktemp -d)" +n_ok=0 n_fail=0 + +deadline() { + { ( perl -e 'alarm shift; exec @ARGV or die "exec $ARGV[0]: $!\n"' "$@" 2>&3 3>&-; exit $? ) 2>/dev/null; } 3>&2 +} +cleanup() { + trap '' INT TERM + for name in $vm $server; do + deadline 30 container stop "$name" >/dev/null 2>&1 || true + deadline 30 container delete --force "$name" >/dev/null 2>&1 || true + done + rm -rf "$work" +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +ok() { printf " ok %s\n" "$1"; n_ok=$((n_ok + 1)); } +bad() { printf " FAIL %s\n" "$1"; n_fail=$((n_fail + 1)); } +summary() { + printf "\ntest-routes: %d ok, %d fail\n" "$n_ok" "$n_fail" + if (( n_fail )); then exit 1; fi + echo "test-routes: all checks passed" +} +poll() { + local i + for ((i = 0; i < $1 * 2; i++)); do "${@:2}" && return 0; sleep 0.5; done + return 1 +} + +# probe USER VM tcp|udp HOST PORT -> "reachable", "blocked" (the nft reject), +# "unreachable" (no route) or "other: …" +probe() { + local rc=0 out cmd + if [[ "$3" == tcp ]]; then cmd="exec 3<>/dev/tcp/$4/$5 && printf 'GET / HTTP/1.0\r\n\r\n' >&3" + else cmd="printf x > /dev/udp/$4/$5"; fi + out=$(deadline 20 container exec -u "$1" "$2" timeout 3 bash -c "$cmd" 2>&1) || rc=$? + if [[ $rc == 0 ]]; then echo reachable + elif [[ $rc == 1 && ( "$out" == *"No route to host"* || "$out" == *"Operation not permitted"* ) ]]; then echo blocked + elif [[ $rc == 1 && "$out" == *"Network is unreachable"* ]]; then echo unreachable + else echo "other: rc $rc $(printf '%s' "$out" | tr '\n' ' ' | cut -c1-160)"; fi +} +expect() { # expect "WANT|WANT2" LABEL USER tcp|udp HOST PORT + local got + got=$(probe "$3" "$vm" "$4" "$5" "$6") + case "|$1|" in *"|$got|"*) ok "$got: $2" ;; *) bad "$got (expected ${1//|/ or }): $2" ;; esac +} + +start_vm() { + vm="knotchyctl-$(openssl rand -hex 4)" + if ! KNOTCHY_ENV_FILE="$1" KNOTCHY_SECRETS_DIR="${2:-}" KNOTCHY_FIRST_VIEWER_TIMEOUT=300 \ + "$root/scripts/run-vm.sh" "$vm" about:blank >/dev/null; then + bad "could not start $vm"; summary + fi + streaming() { [[ "$(deadline 10 container logs "$vm" 2>/dev/null)" == *"streaming on"* ]]; } + if ! poll 30 streaming; then + bad "$vm did not start streaming"; deadline 10 container logs "$vm" 2>&1 | tail -5; summary + fi +} +stop_vm() { + deadline 30 container stop "$vm" >/dev/null 2>&1 || true + deadline 30 container delete --force "$vm" >/dev/null 2>&1 || true + vm="" +} + +if ! deadline 60 container run -d --rm --name "$server" --dns 1.1.1.1 --cap-add NET_ADMIN \ + --masked-path NONE --read-only-path NONE local/knotchy-route-server:latest >/dev/null; then + bad "could not start the route server (build it: see the top of this script)"; summary +fi +ready() { [[ "$(deadline 10 container logs "$server" 2>/dev/null)" == *"route-server ready"* ]]; } +if ! poll 30 ready; then bad "route server did not come up"; summary; fi +srv=$(deadline 10 container logs "$server" | awk '/route-server ready/ {print $NF}') +gw4=$(container network inspect default | python3 -c 'import json, sys; print(json.load(sys.stdin)[0]["status"]["ipv4Gateway"])') +echo "Route server $srv, Mac (vmnet gateway) $gw4" + +echo "Proxy route (HTTP proxy $srv:8888):" +printf 'KNOTCHY_ROUTE=proxy\nKNOTCHY_PROXY_ADDR=%s:8888\n' "$srv" > "$work/proxy.env" +mkdir -m 700 "$work/secrets" +printf 'KNOTCHY_PROXY_URL=http://knotchy:s3cr3t@%s:8888\n' "$srv" > "$work/secrets/route" +start_vm "$work/proxy.env" "$work/secrets" +if [[ ! -e "$work/secrets/route" ]]; then ok "the VM deleted the secrets file"; else bad "the secrets file is still on the Mac"; fi +if ! deadline 10 container exec "$vm" grep -q knotchy-secrets /proc/mounts; then ok "the secrets share is unmounted" +else bad "the secrets share is still mounted"; fi +if ! deadline 10 container inspect "$vm" | grep -q s3cr3t; then ok "container inspect doesn't show the password" +else bad "container inspect shows the password"; fi +capbnd=$(deadline 10 container exec "$vm" awk '/^CapBnd/ {print $2}' /proc/1/status) +if [[ "$capbnd" == 0000000000000000 ]]; then ok "the browser has no capabilities (SYS_ADMIN dropped)"; else bad "CapBnd $capbnd"; fi +expect reachable "the proxy port" knotchy tcp "$srv" 8888 +expect blocked "the fixture's other port" knotchy tcp "$srv" 1080 +expect blocked "the internet directly (1.1.1.1:443)" knotchy tcp 1.1.1.1 443 +expect blocked "DNS directly (1.1.1.1:53)" knotchy udp 1.1.1.1 53 +expect blocked "the Mac" knotchy tcp "$gw4" 22 +environ=$(deadline 10 container exec -u knotchy "$vm" cat /proc/1/environ | tr '\0' '\n') +if [[ "$environ" == *"https_proxy=http://knotchy:s3cr3t@$srv:8888"* && "$environ" != *KNOTCHY_PROXY* ]]; then + ok "the browser gets https_proxy and none of the KNOTCHY_PROXY_* variables" +else bad "the browser's environment is wrong"; fi +stop_vm + +echo "WireGuard route (server $srv:51820):" +client=$(deadline 10 container exec "$server" cat /tmp/client.conf) +key=$(awk '$1 == "PrivateKey" {print $3}' <<< "$client") +pub=$(awk '$1 == "PublicKey" {print $3}' <<< "$client") +setconf=$(printf '[Interface]\nPrivateKey = %s\n\n[Peer]\nPublicKey = %s\nEndpoint = %s:51820\nAllowedIPs = 0.0.0.0/0\nPersistentKeepalive = 25\n' "$key" "$pub" "$srv" | base64 | tr -d '\n') +# A private resolver on the VPN side, as Mullvad and Proton VPN use. +printf 'KNOTCHY_ROUTE=wireguard\nKNOTCHY_WG_SETCONF=%s\nKNOTCHY_WG_ENDPOINT=%s:51820\nKNOTCHY_WG_ADDRESS=10.99.0.2/32\nKNOTCHY_WG_DNS=10.99.0.1\nKNOTCHY_WG_MTU=1420\n' \ + "$setconf" "$srv" > "$work/wg.env" +start_vm "$work/wg.env" +route=$(deadline 10 container exec "$vm" ip -4 route get 1.1.1.1) +if [[ "$route" == *"dev wg0"* ]]; then ok "the internet routes through wg0"; else bad "the internet routes: $route"; fi +before=$(deadline 10 container exec "$server" wg show wg0 transfer | awk '{print $2}') +expect reachable "the internet through the tunnel (1.1.1.1:443)" knotchy tcp 1.1.1.1 443 +after=$(deadline 10 container exec "$server" wg show wg0 transfer | awk '{print $2}') +if [[ -n "$after" && "${after:-0}" -gt "${before:-0}" ]]; then ok "the server saw the traffic ($before -> $after bytes)" +else bad "the server's received bytes didn't grow ($before -> $after)"; fi +expect blocked "the server's other port, outside the tunnel" knotchy tcp "$srv" 8888 +expect blocked "the endpoint, for the browser's user" knotchy udp "$srv" 51820 +expect blocked "the Mac" knotchy tcp "$gw4" 22 +expect blocked "a private address on the VPN side (10.99.0.2 -> 10.99.0.1:22)" knotchy tcp 10.99.0.1 22 +expect reachable "DNS to the VPN's resolver (10.99.0.1:53)" knotchy udp 10.99.0.1 53 +expect blocked "another port on the VPN's resolver (10.99.0.1:80)" knotchy tcp 10.99.0.1 80 +resolv=$(deadline 10 container exec "$vm" cat /etc/resolv.conf) +if [[ "$resolv" == "nameserver 10.99.0.1" ]]; then ok "resolv.conf names the VPN's DNS only"; else bad "resolv.conf: $resolv"; fi +environ=$(deadline 10 container exec -u knotchy "$vm" cat /proc/1/environ | tr '\0' '\n') +if [[ "$environ" != *KNOTCHY_WG* ]]; then ok "the browser doesn't get the WireGuard keys"; else bad "KNOTCHY_WG_* reached the browser"; fi +deadline 10 container exec "$vm" pkill wireguard-go || true +sleep 1 +expect "blocked|unreachable" "the internet with the tunnel gone" knotchy tcp 1.1.1.1 443 +via=$(deadline 10 container exec "$vm" ip -4 route get "$srv" | awk '{for (i = 1; i < NF; i++) if ($i == "via") print $(i + 1)}') +deadline 10 container exec "$vm" ip -4 route replace default via "${via:-$gw4}" dev eth0 || true +expect blocked "the internet over a restored direct route" knotchy tcp 1.1.1.1 443 +stop_vm + +echo "Missing route settings:" +for route in proxy wireguard; do + printf 'KNOTCHY_ROUTE=%s\n' "$route" > "$work/broken.env" + rc=0 + out=$(deadline 60 container run --rm --dns 1.1.1.1 --cap-add NET_ADMIN --masked-path NONE --read-only-path NONE \ + --env-file "$work/broken.env" -e KNOTCHY_URL=about:blank local/knotchy-vm:latest 2>&1) || rc=$? + if [[ $rc != 0 && "$out" == *"knotchy-init: "* && "$out" != *"streaming on"* ]]; then + ok "a $route route without its settings stops the VM ($(grep -o 'knotchy-init: .*' <<< "$out" | head -1))" + else bad "a $route route without its settings didn't stop the VM (rc $rc)"; fi +done + +# The app refuses a proxy on the local network without --allow-local-proxy. +echo "End to end (knotchyctl):" +swift build --package-path "$root" --product knotchyctl >/dev/null +ctl="$(swift build --package-path "$root" --product knotchyctl --show-bin-path)/knotchyctl" +snap() { + if deadline 120 "$ctl" snapshot https://example.com "$2" --after 2 "${@:3}" >/dev/null 2>"$work/ctl.err"; then return 0; fi + bad "$1: knotchyctl failed: $(tail -1 "$work/ctl.err")"; return 1 +} +if snap "HTTP proxy" "$work/http.png" --allow-local-proxy --proxy "http://knotchy:s3cr3t@$srv:8888"; then + if deadline 10 container exec "$server" grep -q 'Established connection to host "example.com"' /tmp/tinyproxy.log; then + ok "a page loads through the HTTP proxy" + else bad "the HTTP proxy didn't carry the page"; fi +fi +if snap "SOCKS5 proxy" "$work/socks.png" --allow-local-proxy --proxy "socks5://knotchy:s3cr3t@$srv:1080"; then + if deadline 10 container exec "$server" grep -q 'connected to example.com:443' /tmp/microsocks.log; then + ok "a page loads through the SOCKS5 proxy" + else bad "the SOCKS5 proxy didn't carry the page"; fi +fi +deadline 10 container exec "$server" sh -c ': > /tmp/tinyproxy.log' +if snap "wrong password" "$work/wrong.png" --allow-local-proxy --proxy "http://knotchy:wrong@$srv:8888"; then + if deadline 10 container exec "$server" grep -q 'Established connection' /tmp/tinyproxy.log; then + bad "a wrong proxy password still got through" + else ok "a wrong proxy password gets nowhere"; fi +fi +if ! deadline 120 "$ctl" snapshot https://example.com "$work/local.png" --proxy "http://knotchy:s3cr3t@$srv:8888" \ + >/dev/null 2>"$work/ctl.err" && grep -q "use a proxy on this Mac or your local network" "$work/ctl.err"; then + ok "a proxy on the local network is refused" +else bad "a proxy on the local network wasn't refused: $(tail -1 "$work/ctl.err")"; fi +deadline 10 container exec "$server" cat /tmp/client.conf > "$work/client.conf" +before=$(deadline 10 container exec "$server" wg show wg0 transfer | awk '{print $2}') +if snap "WireGuard" "$work/wg.png" --wireguard "$work/client.conf"; then + after=$(deadline 10 container exec "$server" wg show wg0 transfer | awk '{print $2}') + if [[ "${after:-0}" -gt $(( ${before:-0} + 2000 )) ]]; then ok "a page loads through the WireGuard tunnel" + else bad "the tunnel didn't carry the page ($before -> $after bytes)"; fi +fi +summary diff --git a/vm/Containerfile b/vm/Containerfile new file mode 100644 index 0000000..0c84e37 --- /dev/null +++ b/vm/Containerfile @@ -0,0 +1,52 @@ +# The only definition of the protocol version, so preflight and HELLO agree. +# 2: honours proxy and VPN routes (older images would browse directly); 3: PAGE_URL. +ARG KNOTCHY_PROTOCOL=3 + +FROM debian:trixie-slim AS build +ARG KNOTCHY_PROTOCOL +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + gcc libc6-dev pkg-config libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev +# Only developer builds use -Werror, so a new compiler warning can't break setup. +ARG WERROR= +COPY knotchy-agent.c progress-gate.c progress-gate.h /src/ +COPY tests/ /src/tests/ +RUN gcc -O2 -Wall $WERROR -I/src -o /tmp/test-progress-gate \ + /src/tests/test-progress-gate.c /src/progress-gate.c -lm && \ + /tmp/test-progress-gate +RUN gcc -O2 -Wall $WERROR -DPROTOCOL_VERSION=$KNOTCHY_PROTOCOL -I/src -o /knotchy-agent /src/knotchy-agent.c /src/progress-gate.c \ + $(pkg-config --cflags --libs gstreamer-1.0 gstreamer-app-1.0 gstreamer-video-1.0 glib-2.0) -lm && \ + strip /knotchy-agent + +FROM debian:trixie-slim +ARG KNOTCHY_PROTOCOL +LABEL aculix.knotchy.protocol="$KNOTCHY_PROTOCOL" +# A new APT_REFRESH (the build date) re-runs apt, so rebuilds get security updates. +ARG APT_REFRESH=unset +# Without autoaudiosink (plugins-good) any page with media crashes the web process. +# Plugins that can't work in the VM are deleted to shrink the attack surface; +# plain rm fails the build unless each name matches exactly one file. +RUN echo "apt refresh: $APT_REFRESH" && apt-get update && apt-get -y upgrade && \ + apt-get install -y --no-install-recommends \ + gstreamer1.0-wpe gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly \ + gstreamer1.0-libav gstreamer1.0-plugins-bad \ + libgles2 glib-networking ca-certificates nftables \ + wireguard-go wireguard-tools iproute2 \ + fonts-dejavu-core fonts-liberation2 fonts-inter-variable fonts-noto-core fonts-noto-color-emoji && \ + rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man /usr/share/locale && \ + for p in video4linux2 rtsp rtp rtpmanager udp jack oss4 ossaudio ximagesrc shout2 1394 pulseaudio; do \ + f="$(dpkg -L gstreamer1.0-plugins-good | grep "/libgst$p\.so\$")" && rm "$f" || \ + { echo "prune: libgst$p.so not found in gstreamer1.0-plugins-good" >&2; exit 1; }; \ + done && \ + useradd --create-home --uid 1000 knotchy +COPY --from=build /knotchy-agent /usr/local/bin/knotchy-agent +COPY --chmod=755 knotchy-init /usr/local/bin/knotchy-init +COPY --chmod=644 fonts.conf /etc/fonts/local.conf + +# WebKit's bubblewrap sandbox needs the non-root user knotchy-init switches to. +WORKDIR /home/knotchy +ENTRYPOINT ["/usr/local/bin/knotchy-init"] + +# Last, so the per-build timestamp doesn't invalidate cached layers. +ARG BUILD_DATE=unknown +LABEL aculix.knotchy.built="$BUILD_DATE" diff --git a/vm/fonts.conf b/vm/fonts.conf new file mode 100644 index 0000000..912dae6 --- /dev/null +++ b/vm/fonts.conf @@ -0,0 +1,32 @@ + + + + + system-uiInter VariableInter + -apple-systemInter VariableInter + BlinkMacSystemFontInter VariableInter + ui-sans-serifInter VariableInter + SF ProInter VariableInter + SF Pro TextInter VariableInter + SF Pro DisplayInter VariableInter + San FranciscoInter VariableInter + Helvetica NeueInter VariableInter + Segoe UIInter VariableInter + RobotoInter VariableInter + + sans-serifInter VariableInterNoto SansDejaVu Sans + serifNoto SerifDejaVu Serif + + + ui-monospaceDejaVu Sans Mono + SF MonoDejaVu Sans Mono + SFMono-RegularDejaVu Sans Mono + MenloDejaVu Sans Mono + MonacoDejaVu Sans Mono + ConsolasDejaVu Sans Mono + + emojiNoto Color Emoji + Apple Color EmojiNoto Color Emoji + Segoe UI EmojiNoto Color Emoji + Segoe UI SymbolNoto Color Emoji + diff --git a/vm/knotchy-agent.c b/vm/knotchy-agent.c new file mode 100644 index 0000000..6c0b0f3 --- /dev/null +++ b/vm/knotchy-agent.c @@ -0,0 +1,697 @@ +/* + * knotchy-agent: streams one WPE WebKit view as H.264 to a single viewer over a + * Unix socket and injects the viewer's input. + * + * Messages: [u8 type][u32 BE payload length][payload] + * agent -> viewer + * 0x01 HELLO u8 protocol version, u16 width, u16 height + * 0x02 VIDEO u16 width, u16 height, H.264 Annex-B access unit + * 0x03 PROGRESS u8 percent + * 0x04 NAVIGATED empty; every navigation (the error page resets progress without one) + * 0x05 PAGE_URL UTF-8, at most 16 KiB, never about:blank + * A new viewer gets HELLO, then NAVIGATED, PROGRESS and PAGE_URL as last sent, + * then an IDR frame. + * viewer -> agent + * 0x10 POINTER_MOVE u16 x, u16 y, u8 mods + * 0x11 POINTER_BUTTON u8 button (1 left, 2 right, 3 middle), u8 pressed, u16 x, u16 y, u8 mods + * 0x12 SCROLL i16 ticks_x, i16 ticks_y (negative = right/down), u16 x, u16 y + * 0x13 KEY u8 pressed, u8 mods, UTF-8 character or XKB keysym name + * 0x14 NAVIGATE UTF-8 URL + * 0x15 SCRIPT UTF-8 JavaScript + * 0x16 KEYFRAME empty + * mods: bit 0 shift, 1 control, 2 alt, 3 meta, 4/5/6 left/right/middle button held + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "progress-gate.h" + +#ifndef PROTOCOL_VERSION +#error "build with -DPROTOCOL_VERSION=; vm/Containerfile passes it from KNOTCHY_PROTOCOL" +#endif + +_Static_assert (PROTOCOL_VERSION >= 1 && PROTOCOL_VERSION <= 255, + "PROTOCOL_VERSION must fit HELLO's u8 version field"); + +enum { + MSG_HELLO = 0x01, + MSG_VIDEO = 0x02, + MSG_PROGRESS = 0x03, + MSG_NAVIGATED = 0x04, + MSG_PAGE_URL = 0x05, + MSG_POINTER_MOVE = 0x10, + MSG_POINTER_BUTTON = 0x11, + MSG_SCROLL = 0x12, + MSG_KEY = 0x13, + MSG_NAVIGATE = 0x14, + MSG_SCRIPT = 0x15, + MSG_KEYFRAME = 0x16, +}; + +#define MAX_INPUT_MESSAGE (64 * 1024) +#define MAX_PAGE_URL (16 * 1024) + +typedef struct { + GMainLoop *loop; + GstElement *pipeline; + GstElement *src; + GstElement *sink; + GstPad *src_pad; + int width, height; + int listen_fd; + int client_fd; /* guarded by write_lock */ + gboolean viewer_dead; /* guarded by write_lock */ + gboolean viewer_present; /* main loop only */ + guint client_watch; + GMutex write_lock; + GByteArray *inbuf; + gboolean need_keyframe; + gint64 started_us; + gboolean first_frame_logged; + guint watchdog; + int orphan_timeout_s; + gchar *initial_url; + gchar *page_url; /* guarded by write_lock */ + gboolean debug; + ProgressGate progress; /* main loop only */ +} Agent; + +static int +env_int (const char *name, int fallback) +{ + const char *v = g_getenv (name); + return v && *v ? atoi (v) : fallback; +} + +static guint16 +rd16 (const guint8 * p) +{ + return (guint16) (p[0] << 8 | p[1]); +} + +static void +wr16 (guint8 * p, guint16 v) +{ + p[0] = v >> 8; + p[1] = v & 0xff; +} + +static void +wr32 (guint8 * p, guint32 v) +{ + p[0] = v >> 24; + p[1] = (v >> 16) & 0xff; + p[2] = (v >> 8) & 0xff; + p[3] = v & 0xff; +} + +static gboolean +writev_all (int fd, struct iovec *iov, int iovcnt) +{ + while (iovcnt > 0) { + ssize_t n = writev (fd, iov, iovcnt); + if (n < 0) { + if (errno == EINTR) + continue; + return FALSE; + } + while (iovcnt > 0 && (size_t) n >= iov->iov_len) { + n -= iov->iov_len; + iov++; + iovcnt--; + } + if (iovcnt > 0) { + iov->iov_base = (guint8 *) iov->iov_base + n; + iov->iov_len -= n; + } + } + return TRUE; +} + +static void +drop_client_locked (Agent * a) +{ + if (a->client_fd >= 0) { + shutdown (a->client_fd, SHUT_RDWR); + close (a->client_fd); + a->client_fd = -1; + a->viewer_dead = FALSE; + g_print ("knotchy-agent: viewer disconnected\n"); + } +} + +static void +send_locked (Agent * a, guint8 type, const guint8 * p1, gsize n1, + const guint8 * p2, gsize n2) +{ + guint8 header[5]; + struct iovec iov[3]; + + if (a->client_fd < 0 || a->viewer_dead) + return; + header[0] = type; + wr32 (header + 1, (guint32) (n1 + n2)); + iov[0] = (struct iovec) {header, sizeof header}; + iov[1] = (struct iovec) {(void *) p1, n1}; + iov[2] = (struct iovec) {(void *) p2, n2}; + if (!writev_all (a->client_fd, iov, n2 ? 3 : 2)) { + /* Not close(): on the streaming thread, a reused fd number would end up + * under the main loop's watch. The main loop sees EOF and cleans up. */ + a->viewer_dead = TRUE; + shutdown (a->client_fd, SHUT_RDWR); + } +} + +static void +send_message (Agent * a, guint8 type, const guint8 * payload, gsize len) +{ + g_mutex_lock (&a->write_lock); + send_locked (a, type, payload, len, NULL, 0); + g_mutex_unlock (&a->write_lock); +} + +static void +request_keyframe (Agent * a) +{ + g_mutex_lock (&a->write_lock); + a->need_keyframe = TRUE; + g_mutex_unlock (&a->write_lock); + gst_element_send_event (a->sink, + gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, TRUE, + 0)); +} + +static void +navigate_to (Agent * a, const char *url) +{ + if (a->debug) + g_print ("knotchy-agent: navigating to %s\n", url); + progress_gate_navigated (&a->progress); + send_message (a, MSG_NAVIGATED, NULL, 0); + g_object_set (a->src, "location", url, NULL); +} + +static void +on_page_uri (GObject * view, GParamSpec * pspec, gpointer user_data) +{ + Agent *a = user_data; + gchar *uri = NULL; + g_object_get (view, "uri", &uri, NULL); + if (uri && *uri && strcmp (uri, "about:blank") != 0 && strlen (uri) <= MAX_PAGE_URL + && g_utf8_validate (uri, -1, NULL)) { + g_mutex_lock (&a->write_lock); + if (g_strcmp0 (uri, a->page_url) != 0) { + g_free (a->page_url); + a->page_url = g_strdup (uri); + send_locked (a, MSG_PAGE_URL, (const guint8 *) uri, strlen (uri), NULL, 0); + } + g_mutex_unlock (&a->write_lock); + } + g_free (uri); +} + +/* Plain GObject calls, so the agent needn't link against WebKit. */ +static void +on_configure_web_view (GstElement * src, GObject * view, gpointer user_data) +{ + g_signal_connect (view, "notify::uri", G_CALLBACK (on_page_uri), user_data); +} + +static gboolean +navigate_to_initial_url (gpointer user_data) +{ + Agent *a = user_data; + navigate_to (a, a->initial_url); + return G_SOURCE_REMOVE; +} + +static GstFlowReturn +on_new_sample (GstAppSink * sink, gpointer user_data) +{ + Agent *a = user_data; + GstSample *sample = gst_app_sink_pull_sample (sink); + GstBuffer *buf; + GstMapInfo map; + guint8 dims[4]; + + if (!sample) + return GST_FLOW_EOS; + buf = gst_sample_get_buffer (sample); + + if (!a->first_frame_logged) { + a->first_frame_logged = TRUE; + g_print ("knotchy-agent: first encoded frame %" G_GINT64_FORMAT " ms after start\n", + (g_get_monotonic_time () - a->started_us) / 1000); + g_idle_add (navigate_to_initial_url, a); + } + + g_mutex_lock (&a->write_lock); + if (a->client_fd >= 0) { + gboolean is_key = !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + if (is_key) + a->need_keyframe = FALSE; + if (!a->need_keyframe && gst_buffer_map (buf, &map, GST_MAP_READ)) { + wr16 (dims, a->width); + wr16 (dims + 2, a->height); + send_locked (a, MSG_VIDEO, dims, sizeof dims, map.data, map.size); + gst_buffer_unmap (buf, &map); + } + } + g_mutex_unlock (&a->write_lock); + + gst_sample_unref (sample); + return GST_FLOW_OK; +} + +static GstNavigationModifierType +to_gst_mods (guint8 m) +{ + GstNavigationModifierType s = 0; + if (m & 0x01) s |= GST_NAVIGATION_MODIFIER_SHIFT_MASK; + if (m & 0x02) s |= GST_NAVIGATION_MODIFIER_CONTROL_MASK; + if (m & 0x04) s |= GST_NAVIGATION_MODIFIER_MOD1_MASK; + if (m & 0x08) s |= GST_NAVIGATION_MODIFIER_META_MASK; + if (m & 0x10) s |= GST_NAVIGATION_MODIFIER_BUTTON1_MASK; + if (m & 0x20) s |= GST_NAVIGATION_MODIFIER_BUTTON2_MASK; + if (m & 0x40) s |= GST_NAVIGATION_MODIFIER_BUTTON3_MASK; + return s; +} + +static void +inject (Agent * a, GstEvent * ev) +{ + /* WebKit uses event time for double-click detection. */ + GST_EVENT_TIMESTAMP (ev) = g_get_monotonic_time () * 1000; + gst_pad_send_event (a->src_pad, ev); +} + +static void +handle_message (Agent * a, guint8 type, const guint8 * p, guint32 len) +{ + switch (type) { + case MSG_POINTER_MOVE: + if (len >= 5) + inject (a, gst_navigation_event_new_mouse_move (rd16 (p), rd16 (p + 2), + to_gst_mods (p[4]))); + break; + case MSG_POINTER_BUTTON: + if (len >= 7) { + int button = p[0]; + double x = rd16 (p + 2), y = rd16 (p + 4); + GstNavigationModifierType mods = to_gst_mods (p[6]); + inject (a, p[1] + ? gst_navigation_event_new_mouse_button_press (button, x, y, mods) + : gst_navigation_event_new_mouse_button_release (button, x, y, mods)); + } + break; + case MSG_SCROLL: + if (len >= 8) { + gint16 tx = (gint16) rd16 (p), ty = (gint16) rd16 (p + 2); + double x = rd16 (p + 4), y = rd16 (p + 6); + /* wpevideosrc sends one axis per event and WebKit uses only the sign. */ + for (int i = 0; i < ABS (tx) && i < 50; i++) + inject (a, gst_navigation_event_new_mouse_scroll (x, y, tx > 0 ? 1 : -1, 0, 0)); + for (int i = 0; i < ABS (ty) && i < 50; i++) + inject (a, gst_navigation_event_new_mouse_scroll (x, y, 0, ty > 0 ? 1 : -1, 0)); + } + break; + case MSG_KEY: + if (len >= 3) { + char *key = g_strndup ((const char *) p + 2, len - 2); + GstNavigationModifierType mods = to_gst_mods (p[1]); + if (g_utf8_validate (key, -1, NULL)) + inject (a, p[0] + ? gst_navigation_event_new_key_press (key, mods) + : gst_navigation_event_new_key_release (key, mods)); + g_free (key); + } + break; + case MSG_NAVIGATE:{ + char *url = g_strndup ((const char *) p, len); + if (g_utf8_validate (url, -1, NULL)) { + if (!a->progress.has_navigated) { + if (a->debug) + g_print ("knotchy-agent: early NAVIGATE replaces the initial URL\n"); + g_free (a->initial_url); + a->initial_url = g_strdup (url); + } else { + navigate_to (a, url); + } + } + g_free (url); + break; + } + case MSG_SCRIPT:{ + char *js = g_strndup ((const char *) p, len); + if (g_utf8_validate (js, -1, NULL)) + g_signal_emit_by_name (a->src, "run-javascript", js); + g_free (js); + break; + } + case MSG_KEYFRAME: + request_keyframe (a); + break; + default: + break; + } +} + +/* PID 1 exiting stops the VM and --rm deletes it, so a crashed app leaves no + * session behind. */ +static gboolean +on_watchdog (gpointer user_data) +{ + Agent *a = user_data; + a->watchdog = 0; + g_printerr ("knotchy-agent: no viewer; shutting the session down\n"); + g_main_loop_quit (a->loop); + return G_SOURCE_REMOVE; +} + +static void +arm_watchdog (Agent * a, int seconds) +{ + if (a->watchdog) + g_source_remove (a->watchdog); + a->watchdog = g_timeout_add_seconds (seconds, on_watchdog, a); +} + +static void +disarm_watchdog (Agent * a) +{ + if (a->watchdog) { + g_source_remove (a->watchdog); + a->watchdog = 0; + } +} + +static void +close_client (Agent * a) +{ + gboolean had_client; + + had_client = a->viewer_present; + a->viewer_present = FALSE; + if (a->client_watch) { + g_source_remove (a->client_watch); + a->client_watch = 0; + } + g_mutex_lock (&a->write_lock); + drop_client_locked (a); + g_mutex_unlock (&a->write_lock); + g_byte_array_set_size (a->inbuf, 0); + if (had_client) + arm_watchdog (a, a->orphan_timeout_s); +} + +static gboolean +on_client_readable (gint fd, GIOCondition cond, gpointer user_data) +{ + Agent *a = user_data; + guint8 chunk[16384]; + ssize_t n = read (fd, chunk, sizeof chunk); + + if (n <= 0) { + if (n < 0 && errno == EINTR) + return G_SOURCE_CONTINUE; + a->client_watch = 0; + close_client (a); + return G_SOURCE_REMOVE; + } + g_byte_array_append (a->inbuf, chunk, n); + + while (a->inbuf->len >= 5) { + guint8 *d = a->inbuf->data; + guint32 len = (guint32) d[1] << 24 | d[2] << 16 | d[3] << 8 | d[4]; + if (len > MAX_INPUT_MESSAGE) { + a->client_watch = 0; + close_client (a); + return G_SOURCE_REMOVE; + } + if (a->inbuf->len < 5 + len) + break; + handle_message (a, d[0], d + 5, len); + g_byte_array_remove_range (a->inbuf, 0, 5 + len); + } + return G_SOURCE_CONTINUE; +} + +static gboolean +on_accept (gint fd, GIOCondition cond, gpointer user_data) +{ + Agent *a = user_data; + guint8 hello[5]; + int cfd = accept4 (fd, NULL, NULL, SOCK_CLOEXEC); + + if (cfd < 0) + return G_SOURCE_CONTINUE; + + /* CLOEXEC keeps the socket from WebKit's children. The timeout keeps a stalled + * viewer from blocking the encoder thread and, via write_lock, the main loop. */ + struct timeval send_timeout = { .tv_sec = 2, .tv_usec = 0 }; + setsockopt (cfd, SOL_SOCKET, SO_SNDTIMEO, &send_timeout, sizeof send_timeout); + + close_client (a); + disarm_watchdog (a); + + g_mutex_lock (&a->write_lock); + a->client_fd = cfd; + a->viewer_present = TRUE; + a->need_keyframe = TRUE; + hello[0] = PROTOCOL_VERSION; + wr16 (hello + 1, a->width); + wr16 (hello + 3, a->height); + send_locked (a, MSG_HELLO, hello, sizeof hello, NULL, 0); + /* Same critical section as HELLO, so no VIDEO frame can come between. */ + if (a->progress.has_navigated) + send_locked (a, MSG_NAVIGATED, NULL, 0, NULL, 0); + if (a->progress.last_sent >= 0) { + guint8 pct = (guint8) a->progress.last_sent; + send_locked (a, MSG_PROGRESS, &pct, 1, NULL, 0); + } + if (a->page_url) + send_locked (a, MSG_PAGE_URL, (const guint8 *) a->page_url, strlen (a->page_url), NULL, 0); + g_mutex_unlock (&a->write_lock); + + a->client_watch = g_unix_fd_add (cfd, G_IO_IN | G_IO_HUP | G_IO_ERR, + on_client_readable, a); + request_keyframe (a); + g_print ("knotchy-agent: viewer connected %" G_GINT64_FORMAT " ms after start\n", + (g_get_monotonic_time () - a->started_us) / 1000); + return G_SOURCE_CONTINUE; +} + +static int +listen_unix (const char *path) +{ + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + char *dir = g_path_get_dirname (path); + int fd; + + g_mkdir_with_parents (dir, 0700); + g_free (dir); + unlink (path); + if (strlen (path) >= sizeof addr.sun_path) + return -1; + strcpy (addr.sun_path, path); + + fd = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + if (bind (fd, (struct sockaddr *) &addr, sizeof addr) < 0 + || listen (fd, 4) < 0) { + close (fd); + return -1; + } + return fd; +} + +/* A failed navigation is a normal outcome, not a reason to end the session. */ +static gboolean +is_load_failure (GstMessage * msg, const GError * err, const gchar * dbg) +{ + return GST_IS_ELEMENT (GST_MESSAGE_SRC (msg)) + && g_error_matches (err, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED) + && dbg && strstr (dbg, "Failed to load") != NULL; +} + +static void +show_error_page (Agent * a, const gchar * detail) +{ + gchar *escaped = g_markup_escape_text (detail, -1); + gchar *html = g_strdup_printf ("" + "" + "

This page couldn't be loaded

%s

", + escaped); + GBytes *bytes = g_bytes_new_take (html, strlen (html)); + + /* WebKit reports the failed load's final 100 after load-failed; resetting + * the gate drops it, so a failed page never reads as loaded. */ + progress_gate_navigated (&a->progress); + g_signal_emit_by_name (a->src, "load-bytes", bytes); + g_bytes_unref (bytes); + g_free (escaped); +} + +static gboolean +on_bus_message (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + Agent *a = user_data; + GError *err = NULL; + gchar *dbg = NULL; + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + gst_message_parse_error (msg, &err, &dbg); + if (is_load_failure (msg, err, dbg)) { + g_printerr ("knotchy-agent: %s\n", dbg); + show_error_page (a, strstr (dbg, "Failed to load")); + } else { + /* Includes WEBKIT_WEB_PROCESS_CRASHED. Fail closed: the VM goes away. */ + g_printerr ("knotchy-agent: pipeline error: %s (%s)\n", err->message, + dbg ? dbg : ""); + g_main_loop_quit (a->loop); + } + g_clear_error (&err); + g_free (dbg); + break; + case GST_MESSAGE_ELEMENT:{ + const GstStructure *s = gst_message_get_structure (msg); + gdouble value; + if (s && gst_structure_has_name (s, "wpe-stats") + && gst_structure_get_double (s, "estimated-load-progress", &value)) { + int pct = progress_gate_feed (&a->progress, value); + if (a->debug) + g_print ("knotchy-agent: wpe-stats %.1f (%s)\n", value, + pct < 0 ? "dropped" : "sent"); + if (pct >= 0) { + guint8 b = (guint8) pct; + send_message (a, MSG_PROGRESS, &b, 1); + } + } + break; + } + case GST_MESSAGE_EOS: + g_main_loop_quit (a->loop); + break; + default: + break; + } + return G_SOURCE_CONTINUE; +} + +static gboolean +on_terminate (gpointer user_data) +{ + g_main_loop_quit (((Agent *) user_data)->loop); + return G_SOURCE_REMOVE; +} + +int +main (int argc, char **argv) +{ + Agent a = { .client_fd = -1 }; + const char *url = g_getenv ("KNOTCHY_URL"); + const char *size = g_getenv ("KNOTCHY_SIZE"); + const char *sock = g_getenv ("KNOTCHY_SOCKET"); + int fps = env_int ("KNOTCHY_FPS", 30); + int qp = env_int ("KNOTCHY_QP", 21); + GError *err = NULL; + gchar *desc; + GstBus *bus; + + a.started_us = g_get_monotonic_time (); + a.debug = env_int ("KNOTCHY_DEBUG", 0) != 0; + progress_gate_init (&a.progress); + signal (SIGPIPE, SIG_IGN); + gst_init (&argc, &argv); + + if (!url || !*url) + url = "about:blank"; + if (!sock || !*sock) + sock = "/tmp/knotchy/knotchy.sock"; + if (!size || sscanf (size, "%dx%d", &a.width, &a.height) != 2) { + a.width = 1024; + a.height = 640; + } + /* The cap keeps an IDR frame well under the viewer's 2 MiB VIDEO limit. */ + a.width = CLAMP (a.width, 320, 2048) & ~1; + a.height = CLAMP (a.height, 240, 1280) & ~1; + + /* Constant quality: bandwidth is free over a local socket, sharp text isn't. */ + desc = g_strdup_printf ("wpevideosrc name=src draw-background=true " + "! video/x-raw,width=%d,height=%d,framerate=%d/1 " + "! videoconvert ! video/x-raw,format=I420 " + "! x264enc tune=zerolatency speed-preset=ultrafast pass=qual quantizer=%d " + "key-int-max=%d byte-stream=true option-string=repeat-headers=1 " + "! video/x-h264,stream-format=byte-stream,alignment=au " + "! appsink name=sink sync=false max-buffers=8", + a.width, a.height, fps, qp, fps * 4); + a.pipeline = gst_parse_launch (desc, &err); + g_free (desc); + if (!a.pipeline) { + g_printerr ("knotchy-agent: %s\n", err->message); + return 1; + } + + a.src = gst_bin_get_by_name (GST_BIN (a.pipeline), "src"); + a.sink = gst_bin_get_by_name (GST_BIN (a.pipeline), "sink"); + a.src_pad = gst_element_get_static_pad (a.src, "src"); + /* wpevideosrc creates the view only once a page paints, which a failed first + * load never does. The URL is a property, never parsed as pipeline syntax. */ + a.initial_url = g_strdup (url); + g_object_set (a.src, "location", "about:blank", NULL); + g_signal_connect (a.src, "configure-web-view", G_CALLBACK (on_configure_web_view), &a); + + gst_app_sink_set_callbacks (GST_APP_SINK (a.sink), + &(GstAppSinkCallbacks) {.new_sample = on_new_sample}, &a, NULL); + + a.inbuf = g_byte_array_new (); + g_mutex_init (&a.write_lock); + a.loop = g_main_loop_new (NULL, FALSE); + + a.listen_fd = listen_unix (sock); + if (a.listen_fd < 0) { + g_printerr ("knotchy-agent: cannot listen on %s: %s\n", sock, g_strerror (errno)); + return 1; + } + g_unix_fd_add (a.listen_fd, G_IO_IN, on_accept, &a); + + bus = gst_element_get_bus (a.pipeline); + gst_bus_add_watch (bus, on_bus_message, &a); + gst_object_unref (bus); + + /* As PID 1, SIGTERM has no default action. */ + g_unix_signal_add (SIGTERM, on_terminate, &a); + g_unix_signal_add (SIGINT, on_terminate, &a); + + a.orphan_timeout_s = MAX (1, env_int ("KNOTCHY_ORPHAN_TIMEOUT", 10)); + arm_watchdog (&a, MAX (1, env_int ("KNOTCHY_FIRST_VIEWER_TIMEOUT", 60))); + + g_print ("knotchy-agent: %dx%d@%d streaming on %s\n", a.width, a.height, fps, sock); + gst_element_set_state (a.pipeline, GST_STATE_PLAYING); + g_main_loop_run (a.loop); + disarm_watchdog (&a); + + gst_element_set_state (a.pipeline, GST_STATE_NULL); + close_client (&a); + close (a.listen_fd); + unlink (sock); + return 0; +} diff --git a/vm/knotchy-init b/vm/knotchy-init new file mode 100755 index 0000000..d7a301e --- /dev/null +++ b/vm/knotchy-init @@ -0,0 +1,197 @@ +#!/bin/sh +# PID 1 of a session VM: firewalls egress to the public internet (or only the +# proxy, or only the WireGuard server and its tunnel), then runs knotchy-agent as +# the knotchy user with no capabilities, so nothing in the browser can undo it. +# Fails closed: a missing or malformed setting stops the VM. Needs NET_ADMIN. +# Env: KNOTCHY_ROUTE (direct|proxy|wireguard), KNOTCHY_BLOCK_CIDRS, KNOTCHY_PROXY_ADDR, +# KNOTCHY_PROXY_URL*, KNOTCHY_WG_SETCONF*, KNOTCHY_WG_ENDPOINT, KNOTCHY_WG_ADDRESS, +# KNOTCHY_WG_DNS, KNOTCHY_WG_MTU. *Secrets, normally in /run/knotchy-secrets/route. +set -eu + +fail() { + echo "knotchy-init: $*" >&2 + exit 1 +} + +blocked="0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, + 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.88.99.0/24, 192.168.0.0/16, + 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4" + +is_ipv4() { + case "$1" in *[!0-9.]* | "" | *..* | .* | *.) return 1 ;; esac + old_ifs=$IFS; IFS=. + # shellcheck disable=SC2086 # splitting on dots is the point + set -- $1 + IFS=$old_ifs + [ $# -eq 4 ] || return 1 + for octet in "$@"; do + [ "${#octet}" -le 3 ] && [ "$octet" -le 255 ] || return 1 + done +} + +is_port() { + case "$1" in *[!0-9]* | "") return 1 ;; esac + [ "${#1}" -le 5 ] && [ "$1" -ge 1 ] && [ "$1" -le 65535 ] +} + +is_cidr() { + case "$1" in */*) ;; *) return 1 ;; esac + prefix=${1##*/} + case "$prefix" in *[!0-9]* | "") return 1 ;; esac + is_ipv4 "${1%/*}" && [ "${#prefix}" -le 2 ] && [ "$prefix" -le 32 ] +} + +set -f +for cidr in ${KNOTCHY_BLOCK_CIDRS:-}; do + case "$cidr" in + *[!0-9./]* | "") fail "malformed CIDR '$cidr' in KNOTCHY_BLOCK_CIDRS" ;; + *) blocked="$blocked, $cidr" ;; + esac +done + +route=${KNOTCHY_ROUTE:-direct} +case $route in + direct | proxy | wireguard) ;; + *) fail "unknown KNOTCHY_ROUTE '$route'" ;; +esac + +# Secrets come on a share because `container` keeps -e values on disk. Delete +# the file (on the Mac too) and unmount before anything else runs. +proxy_url=${KNOTCHY_PROXY_URL:-} +wg_setconf=${KNOTCHY_WG_SETCONF:-} +secrets=/run/knotchy-secrets +if [ -e "$secrets/route" ]; then + while IFS= read -r line; do + case "$line" in + KNOTCHY_PROXY_URL=*) proxy_url=${line#*=} ;; + KNOTCHY_WG_SETCONF=*) wg_setconf=${line#*=} ;; + esac + done < "$secrets/route" + rm -f "$secrets/route" || fail "could not delete the secrets file" +fi +if grep -q " $secrets " /proc/mounts; then + umount "$secrets" || fail "could not unmount the secrets share" +fi + +case $route in + direct) + [ -z "${KNOTCHY_PROXY_ADDR:-}${KNOTCHY_WG_ENDPOINT:-}$proxy_url$wg_setconf" ] || + fail "proxy or VPN settings given for a direct route" + egress="ip daddr @blocked4 reject with icmpx type admin-prohibited" ;; + proxy) + proxy_addr=${KNOTCHY_PROXY_ADDR:-} + proxy_ip=${proxy_addr%:*} + proxy_port=${proxy_addr##*:} + is_ipv4 "$proxy_ip" && is_port "$proxy_port" || fail "malformed KNOTCHY_PROXY_ADDR" + case "$proxy_url" in + http://*@"$proxy_addr" | http://"$proxy_addr" | socks5://*@"$proxy_addr" | socks5://"$proxy_addr") ;; + *) fail "malformed or missing KNOTCHY_PROXY_URL" ;; + esac + egress="ip daddr $proxy_ip tcp dport $proxy_port accept + reject with icmpx type admin-prohibited" ;; + wireguard) + [ -n "$wg_setconf" ] || fail "KNOTCHY_WG_SETCONF is missing" + wg_endpoint=${KNOTCHY_WG_ENDPOINT:-} + wg_ip=${wg_endpoint%:*} + wg_port=${wg_endpoint##*:} + is_ipv4 "$wg_ip" && is_port "$wg_port" || fail "malformed KNOTCHY_WG_ENDPOINT" + [ -n "${KNOTCHY_WG_ADDRESS:-}" ] || fail "KNOTCHY_WG_ADDRESS is empty" + for cidr in $KNOTCHY_WG_ADDRESS; do is_cidr "$cidr" || fail "malformed KNOTCHY_WG_ADDRESS"; done + wg_dns=${KNOTCHY_WG_DNS:-1.1.1.1} + [ -n "$wg_dns" ] || wg_dns=1.1.1.1 + for ns in $wg_dns; do is_ipv4 "$ns" || fail "malformed KNOTCHY_WG_DNS"; done + wg_mtu=${KNOTCHY_WG_MTU:-1420} + case "$wg_mtu" in *[!0-9]* | "") fail "malformed KNOTCHY_WG_MTU" ;; esac + [ "${#wg_mtu}" -le 4 ] && [ "$wg_mtu" -ge 576 ] || fail "malformed KNOTCHY_WG_MTU" + wg_dns_set=$(echo $wg_dns | sed 's/ /, /g') + # DNS only to the VPN's (often private) resolvers; outside the tunnel, only wireguard-go. + egress="oifname \"wg0\" ip daddr { $wg_dns_set } meta l4proto { tcp, udp } th dport 53 accept + oifname \"wg0\" ip daddr @blocked4 reject with icmpx type admin-prohibited + oifname \"wg0\" accept + ip daddr $wg_ip udp dport $wg_port meta skuid 0 accept + reject with icmpx type admin-prohibited" ;; +esac + +# No DNS exception in the firewall: a --dns resolver in a blocked range can't answer. +dns=$(awk '$1 == "nameserver" && $2 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ { + printf "%s%s", sep, $2; sep = ", " }' /etc/resolv.conf) +bad_ns=$(awk '$1 == "nameserver" && $2 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ { + split($2, o, "."); a = o[1] + 0; b = o[2] + 0 + if (a == 10 || a == 127 || (a == 169 && b == 254) || (a == 172 && b >= 16 && b <= 31) || + (a == 192 && b == 168) || (a == 100 && b >= 64 && b <= 127)) { print $2; exit } +}' /etc/resolv.conf) +[ -z "$bad_ns" ] || fail "resolver $bad_ns is in a blocked range; it cannot be reached through the egress firewall" + +nft -f - < /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null || true + +nft list table inet knotchy >/dev/null 2>&1 || fail "egress firewall is not in place" + +case $route in + direct) + echo "knotchy-init: egress locked to the public internet${dns:+ (DNS via $dns)}" ;; + proxy) + echo "knotchy-init: egress locked to the proxy at $KNOTCHY_PROXY_ADDR" + export http_proxy="$proxy_url" https_proxy="$proxy_url" \ + HTTP_PROXY="$proxy_url" HTTPS_PROXY="$proxy_url" no_proxy="" ;; + wireguard) + # The firewall is already in place, so a half-built tunnel leaks nothing. + old_umask=$(umask) + umask 077 + printf '%s' "$wg_setconf" | base64 -d > /run/knotchy-wg.conf 2>/dev/null || + fail "malformed KNOTCHY_WG_SETCONF" + # Without SYS_ADMIN, which init only needed to unmount the secrets share. + WG_I_PREFER_BUGGY_USERSPACE_TO_POLISHED_KMOD=1 setpriv --bounding-set -sys_admin \ + wireguard-go wg0 >/dev/null 2>&1 || + fail "could not start wireguard-go" + wg setconf wg0 /run/knotchy-wg.conf || fail "the WireGuard settings were rejected" + rm -f /run/knotchy-wg.conf + umask "$old_umask" + for cidr in $KNOTCHY_WG_ADDRESS; do + ip -4 address add "$cidr" dev wg0 || fail "could not give the tunnel its address" + done + ip link set dev wg0 mtu "$wg_mtu" up || fail "could not bring the tunnel up" + via=$(ip -4 route get "$wg_ip" | awk '{ for (i = 1; i < NF; i++) if ($i == "via") { print $(i + 1); exit } }') + dev=$(ip -4 route get "$wg_ip" | awk '{ for (i = 1; i < NF; i++) if ($i == "dev") { print $(i + 1); exit } }') + [ -n "$dev" ] || fail "no route to the VPN server" + ip -4 route add "$wg_ip/32" ${via:+via "$via"} dev "$dev" || fail "could not route to the VPN server" + ip -4 route replace default dev wg0 || fail "could not route through the tunnel" + printf 'nameserver %s\n' $wg_dns > /etc/resolv.conf || fail "could not set the VPN's DNS" + echo "knotchy-init: egress locked to the WireGuard tunnel via $KNOTCHY_WG_ENDPOINT" ;; +esac + +# The browser has no business knowing the Mac's networks or the VPN's keys. +unset KNOTCHY_ROUTE KNOTCHY_BLOCK_CIDRS KNOTCHY_PROXY_ADDR KNOTCHY_PROXY_URL KNOTCHY_WG_SETCONF \ + KNOTCHY_WG_ENDPOINT KNOTCHY_WG_ADDRESS KNOTCHY_WG_DNS KNOTCHY_WG_MTU +export HOME=/home/knotchy USER=knotchy LOGNAME=knotchy +exec setpriv --reuid=knotchy --regid=knotchy --init-groups \ + --inh-caps=-all --bounding-set=-all --no-new-privs \ + /usr/local/bin/knotchy-agent diff --git a/vm/progress-gate.c b/vm/progress-gate.c new file mode 100644 index 0000000..77e1e3f --- /dev/null +++ b/vm/progress-gate.c @@ -0,0 +1,53 @@ +#include + +#include "progress-gate.h" + +void +progress_gate_init (ProgressGate * g) +{ + g->has_navigated = false; + g->saw_below_100 = false; + g->last_sent = -1; +} + +void +progress_gate_navigated (ProgressGate * g) +{ + g->has_navigated = true; + g->saw_below_100 = false; + g->last_sent = -1; +} + +int +progress_gate_feed (ProgressGate * g, double value) +{ + if (!isfinite (value)) + return -1; + + /* Floor, not round: 99.6 % is not loaded. */ + int v = value <= 0 ? 0 : value >= 100 ? 100 : (int) floor (value); + + if (!g->has_navigated) + return -1; + + /* A drop means a new load started. */ + if (g->last_sent >= 0 && v < g->last_sent) { + g->saw_below_100 = true; + g->last_sent = v; + return v; + } + + /* A 100 before any lower value is the previous page finishing. */ + if (v < 100) + g->saw_below_100 = true; + + if (v == 100) { + if (!g->saw_below_100 || g->last_sent == 100) + return -1; + } else if (g->last_sent >= 0 && v < g->last_sent + 5) { + return -1; + } + + g->last_sent = v; + return v; +} diff --git a/vm/progress-gate.h b/vm/progress-gate.h new file mode 100644 index 0000000..473d0f4 --- /dev/null +++ b/vm/progress-gate.h @@ -0,0 +1,21 @@ +/* Decides which WebKit load-progress values the agent forwards as PROGRESS. */ +#ifndef KNOTCHY_PROGRESS_GATE_H +#define KNOTCHY_PROGRESS_GATE_H + +#include + +typedef struct { + bool has_navigated; /* false while the about:blank bootstrap loads */ + bool saw_below_100; + int last_sent; +} ProgressGate; + +void progress_gate_init (ProgressGate * g); + +/* On every new top-level load, the error page included. */ +void progress_gate_navigated (ProgressGate * g); + +/* Returns the percent to send, or -1 to drop the value. */ +int progress_gate_feed (ProgressGate * g, double value); + +#endif diff --git a/vm/tests/test-progress-gate.c b/vm/tests/test-progress-gate.c new file mode 100644 index 0000000..7dc3173 --- /dev/null +++ b/vm/tests/test-progress-gate.c @@ -0,0 +1,155 @@ +/* cc -Wall -Werror -I vm -o /tmp/test-progress-gate \ + * vm/tests/test-progress-gate.c vm/progress-gate.c -lm && /tmp/test-progress-gate */ +#include +#include + +#include "progress-gate.h" + +static int failures; + +#define EXPECT(got, want) \ + do { \ + int g_ = (got), w_ = (want); \ + if (g_ != w_) { \ + printf ("FAIL %s:%d: %s = %d, want %d\n", __FILE__, __LINE__, #got, g_, \ + w_); \ + failures++; \ + } \ + } while (0) + +static ProgressGate +navigated_gate (void) +{ + ProgressGate g; + progress_gate_init (&g); + progress_gate_navigated (&g); + return g; +} + +static void +drops_everything_before_navigation (void) +{ + ProgressGate g; + progress_gate_init (&g); + EXPECT (progress_gate_feed (&g, 10), -1); + EXPECT (progress_gate_feed (&g, 100), -1); + EXPECT (g.last_sent, -1); +} + +static void +late_bootstrap_100_is_not_page_loaded (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, 100), -1); + EXPECT (progress_gate_feed (&g, 10), 10); + EXPECT (progress_gate_feed (&g, 100), 100); +} + +static void +rate_limits_increases_but_always_sends_100_once (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, 10), 10); + EXPECT (progress_gate_feed (&g, 12), -1); + EXPECT (progress_gate_feed (&g, 15), 15); + EXPECT (progress_gate_feed (&g, 98), 98); + EXPECT (progress_gate_feed (&g, 100), 100); + EXPECT (progress_gate_feed (&g, 100), -1); +} + +static void +decrease_is_a_new_navigation_and_sent_immediately (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, 10), 10); + EXPECT (progress_gate_feed (&g, 100), 100); + EXPECT (progress_gate_feed (&g, 30), 30); + EXPECT (progress_gate_feed (&g, 32), -1); + EXPECT (progress_gate_feed (&g, 100), 100); +} + +static void +first_value_after_navigation_is_always_sent (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, 0), 0); + EXPECT (progress_gate_feed (&g, 3), -1); +} + +static void +floors_and_clamps_values (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, -5), 0); + EXPECT (progress_gate_feed (&g, 99.9), 99); + EXPECT (progress_gate_feed (&g, 150), 100); +} + +static void +navigating_again_rearms_the_bootstrap_guard (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, 50), 50); + progress_gate_navigated (&g); + EXPECT (progress_gate_feed (&g, 100), -1); +} + +static void +observed_wikipedia_sequence (void) +{ + ProgressGate g; + progress_gate_init (&g); + EXPECT (progress_gate_feed (&g, 10.0), -1); + EXPECT (progress_gate_feed (&g, 100.0), -1); + progress_gate_navigated (&g); + EXPECT (progress_gate_feed (&g, 10.0), 10); + EXPECT (progress_gate_feed (&g, 10.3), -1); + EXPECT (progress_gate_feed (&g, 48.7), 48); + EXPECT (progress_gate_feed (&g, 63.7), 63); + EXPECT (progress_gate_feed (&g, 84.4), 84); + EXPECT (progress_gate_feed (&g, 86.0), -1); + EXPECT (progress_gate_feed (&g, 86.4), -1); + EXPECT (progress_gate_feed (&g, 100.0), 100); +} + +static void +non_finite_values_are_dropped (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, 10), 10); + EXPECT (progress_gate_feed (&g, NAN), -1); + EXPECT (progress_gate_feed (&g, INFINITY), -1); + EXPECT (progress_gate_feed (&g, -INFINITY), -1); + EXPECT (g.last_sent, 10); +} + +static void +navigation_starts_a_fresh_load (void) +{ + ProgressGate g = navigated_gate (); + EXPECT (progress_gate_feed (&g, 50), 50); + progress_gate_navigated (&g); + EXPECT (g.last_sent, -1); + EXPECT (progress_gate_feed (&g, 52), 52); +} + +int +main (void) +{ + drops_everything_before_navigation (); + late_bootstrap_100_is_not_page_loaded (); + rate_limits_increases_but_always_sends_100_once (); + decrease_is_a_new_navigation_and_sent_immediately (); + first_value_after_navigation_is_always_sent (); + floors_and_clamps_values (); + navigating_again_rearms_the_bootstrap_guard (); + observed_wikipedia_sequence (); + non_finite_values_are_dropped (); + navigation_starts_a_fresh_load (); + if (failures) { + printf ("progress-gate: %d failure(s)\n", failures); + return 1; + } + printf ("progress-gate: all tests passed\n"); + return 0; +}

(8S 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