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() -> Set4UeAZrYG{TD~CaJF$Y3)_cYboMG?_?++C;;!!Xv z;k`+?pKY&ZpaXKodO|2x?VGYO1sbVvU@Swk}+7#3$33;%JYqd<@MZ+W}=z(W_PX^Xe;^Z$V>DKUA`YGM6A3nNAjARyM%{|7?z{}<-}KM~phzrn=6&i@yL z=Kp^owEt!Rl8ga!0|H`s|6d5r2fEQj)#JGPr8{rC!n5W$(2y1hpbSi5nk2O`x*KK` zzszn)s%gROJhHl#Zq6D+zp%<48TLGng(?MViC3o$4FZ8~Ob&$xqKYP>BsTSQ?Dg^S z?dNxL>2=+8v;9yGqfxO_&3*DSv;FgV(=fyTk?o`l^ Ou|SQ@CG$0 zq5t2|>kkb`(n)K_jLU~7yk3ENzghbA8k~K32WDoS)LHhKM$(+Rth?tXtgiTq?i_yt za1wpLa{(xKHoQ8nffgx8l^6LPYu otFAMi !75;k2>U)x?FCt z5_zCxYf_@=`RQv{7x^x~XK80@zA711@=QiK-}G0D#yU_W#|-Hh%^A9+pn)!i4`%bk zB2wx(*>}OsFe{ma8HBjI<&YExd>p>pILn{YaxJ}`c}p98) A8zL)0X)p zk;7BCcmY3wsj2IV8M)h}tb$#Y!qqBk^Fg?T*|YT;40wYJ2)XVCsl6?)+6a=JDdPIE z4mU&rKy-Y!_>kr-s8ohyX(jFuJyN}juY Ldw{yB&m>P7w|eRQ18I#mS{8Dl zs1a&LMY1UhlsM8pR*ptMWn{CdgrT-WBitH&XWiDd5YF^>cVx`sMZ!Y#`(*a; r;(5hUm##vPM$u_<~k^kNl#Za zS1{>4_LBa%D>-6;P 6BMwvY67u2Z?N*xsxt#RvFIEW%SARYS zl4|+fHsJE{l&&+N!=tpCysU7&Qk$c?nvaF=yri14Wy__@s9zE&yNd>mjii|_7F4wb zWo^Y+yT{8qF0SpP7^-O4%1478vzTkz;)x(a4BrRJN;&Hfi$6Z+DLX${w5wUxmvCof zLb|!_FZ~T}^ug7PZ-*yHGc10{Eq_6EAMZ^havoy8V3Rv@wW (l>?mdnf zv>ad0Fk|p;0~OROzAn+Kh8mz|W)t_vHMLUqEg7U`Ey5b0P@2rSe>a_{n=eW74HhYI z=9z<03AV0Beill>o{1*xXFY~v)GA-$V(B>df;aX3ojTaysEmwf#m;=t(Wl4vFSeQ= z86yb&7L!@--0$BzlJEs vwaaQuRBtXCdwpk3LN#xdr_%D z#z-N{08O>MmL0Y9Ph46u2%wk{E%lB2TnmUV2ILjhl*30` wOQ7bx|i3+DJRJSU^^ zLlXo#z7Yv;17&d0X(x$z&O%&y$c>d&$wbFU4~O+(JoPoxY~=^|BDuxOS%y^7(J mN=w`R2*O2}3#m8E# ze#V lt-oqV>#@cKQWxa5&2XD&OjLqTH>AyCN^vzPl2NS=A`#QB>(W z&+hajKpvOfJ$A-R lc>E$XSEIaP37A_H_5*AESQ0w+4jL7yDS^C`g3 z>CC}(1t|AVFAN&a 8h$ zpZAEZTZB{h%VS)wywxR;tq&9u -!F7O +s@>Z;*l z>At%rNI@D&%@7UDk&hw-=I~S&AQiWA$C4#EOWO}FZRgh%iE!3XM7C1IFb*x*I;LsN zleIH{DVD}87xK(=Ed5Qc|HYcgtn&6T_7kv2HW_>b9>;aw2Kwj;9jEAu)d9Bn9=gn2 zn)&8wpNIZi7m}Lh%_Sw2-}y*{Q>3_EcYTWig6o}P@gT;MNbLkYJ_|n0<_cUJO}%i& z=GU@THqH0SJ4R)&OFNi3Sgv}6Y{gw&@d|>VBTOU0EQjN*bfw}0JEIK$ovm1w2>$Sy z6RroW$y=oB!)Vv2g>p^=ehfwN${5NWS0=8{wvg750LSMD{vMJObC=VByn8i5F3}{6 zluoe*1=b&z$2Q+(BB9DU0P&wFB>R5X3^G(3z%T|Xt#6GnMpo~2OTflg6IDJxyELNW zLMY$bEGcCNSk}l{n^_(yhVaN1dx_1{$Wg~kJdI?w*}~5px Zx^#YHF_EduKhf@H{TYWTYVOOr)F9tk+lUT63Q_z+Ne{QKbzI6K~waG z9H>8so-ka%hDQG$7!Pi$HP1z{tWAl%vBs?l9dTP4Xt${~T5xVXt1GKDnWV6-F7Ny? z)u^|s=VKw58HGp40jr-eT7Y8Ire+TBas^^mBx<21nBa*o!O5;MYQ~EP8T#X#X|rZA zRxW857rH8`F#5~8v -A z3zDKT2+PAkJ+4ffe=E5fuR8c477wn5&yXmy(~}79{?ky(dCeiwiIe*Tnr1a-NprD| zX6t_Nck6j~o&D3-fdS&mMPY(5Zxj;IfaZx gZb(0bDugmtYmL?*|}wFCR6eKOUT{Mjk(&S;Y=u zMVJPq*X>2C(_%0mc*(QAQK?5&YQX%qr-BjuLdb#k?r_|a_$$XdPLB?bpIgUuM5FaH zhR?uI>&|j{rlG?_`3lv#=ThjD*7NrEteN7b`<*OzJti74(q3^qln}3x#-`Is!gM9M z5VZ4(zf$aA5v+=vYE27{7Ns%CCW)fz8Zjl44`-Qn*+Q)_DIcq|-#~^rvYE)DXOkJ7 zzRgA9^|>9IrAZZZW=;Bal_u`>y zVZ=o}Xn^+nY4~w5!A-{l&T<~1R}aRF&P1N2yaAqXKKrI1;nQpyr6dO#XId>Xk4;ph zvfusy{JHx86Hu|!N?M6s#PxSM*rdJ-#|Fj3jCd4!fhFF%nl*GWq}|Bj>U%>yHpFVX zo-{McBUm* %*yrC=*VH>mid$?%N2kYoiAn$<7 zc_>jBbRBaF()!T+_nBgH!82Y}lL ~;0DJ(6~qA={3 zfb+cl>+4yhbr_SGm~4Pt_XQD(hF}2AsF?ZZ{+^I-%z)@MZjBz^_Xrn>!CEe21I+PQ z#RuaU^3L`JbCtUZQBbc(_}gn4>_AQ?uXUY1p5ZC8`QtH3?P-P!%I>=a!TxZpKMO}V zCF-2biVS3&4@786r55S{NXH~;=pbwcc38Wt;3emBfPW96+4m5`!Gduv&>x6!Ob$;& zR#8Pj LvY@Wn_h;l2_VxS$^kY zv*95IO)P$Y8959N6?7vUmkw25K~-8n^|BF-?=iUr26j1jiY~W*tDJrjw=+_W=%xmk zSfVh_@Gnpm_xS*yy@HY$cl&6B#z3WJa5)fk=zg>uE1s0U#AamTA3c6sl-8Pnj}r0L zr?N66_0&Hz{EH@mWW4>c+0CQ$5|_A-dW=I2zNj1M67AJksAjfM`@`kcMY52&jG3K2 zG(6b5$Rul*np*xI+hk^Y(KXhkY;&7=q90W(Koqjg+@? 0plWzq1-u~>=G^oe1`2zwH25DP`i=7wHiPE&BwCX`xvsBn z`T`eFHT 9@vNMPgwQjW zp6vHpnjxMWQ0K~CH;$$x-${60Sy)3Y=3N<141v!QYOtU$bEn8wj-GU;tN|&$E>5Y# z(HS!!t0&%p>q>fkMP+w%dfWHCvEN|KZy?Zm#0^ChD|5azr;T<`T3RxQm8fu4oaawX z>w%c{W1#dw7Vwe!*|@=xx1A&q13WPXdi{T&E?22Dk$!HxEy+9gGPG?hQj|3*(-!B6 zc%_NLn*{jgYUlQ4G(OkZl}65W^swSfl)8~+6c>a@X#3%p-fACmu5l4LY%}3rtkXj9 zT 6?X=$x~ z))O_=LZ~xX)xfNXN~M&^i{*ye8%CK1LC%|5Cup-X+#O7!_TVndW=Qky&zW9@0wVEC z+QRuVgGvM=*sn1+{P^s#Ggu1MvD04vsvZj`jy|cP6=S)X;;SsGh$&ljJei+}83UL% z>jx|53$s2gmaRC|o%&K5U5W;-G@BqJXB8VFaL%T&B5y6=P7!WCZXMCoTaDSF$OEJz z2w$W2Bc;UWkz*V_zb_tZ1I*OQl)FK9>zr{6htc3}v4~#(_QnJAvLL-$XsEoPBp{@m zs%gELaa_S@k0^!yu&;}wKn(tP*^e5z{Oy2xzYCgsNLlWuhU@c#NyGGhIJ-qs+pp|> zhC^YnHRpvI>)G!tGGOA3p=T3%AM$(~0e#Ed&zOVef3NA#a*?Ef2ljpMBfx#9?Eyb( z$^FDAgwml#(;;~NJ?`o+T@a%xh-DQc98A>2ghdNzo i1b;YJXCurrci z-M2~*l#?F_olL7B#tW-!Wg+$Hxo~F}F(6G%0Ox23OH#`2l*yV-H$>B&g&MYrs?Z#~ z+yP^lcfZ*YHqc!7&xoM!em?Jm_huBdv62mw6yDWHT2l;;Za_ZUUxX@$ewJojJ3P cVeon7u@HXZ##D>jH($;dF7doQ)Fe#?196077)h*z~#zKum?R6H7! z@)hy#nfsPA+y$QuIT>xv{-1n6SSDR@!)XD~)=uK93>DaPos8L&xFY0ctT?k&7=37F z_#AU6YW>WUjRIc7VM|&KXTPY-3#!RR_afEbiH0@5#a8lB{Rh|ObPCZG0G;R`m)Mc^ z+Gj1M_u8{ZGKe--_{guBff<>-fdt- xD{ z%fm*5H7P|#1&-#)ry;*jN4jr=#wAw~SnZ>$(-H0+XfA#nowH>nApdyXR`%+Ag)S96 zXr}iGiyUAJnvx0cW5hv)XOINuZU8nqriX%NO1vm14#n0q1@mb`vT-fI+CW~5Pxc<0 zsas;3*=)=Y9Iv)oYg+nU6KQ<8F!+6B9Fk)o4k#ua&N(miFNCTE*3F;AGbY`3#eP=y zV+zNzC%bj{Q(CsX2rbYc$McB;2UUK8gGzr>?Eu^3?iQzVF^V8zMIH7`N~IfybjV$} z$r%;FX8D`(T!4##y2B8_VfVK5%gQHSZB}{BJ?^D{OUb@(VvVzln-Numys7K2?)=v1 zH+eAgt)uWhA*RnR3#(IIrBEP*#w#}|5}6h1UK90etrO)8;}_Xk$1E8t8R#eBseAq_ z<%)mhao-%ZojrFUy4Bt740#){5PWBBM9hkJke?g-*M4$X8gZ199=K=lJQbOVk(3_H zs 3g=dq0I0E(dZz?lt9#D4N)5COS-(pTvG%o?lUZQ>xOP0UndK8S z#(u0)D}w87V0jHCSUX v#t!PhAI zy+3v)s6{rU-zYFt_Rxg|?s@kUt>LwMgN)rn!;;k;>8kr+uakSOpR*w^av)wfl%r$K zu!_eG^mVJN0Dc}!#jpLLa96Qj;O@$=MUdz5cQN?(q(q-MwBfi=GUf0gWoU;#CK8tE z7ltGRne8EXH*&&EPj|3D+?`_k<>>j~%{tEBLeDBb!2zHtoIM^UH@f}H&ATSidTm6D zUfieum}0g9HPkU2RU99;_PcMjmG01*?&`2&x=dw9>uqCcvJBK7V>G%&H!x&?N=Wxz z11#8S`{l@Q02EVH1BwkBO;pjH$BpW`qb}2qnJT3Zne^NAvv%<4NH*jUnfwS$iy+9ju@_z6tuO$gmd85d(lumh zfJ?*U1B)Q@x00E%|IQRKr;^^{45ajRU%~E9uN|yw!uY?Z7THF~m$nrU^HCW2tlMQv zyBP$jBwPFjE*AJ3$HCRVQX>nx={Yv5lQA>M#2C-`F$7n{_O&@2n}-T5hj-7|Xyirt zF~_x@KGu$2g?CH;3}KeEc9&7vCI$ru!LTpU4pR5z{nBR4B{>>gQ#`gEaaSVi2W4gN ztTciBwo#6*6U_;H4qISj0X{mXjjj5P$7Dfw?^jg36BO6s)Y0FtdlHAi91ctDTJ{I@ zr_Yx?)VtmjSO^_>uY%8E}L9%S>KF(Jx5b*uiy1^}{mfei0Qu^!D=jf_yQ}X~ec{ z2>Kv*SftrDp9-n_l^-C!IX!+fGmJ4{bt$m(sN?#;pFWeh0>P^M2xTyb!Q2n?y~VP< zZwogsjAw~m)7NHP4&x}SZHpFC#BjqOabN6zu|)W3bX%}*K+};>V@gc*4@>S+KW=j3 zrsYO~JnR3#PYAu%j4ZUR;@UZk$eJ}Y?C=UGg^i_Ul1vrIQTz}l0#(js%Uo mUf4flMv%ZaFd5ly%dUt|GI=*pzk5s_)V$+%~0J*F{`=CcDj4QPg_ZCL^JPo1q zD4q}CE)YU9GS4P%2HpL?+GSIq;db^9EbC$(l4)k};ctes!c$6D>*X_ 2D49Su9TpGbkiAJ}RJLzIdY35>R#6j;0FDxE!X+y=u0G3`gLm)Y|MM9g;^DcL+ zq?ePSK-YCYE^+2$j$9GlSdJL^5f*@HPk16NQYx>oasklylTF6}L-wH2=^dfP>x>0F zOD65G=@oAnD?hN{CNiuX5@m lB ^#NnOhe|8eiUHXf9R!2IG zj`7j@i8~Clc3C*J+rm#Yc<;Z`)M?@D_$>o|z|5LOkLoq!94AdSf-`b5p$R)(B_>!R z;^jsStASIn>`Htr-E_W XOjAKtw2QrcGcmivXdLA9f=QGaOVdAq3U*n2M^yZ=SEL;3SlOtvRnsdjIZ zuf}7TWrE-Oq!{XEoeg~hs4>!chz%z_6n6s)Z-9DNdgvbA*<9b4%M=HK--AmHaVbxz z3$(lt&G7*s_83@?7_HcSZ$IB6)?mYz>^9XUgV_Bb+g;u|Ey^@K%7{g14DSkX-U-Qq z6Zk1mH`k*gzno%RHCQH`9O5~~VqL+O90bp)ADkRFI4B4VmS{O5%TwM3;$QAd@7R6X zQ+P+GD6Pe>rVN3_M?Rw;T#dij?vT%KfzKl)p{Zx(u7hdr07gU^XK8f^-6z;K2K4r) z)^-M;)c7)O^acRev3JIFn4$IA!?LM#nukJ~&>T)vJGJ6i*rg$Q1E2ef-d#aimrS2% zliDp=vqc ~^kGJ+7+@*$|BC$ia9+PcSzES?oP4rGo##HP| zsiW9EELd0Kb=lSLl%oerCpEkQg=hqPO#I1y!&)6xWBYI*n-xjZjW~mc7v1nmQ==Jm z^PXFcHEOP~{r6WiH`G};ZXx@4v UBh`B?E^Dg0%RV=7pt4Y^DWs|8A4}4Xa+w%to#L j z%g=;E4L <&Y77hW4F*FYHQt+NMa41V8HrgPZ4=YRWo znp#+xT qh{g!2n)da)VdPoaY zgt;%i%{*8k?1wD1-N9cT;s}eT9X8Z^uSp$8U|~vM(nqA#{FnKu=N3++8;M*>IAij; z0>zOpc Jc?WxI=_6TQMYbnkh_ z^Y?lKz%8PaO2HMHZ|Azr^h@;hnMB34BdwZ-7xnvtjA e(KaBYT#uWtC(lHi45io7Wg3%xeqz|daO_VF5G!3un5^DGkx zc0$Hh+70Z_n!+97)@eL4hP=SLyE>$&bejJtX$0+}+**#-f%w!6zCi5wJnFkY{HzgN z57~WuZ1_d`gt~Vq9X%iM2tMPp4QKQ@NH+C10ES#yxSLUD)>S1;(K2Z3__)m*rJ12; zpPfCchU(CT@NNrayww+l}-jV8TuMe4wZ--`0~Wed(|?zmRbk2@LjTHOeCDr*|W>=4s6NAi(NcHla5* zpTAE0Owo{r<(Q24mJ8ftf0rlX0~2*J{j2H&sJ6Gs$ n!S6SOD-^Bfv6i-vLN!K(Vc ! okxHA!I}Q#xOH$*-}i5g1a|)V`RHS!RqD9X#1m}n`Ah+(yKC7@b-6F4p HGti!I%&Ymq8=*m3Aea?%=ko`Hcu)o7Xgb(`$7JeYm!}TcExprYg zzk*XbcKQ#C{S~*r`3yx{ppyL3fHJn7)z3&<|DVMq!O-a1g{(f}d{cR5Pl7(Y^as{p zYwG?#i7mhs(WhY1MJ!)I?mCw|ud4J1Bwg+-9`62N6t^pPZh;7UD7Ml@hx7su6wCO5 zm CdKZBpQPK2$95w8Xd{0C1CDuk0m!?dCv?wV zZ^i7x5n@aSU%okW_tAt2R#R~njzSbr1ip^D47W(54M#m`*_Y=}@5`sM$~-(;#h@CA z=TB)RrkH6h>!I3-J^uJcR9N!`z^9#vD84wR$G zXbk5Cn+p3bRV({ d90UHesc zMGD_}Bz5HNGQM(qOgVhlS3ipEo}a9o%lqTf1!EWrSqCaw_^pne;OdUsx73q}9~dYn z&=f3eTfU_F6rkx^`-6quhy*@|H1f>w0#h;+bCo;)x2-xhMg~LSVEZ7_)A?#PuztH; zmy4pHj)9W9P)eS&7Wv(hm@ vr= ev^QGA`qAr5=7}{ zg2=7O_>23?$&Hx>(n`(Jt;Y0*7$6yoFrQY%u%nhNA!{Ys3tK1BrS8W=zOs3Leeiy+ zadf95GbAD`A%mf2Z)Q{GnAXNYn1FUWc=Kn|b%HRsYZwt zfKi&bA>}19ZB}LV08}yTpVBSRiDyv$1J_0WcA_k_DghICRn#2J1hPuc!WHu4;6zS? z^`ohGK*CBmhv2G0p|t!OJq|;4lnK)J&IRtoW(duGzOvbS(4f0C$?AP-9H+QBamesk zZ)whueMmCC{o@PEzJ>u_%wOVO%uvVmuyKBB_Q)1g8R25{IkMW0fQAR0TJRePfd_Mo z$EA2w+Z=~S*2aFWJg9u>yMgOtu?k@ffVy6H#ivkdkr%q6cCu~8c@ahEICzk+VHbZH zXUv^9Wx@u0pVdjo$Bth;D7^*5Pv^w?ZP}6y5~7Gnaco|*1bXrU=!p`%nMnZ&saz4(n#RU}qvd|Q-9r!EN^+Hlv5 ^Leg@)Gin{B9JX@3ovs(>U@%C%EP$$Z^|)C5b#B0o}Okm?vp9-VCmB+3`md z_!HR@G3-#G-q=!F U_Fq2#AH2O)R2@yU zH3|WOTObgEL$KiP?jGFTWkYZ_?gV#t3GVLhwsH531lYLy F^ z)sc{zhs5e|px_^bsrMy0_qtx`g^bHP9NQHexFQs>eHb=6Ud&zfJHp_`uf8=Q3Vr|L zE3FTvnvgK|g!B;Go7_ @L3wo (F%zEJ0=cUV-;jCGEB|5Hlx*+JqEj|Ka&IlYEq({zRyLT^T}pc8a0!5uraT;oJ| zIe1DYku5$uD !f0J=d}UM#Kg zO?wMbv`ULq1PB5umjd@o%GeD|^*D%nHX $LulI<&1;E1z6-7ET`bh#GGC)Mfpr zR?c$bZq}+iS}O}Mp7wb}dJUGK*!Hb{QQ|>hJ2S-hYPd?i;ZTIgHS?|J;bI{|Qo}^P z2RAXCu*o8f5CP9#J~Of~H~054Sup7rRQzD9x`w91>p!m8;vU7L_u`Mh@%0+JH*&;V z056f?Pg~z0^4jD8D|66a2!c?rY7gLZI45bDMqQnrH0aCi6-z80D_$fW$0y-kE0GE6 zZ0U*^i^Q1^-oxe8>Ejm0#R<>e%1glXK7umQhv&_HE ZAKcLf%3E0yp8OKz6B&L zkX!Dp$7Im-I2ZN@F)zA3GBYlh8Gjc6i*~@4)W`a4qAo%N*x_U;R*#B5iu(-dxD lh?QBost*=FC^#ku}XZbr3eJ#qS;#kv$(RIRWQ41o~wY #9WyO|UH?7F*3bqmOb6360i1P%=Q~vY{t|lXGDJ}J03OIpP*fSGgG=ZF;BDOw zn83)Qc{I$HWP-dbEL4#j|3Wp&K*N;rd_mS|KSnrIM!HHB%5ud{;mB}TuemAdF6T>0 zK355*sZvN@#I(S?A&1~cS%dQl@$Fw3F1RJM~W)64~x}9Dp4g3u$y0s)n3sxph)v(QegZB&DoNr|nvyV>RIf#h$ zb+LbSgm6&(Q6`a)rey&y^TP7@Q#7y((!a!iJHTQ&{Rw5lPW{TVJ4GLOJ%!_;; z&!PmoKiy%6dW0C}B)={|o0_g-25E#Q6E@cQT8`?JFLY^y$h@X{bUKpQfiB31)N$#_ z;`C-n8}e->>ybkT(Pk=P#PSfSoojKRNs<)ud(6m!8=Mi;iw3{_QYx3*HF0gfa2&(p z5 Wmdwj~M@<9wCp3hT$FFzQ`{q`W_gn z-hVS@-PlB+*b%*r<3t+359?L)k<-FwdcgP%1M=ax@Sp!FKw!y i(ez=PcPAy4yhk pwNn|IsBe zvqC{d&Hlf2N&i*pfrk1YU6QV2iTQt5{~wh0frk1&x}^VFfP%6({x1Dr9iji#B|$n7 zGXWY(j2)hB&F7gJX^}w~U%teWPKd?=J^TWYke<9ih+>0=FtcgV3)8zjq&Rp0R}m zU&B}fM+}7|(ok5W{9rLg#6HJ+I)$#+KEJ-J-5Rvt@jTyfZ!{|&e)e!Vf34OlFH={m z+<`bv33zf&dT4=qJU%b7)@1wNqEuLX01_v7n*V>^+J2bonZG>UbU5AcIE$`6)8gw| zZ?$o+7x>fu+Mp_sTsE#glr+wBa`S-q%`KmiqFHt0A=`^k%7+|nu`AU^-+Q9!3`{>L zn7qSWQE5^O ea5VYX5ydf(Q0Ll!2spaF5J4_2S<=G@-8cFg8Nd zB-;ec;TOFwpO~(#+s;$@zBX2ZU2ah5*lUm5yw)WnmO|4s=5Cq!$2;1pRW+{9zYM$W z`%`Z{$97CCU2=^?k2^l5vdK1`W+tbcs8J
qU zH7+S3v737bdJly7s4FT#6HDucsj%x &^4Smhp?NnyasUzL8FYLC4C9x482dUyeA4W37O`4m7^7Hs z3n~g;GkDTV_?p2~ZRo5q&U%(^nJiW7TusrEX%1BV%8soH&L1rudCcuiQa@RaV2#cd zfxA3bQcz2hMi~u8c FL>NIo3so8KtGDDk^jJzq0jUvA6 LapqB>0g-XA)l>xGuh}sn$#Nt&{#WE-V|+HPO}8Ej&Su6K@z~8N-cmsGWeCGk}_d z36xjWQeZNO JHZFO;<%qu;oc`5#lT9hOf;_94Z)fz-;h;8VFoj6- zFX2q&FP~U9U2NHBhn+B9Qr|y7llaE{(J8n{uY5}1DvPY>zl!aJlW)l?3@wJ>ruGeA zbGwtyq63a>c7FC@=`Pd+Hy;mc*QOnU1HV0?Q+kNXVyV5jtdFX9^~?a|c>F7wEI%nX zaNwkz^Y35RK-Bd{uw;cKE}BX2wy}ddsBfmfnT|-n{dA_^vzn*nFeK)@{qeY*w9nQN z42xsUayVKQRwceI^$){_`)IrgBj2nhwo4^K;wbA_jE%wI&nlZ$7%(7Vu@EH1O^*cJ z#qYeXkR|4B-=7~ZI^-~5 leNaIOYfB`h=)TNEhJ*zndg y4Gi|32Db%u+_F6@I|+dW?RT6^+qe0To!;^U%iT&|EmcRg*cRdw#d zNJUY$Q`S+s^B=K425S@iVDzR`#b>RCP3W2nmw3%O5MrYf{Ikv#7nVM78+7;sgnx1Z z@EF!g5%B15V$Bg~H@fy_+EMH&=b19=cP#AY83+;u`IX{&&6vHZ-kueG&=vhY0cR); zg}PLgONym9%gX!rpLOs`&{FKH^+~$Wb`7?9qsif22?ZM71<_7h&*} ze2#;GYR=0>1dKH6RT4)N%%_>Gn!_LM^$}hqj^ou&j|*8?UT8WWW~F6O#|r|^vmP{M zJwaBYbf$8$volJEsrK4}^?KA7zUGI~sdaV&f&(^JR(ahCFrQ`q>ZkvlRr4(8_x+4t z(Lu0psP-Q9?Y(bzr$7=7^4zz>&-wZaFaW-1#P#y^o{WOP{roIESX@;&{w!$aMz!jp z);T2_-ue2I?7d;>FJdzsN1Ue}+b;mO_WSqLm6q4pv=!8P9{S+*9^|W@$f93AZ-a=w zU2w6F+$EI7m66xY+e3doW)@utp*5_Qmi*#s8hD$3n{4dN@4|;ZZy(j}J7sf!a_ROg z>Md&8dl(}bO|a}en2j%R?di7edW+jLYn&Sf8fD~RL`wu0!3iO#f1_#~636--mC<-G z@0$IR{@@|LG_X{-G{Cv;v-lT%&1;wc?eHdq-a>KhsaNi)yq2eeXxb$g=4t_!IMr|= zh;X|4_~h;wtleEz{aU@BqGPZ|T+>YCtL2 ^i=)seZsfyDmH= zi{+$;)rS3}t8{iFE|h@LRbi#nE=CV^y9Y|}f!TBBYd?*YJ!^j4i!fm3ZN8P8)9<^T z!5Z)sK|303e!q>IVt7TW4Wgwj<5|3uIAXOMl!F(q9^Ts0gLxdlTkYHFTsrOCAJc%e zzRl(MCF6WzfC{ZyKo-`AZGfWQ=zANY>RD*X@jIfBuhRo~fb5H}w@=jc6ZE+_dD~r{ zM{xyqI4wA`f71wpPH1mEEp5aLc)rVrnxH(mD%bEIl@ 3&yxT+*O@H_pu{J>qLhQk(SX}?v z)9q^Mo$@nn+yl7`N?-3WkTeKT@cVg#`0*y|P~auz;_If?osobf&v;KH)RuYZ(zB w>a *?)=}AL^u_`Tsh>I94 z_Q)M~VI_UFK}^od`0LS;($muw$cv% zkchDdQemd#c?<9J*x5zt+pEIhF=Rgl;6GwfkUm$)!FKk#2`cHEGY!M+iB3kX56xO- znpC=C)*K6}NXNJ8j%Q5ZeyJa&BaXM1DfD7IMW;hhgQ@#^C;7xFLv&lG)My&|v6g>p z9q%o*SZOr6X+-AaloM>Yop{>4Kr(#4#9bzJZ#uj7)I(*X{-g67*)4CL!`iSRkZ*`H z%{+C$KS?4ke&JPUdnongZZ|r?<6w@@GurF%D~B(U$RQ(UCzBjqx;1l`gjw(-Iwig- z>?aY&8m2Bdouh2}T|M3Z;bBN?T)Dh@yioK?qMf$C*=W4Yx$V;T+A*~v5TN{+6!$|G z`dBU>RGyM-TdnbCD1x7>El~eN1v{TkzHTWzTQudk99ZEBToRj`JG^>#cxe8#^cPRI z{@_Z=Ha}M|^7geUyhMJb-}=KNh^agu4moy5gsdXp-af2F(xeP@opK$&AEmVdG8B2< z*Unl@mez35H@g@EruRNPnxqHPCXO0)WTCUBu2B&B+nDE@mfV}lxx>nqJjix^^+5bF zk7kyI#zU%e;E$Ht?qKpaEd(AHeyRffr23M1S;6qQV9V;C73QSoN~JLklEO4&vF=I~ zO#?xDOP61Du#Mv?e_QguncWI9uy3_I2C;VC_PpuY>-}T+6!%N^H*qD}gdR#v#mywmd;m6GsiF-HcJuOy%b(e~7 zYDO`=1$7Ayw!Nk~ Zi ;y9XL5eSFhI(2B zeQ}x{RD3)+T6qKjmKXeJ$YWj{I5=dvLG*&Tr1H+`jeFvw2|wn_80$8hPTJ^KFx$dy z+dH0xS6?0QX8UC-8F|6G9xub9?}{M1<)PXr{l!nY&0Xp6nOLiABtiY3hncTe9tx2I zw~MwLj%lDh&by&=?%t=?U+$h6K&1OplfQI2d}LeI8)_D}zvtHL*P{quLbz716rn7| zbKtm6@3c>57govE29mzg+%+#*sC=)$qOaJOd_2i vSR>sy;2$n =zEz_tS0=D9Iz)pyA9O=5{fpTWary-wxi zo?iRpl2%7R-@VY`*S%sENZrXy70}(pGEH>m)L=HuE!b@Dy4D^|uu@PB A@}xI5>J=zpFpH?|D2ueEFldW*-<6 zm=ieNU&JDv56Jvh7`DvT!hWH&K$B=j^KIy_fvq$pBm=AH2mr_Z#ZvK2S<^X)HJA9z z-RYP4`mPe!+R1~AU~T2F;I7QT2$$1vUncXYo9jDRr;4@MyjZ~f;tszXshNw<9Y<&a zAB{?9aTmp}6-1kX^QnK5YW-TndqDK(=kDnzBwJEeQPRA_F`#-dp_$g7SuAbynMXKQ zRKj|EzKpQkN`!s}U>)cnlG3;!WZIGdfo7PjA<(ifE1c)T)SyiaS1-l@_)9|D)`^$S zSC@%CWk8O$yEnuAy}uV2{d&jBob}Zyaqlspe>6!t2bvm#Y;9J<=Wb^!nw8jbW$4uH znZc7HVke3r=v(`Ug{;6>1A0JJ!;*>1a88l5hKM?0XK-?;M8N_MYs`&dT=iMpDrHr2 zx3y6tg!%KTcFdNi3h t#FMJFC2Pw kQEaiMH_RnkcWLqEu28BXm>JLcZ^qn4Pf zDfUIMy{8{g87E4A{B2lo`p_-=n$=Q+F_wdIH+4hRjaW~2fu$5qNa5!nk1LR^z|k(U zxp{NuxRYLn2#*Drjs#E-t(QQVH&+s-U`!5!m(N4%<>P)BU`}-%13>gX=BcKZO0#5h z?VF}3Xgq8R{WF&^jcYYz*!%v`@b#~!SNxK92hMUQZd@jwn?5dE%nRs-F;5PTus)?K zX~lVX-!>%cv!H9DI~!Kk>q8Va1$hPi9I9_v%tn51&}d~N1t_u<2JRAK{6{q!wz39} zeXH4Cab9|i{5#Z-ZTcR4V{aZWn+_9!PkNR# hHI_lI%FryOHK~ptXq)YPIz-9gP+0m;?aY7`A^iR%&kZ{;Jf$s|DaV zqz_D^(!kqz-O{%ubzBZ6=y@;|E^CW8k(!h(4q*hRPu64N)$;soMw_ #Er{E^;`8oDxXHYeAT_f+UyImkgI|M)=F~c6Uc7!QCGI?3++UDzh$Q?7Ark) zh0NHbU!=f+nk~ted`>+rsB~{g+Z1F$WQF#b)CwnW#sT9E=#9fPb)k31JU6Quv&HIw zd*YjcwNVchO$IpLP#c!+go_1>RNC?lv1450qDB2cH$-r--Uh~*p@mV_xqI^tFGKwe zQ|FSVE*~Z1IT4+%99$RAx@pzUn2g1(^r)W r;u1Y;zS3a~Pc8)R Z z=y+AP^nKThMyJu<`-lR60D5OhkgUEAP1X^0Tllq-f7NPOk5$Lyx9i*sY9Pm#D6kJY zcFVggIL&)f>%8UF(F}fzY0b+MonOzh*-g-ItXs8*_Hu6bbkrO%yl@yEdal=&KGfhT zJS``jyN2$yGr2@0nzv`l}bscU(K_ieXkvU8@>LnCE5l>aukk-M6KkTiHj#-7zTa z`EkloZ89F8*mu0;WMv($*8o4wcVJpCU$qdf?^a=GctXeGJ8GBYnirXKLT=DfEG^Fb zq`%@wqM{ia?C6>#9c0ew#!=As)$0FlnZ;M+{`)NhjlEeBui0Y3wmuc!>}u-he%QNE z2B657=43L&zUc8XBM1m>D*d#ObKY+L?qo+&Xk-s<4C~q_cmSg?@nK1c%PUPe qtk=>1cuclC!A@^OJ(=UQ6mcGPO%%xtW(Tb=$WHb1|^B5 z#))Uj?o(;%iQYLq-klP+o&4;FG =Ikw}4R1XJ^qzTSM(dDq^|UAcV#v&P=O z-S;xLUw*+L;PYPFhT@T!vSx&GOt%YT;9{-cK`HNn%!%ZNG0dA1@${a4*Ou$@qOUwC zWIsk8s(};7Qj_xClP!97uV_ZZk4PNVKwe~H(CU1SUO9NlVx4SVGf!u2nqs2@q+7dA zDRO356sM!QhYk-OBKSN2Fa8 3DcKtqyJ81LvqTTDF zp|S;m1$@Avfd@s?&knWRo@=&Fn}~BgX$Nu#rbOH;Y#X*&{pY(DMd@05X3*Xq5C25! z(Loa{-j=nkX73_S{Em*3*}(_LYtd{5o8AMa6L$`)@8LQnUHBjP)LAeqt5YVq+0>78 z;8z1_%JbB*|Hd8?U1yp(#2%wFvc0DaN6t+2SBdR1D9yqdg^7ahnuWh2n??5PBg=eu z+3V!D9&|wy(d$2~hw1QH982qmiLK@;_Ue*aXC95j$o&>I7+zOJ >qGe9j5KD-K@@Ay6EI9(G*9hvO;VOTV`@%^p? YiJJlK zxB|BwQIo9at+l{;E&TK-cQ~C0M^qfYd$0Xp*9x}hod<^fh_x^InqhR~F$Op#@*b=> z9g;bnwZOhs{0vEQ;zQd*3{BxuCxd78Ru2S=U+0~7zpf8J#I|oo5%3NNxv9z4I-9ah zEZWb%qK0MKA_=tc*?m6qomko5qY}R-zjEq7B90LC3>_jkg6m;{{TQzzHEf8R;mBaE zN)erPu19zrzpQ(A2>OJ5H@>|OQ@$#pDq*xPiN_ah9y77=g0ge(S=;N^`;B=%WGvt7 z&nS+K;^H%6$=2H_^X;nb#2-gFanH8zQVJTo{Zn7>!Rb42dQ%~_R>`M 2WLQ12-3Fv zp^ cJBKC1NVEIo^u_j7KCX-l!mH1#~2Sq^%R>QqxbaGO-$RLaw8@z z_Kj1YZ*ybCcZA?`T~_F#QCJnU=1Zf vUFbM=S6R*y8TpkBb=J*~6UDI^vT?F0W87*9aK|Tt38}F{8 zUf1#Pc2)}go{z>clTmeg?W%*S8jOdj7NlD)=kk+odTd)S$Wur@C*;rRv|VGms%D8X z1y5o9eYQW^zh{^_Af(F84}(@N!EhGyx1{P&r=Y0`gGdwY(PWDF%7|uT-BUQB=#fVp z3(m|nbIL)I_>ufQ>)%|>8yS3cntMtV_=#Wijzu}v#+^5IAi`IEd`YjZq4vW+z%UU7 zrW UGY8$=sO!ilKH}{1;t5BYQ`*yTN#2<$lS_^u{4Jc9+3qtyS7ft7 zS^Yts;4GDg+0nZpl=7KOaU0*(+DV|mm}l7 DM#xW4=1DTV$6gOHTS?9nKif{Q2b4cn&&xN3%| zTP3i6xZRH~Xy|nP&VLjXQv3=|Dl_5rkG3!ZD!-my>Vy{Obk{!KA#<*L`n|t|#wmFj z)+&IFpCmYrOHjJcDW}Npd)I)KK5lzOJ34;t-r(#j_m@=?|DTj@jZ+g48b`vX{leaW z-_$-^ &VXF;DQfS|7AsRkXiA*ai14%H2sKR zJQ~ZT5m|IzVogF(UjDo$q~YmHt4AHrFKL`MmIbSUcWS#^^M$gGV3H)Md #6QV0Ev0qJiJqhj~XL4>1l~xeOduDz{Nio zw92#4aTTYNfiC!NnptPxw-f_V|GQ!_bX-F&6aCQlY~O^=Cu^&kEI1%nA5C%nBY}B5 zRm2Xn;RxQGf2Of(RI*+8x^fo8=PY}DaG1GVW3M$YbLb~$tBz~wOi>*iXUP^6R5W2% zNXFdOK%(iQP@%D19%E DHZ#c zD)gpBhE?goA(d_!Wg`Cdc)p8-!l<9kPm#|}^!m dx!tQhquwkYy90WihxKF?=0`RE`i1pwH5*P~(wwHXqb u(2u{CzA69QnNA`>Pb}!k0(G8VayQG4~i$m;_ zo%t<1nffP_k#a>`+g|CwO_Rb%!?E|TeoEfptRS*Pl(_2O)t+O~=I#8sD3SiX*d4oE z2nAt3eg*l=Z7HfYf+KQS*dxj?T u;;Z_>;ue-G~8wdf3|&Ic@ih27yGh?1!M1=g1mND4L|`QFC~oytm5Ii}fF9;31_ zHuABet_r!@fon&Hmwp-X@@(Jb^0K}vNArzQrt_@@Na{4jF%pI4W_|DU<=#AbAA#B3 zkUqA|cu=LPwl8p_WK@)I0sLwzq}34*@Sw|M)B%sN;?1iWTaLN^_?2v#ld#>rVbKL- z+{9U?skK?wr!-CWcG`^QGSTcS%4oSZuDg?bOgUaTggm$13@Ym5RsX8FL^4I!%nmYz zJxv_{7UN|*OMlnrI?B1<1J2CT$1YPSl(UA8?H?#DeUv|>HZ=EduwXQfXA8!PaL!^{ zq4^_i$QFn7sVP{Zp8m>sPJ?3rcgt7yy8oW|YDVmIq$Jy%mu^NsFY4dg0_rb`4x&dU z5C)*&k2_^mAn7Omx2!H;+oMY6m-jPp^V*7^V`3aoThni{!v6I3bWf0Z&bw|q>Nl`Z z=#FMiH2tIn_Fg~I22VfeZ7FrcAwr$-!=S3; k7H-9?pE>q59;@I?zvUzZ&k zaiAVIPG;6`Lgo^MTzRZcyzn*Ffy#-6k(A*
q*`mPvh(D_RF{^rW~wDaK>c;0j+fL7Cf8gOV|*wf=ru*J6f(hyG#PB z|IaTD*65oUSHm6%Pm{tLN1Qr{+YsPOAL>4y$<-Q1mJu%Ch$r*Z}a@2^^iH7}<+* zlHdr<%_mgmC-Q8>@mo(PAphRU#qJ#*0A41O-y8bKzMo~k4vI`PIBg^^xnY&usZs&S z6s-(imjRY361{&okj!_IW>?IMsLUBT d6P=l~HR(j(%& z82gG;1yzexo0M)42a6t^nRyXK?0*k4X-Id#bKm)(TL`%giyK5HiKkjc6@M>PBvJxv zE*A^RWb@EP%&}7OnmJLMhl7f@tC1@1K9NY)J852+D_m^u{4h&0+2eIr9_3H5s@8A} zOH00^&g{qE{@5?jp7DOt)!cg( tX^PRsn+W4Y02v-iJ_M>Hia~z z^VS(rn(^)dAhG7S;_H3WU`g+ew;jbC50Pg8g1h}&AM|q?gB11(1>Ox)j^qNa&Q8H- zAdz`>GBJ74Zb7U5^9caZ9~|}m9JZFBg-Sh!MsL*brS;~1ml#R(-U9q=k3KY2CQDqM zjQ8x`hzrHO?&4@|{t+A%5S5!9wPea^-111A`8K{7ep*xbt+i@|KbAeW=ioW>dXVv) z6@cDIEoAZq`@^)qilCy9s`8ejKe93YkPn$gJgglHR|_CBW?l0)r?Pw$C7k_983U6) zgGf(}6i`g2w_@_u%DAZ4bC>%~zL(!Y5$5SmZ9QX~QE+_guh6FM_SBys5?;Rf3
}_LorZ5kjQbJ@ZkdACqK1D&2MUO}v-g zizLcLW$log5x34<%yRH)CQK+OA79Z3?@;fdd)QD7UPZz=thh@7KYz$`FR8ug0h`H* zZnNyxut17Vz7XM;kCJC`z&y&(&t;_z`lue^JRcR9+Ku3VDi14X#!mfj1ud77bL;;2 z`gC;LLnN9W&32jA3Inc40+BY|=IGu}q{Pb{D??yt*^#dw{20cxZS_z>twBQf7{a+p zo$Su~$6hyx-KNvz&H=7qOEVDr4EP(}hupAMSm#a3Eir}?os`74`1keZ;K5=b@?>^k z#H5keL{CPkZF-iSD;ZTW_*2oQ4f?Tr3Z}~FVXVL_vo7T3UW2elUd#RVv?l*^aHB3K zO(59e=#s8HVorsigsrI3&QdL!suc q>ZOu2=*#FqSA|A$}(^O?;Lo4VBWWfl8_=IFStfKh*BZ` zx9dqf9rJW#g-olnz%8P6V$NB2YCD#`K_hpg$cpor#!7r+lgNi=@caWPr>Wqv^d2&Z z*F!kzwAIlLeDz(K$@C5A!il_VB$-|za ;8@NOmxrmxzU4$W1H-S8goCXXwx_64Pk^a>xx%Pgurnj-KH<{S6-3F3Rw~ zuyDRE)_<9BQvgkE5#H2A`1b7P6e_3X QXNu~I%N$*d9c>Ng4ML!Y+ zAB8W?SKaozgVxkK&*tFMeOsRVHa2ZaR!og(;$hB>=|0Esd!lu>=M3+UI$h`q+**~{ zY4SBrQ9s(fR{J^|T(4