From cf4183f4ef54c5c9e82176cc46451934c8fdec2a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 19 Dec 2018 12:35:50 +0100 Subject: [PATCH] Swift 4.2, Xcode 10.1 - fix tests running agains SQLCipher 4.x --- .swift-version | 2 +- .travis.yml | 5 +- CHANGELOG.md | 7 +++ Makefile | 2 +- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 28 +++++------ Sources/SQLite/Extensions/Cipher.swift | 5 ++ Sources/SQLiteObjc/include/SQLite-Bridging.h | 3 +- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 44 +++++++++--------- Tests/CocoaPods/integration_test.rb | 2 +- Tests/SQLiteTests/CipherTests.swift | 10 +++- Tests/SQLiteTests/ConnectionTests.swift | 35 +++++++------- Tests/SQLiteTests/TestHelpers.swift | 9 ---- ...{encrypted.sqlite => encrypted-3.x.sqlite} | Bin .../SQLiteTests/fixtures/encrypted-4.x.sqlite | Bin 0 -> 8192 bytes 16 files changed, 83 insertions(+), 73 deletions(-) rename Tests/SQLiteTests/fixtures/{encrypted.sqlite => encrypted-3.x.sqlite} (100%) create mode 100644 Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite diff --git a/.swift-version b/.swift-version index 7d5c902e..bf77d549 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.1 +4.2 diff --git a/.travis.yml b/.travis.yml index f278301d..909e5672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: objective-c rvm: 2.3 -osx_image: xcode9.3 +# https://docs.travis-ci.com/user/reference/osx +osx_image: xcode10.1 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="11.3" + - IOS_VERSION="12.1" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/CHANGELOG.md b/CHANGELOG.md index 648cdc07..027611ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.11.6 (xxx), [diff][diff-0.11.6] +======================================== + +* Swift 4.2, SQLCipher 4.x ([#866][]) + 0.11.5 (04-14-2018), [diff][diff-0.11.5] ======================================== @@ -57,6 +62,7 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 [diff-0.11.5]: https://github.com/stephencelis/SQLite.swift/compare/0.11.4...0.11.5 +[diff-0.11.6]: https://github.com/stephencelis/SQLite.swift/compare/0.11.5...0.11.6 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -88,3 +94,4 @@ [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 [#797]: https://github.com/stephencelis/SQLite.swift/pull/797 +[#866]: https://github.com/stephencelis/SQLite.swift/pull/866 diff --git a/Makefile b/Makefile index cd4afb8d..d98c8deb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 11.3 +IOS_VERSION = 12.1 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b03a9ce4..c2235aca 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.1', + 'SWIFT_VERSION' => '4.2', } s.subspec 'standard' do |ss| diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 75b38526..a155a115 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1033,7 +1033,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1055,7 +1055,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1069,7 +1069,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1083,7 +1083,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1106,7 +1106,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1130,7 +1130,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1269,7 +1269,7 @@ SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1291,7 +1291,7 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1304,7 +1304,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1317,7 +1317,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1342,7 +1342,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1367,7 +1367,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1383,7 +1383,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1399,7 +1399,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 6c0d4657..71ef1765 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -6,6 +6,11 @@ import SQLCipher /// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/) extension Connection { + /// - Returns: the SQLCipher version + public var cipherVersion: String? { + return (try? scalar("PRAGMA cipher_version")) as? String + } + /// Specify the key for an encrypted database. This routine should be /// called right after sqlite3_open(). /// diff --git a/Sources/SQLiteObjc/include/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLite-Bridging.h index 5b356593..f8c2a3b3 100644 --- a/Sources/SQLiteObjc/include/SQLite-Bridging.h +++ b/Sources/SQLiteObjc/include/SQLite-Bridging.h @@ -23,8 +23,7 @@ // @import Foundation; - -#import "sqlite3.h" +@import SQLite3; NS_ASSUME_NONNULL_BEGIN typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 3b9d6ba5..fd1c8c2e 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.5.0' +gem 'cocoapods', '~> 1.6.0beta2' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index de03030a..b5172144 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -2,76 +2,76 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - activesupport (4.2.10) + activesupport (4.2.11) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - atomos (0.1.2) + atomos (0.1.3) claide (1.0.2) - cocoapods (1.5.0) + cocoapods (1.6.0.beta.2) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.5.0) + cocoapods-core (= 1.6.0.beta.2) cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.0, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.6.5) + molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.1) - xcodeproj (>= 1.5.7, < 2.0) - cocoapods-core (1.5.0) + ruby-macho (~> 1.3, >= 1.3.1) + xcodeproj (>= 1.7.0, < 2.0) + cocoapods-core (1.6.0.beta.2) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.0) + cocoapods-downloader (1.2.2) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.0) + cocoapods-trunk (1.3.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.1.0) colored2 (3.1.2) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.4) escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) i18n (0.9.5) concurrent-ruby (~> 1.0) - minitest (5.10.1) - molinillo (0.6.5) - nanaimo (0.2.5) + minitest (5.11.3) + molinillo (0.6.6) + nanaimo (0.2.6) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.1.0) + ruby-macho (1.3.1) thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.5.7) + xcodeproj (1.7.0) CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.2) + atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.4) + nanaimo (~> 0.2.6) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.5.0) + cocoapods (~> 1.6.0beta2) minitest BUNDLED WITH - 1.16.1 + 1.17.1 diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 4bec8d40..98a539b5 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -42,7 +42,7 @@ def test_pod def xcodebuild(action, scheme, configuration) require 'fourflusher' - command = %W(clean #{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) + command = %W(#{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) case consumer.platform_name when :osx, :macos command += %w(CODE_SIGN_IDENTITY=) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index 3ee0b135..abecffba 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -73,11 +73,17 @@ class CipherTests: XCTestCase { } func test_open_db_encrypted_with_sqlcipher() { - // $ sqlcipher SQLiteTests/fixtures/encrypted.sqlite + // $ sqlcipher Tests/SQLiteTests/fixtures/encrypted-[version].x.sqlite // sqlite> pragma key = 'sqlcipher-test'; // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); - let encryptedFile = fixture("encrypted", withExtension: "sqlite") + guard let cipherVersion:String = db1.cipherVersion, + cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") + else { return } + + let encryptedFile = cipherVersion.starts(with: "3.") ? + fixture("encrypted-3.x", withExtension: "sqlite") : + fixture("encrypted-4.x", withExtension: "sqlite") try! FileManager.default.setAttributes([FileAttributeKey.immutable : 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 9480e3bb..eab3cf00 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -369,18 +369,23 @@ class ConnectionTests : SQLiteTestCase { } func test_interrupt_interruptsLongRunningQuery() { - try! InsertUsers("abcdefghijklmnopqrstuvwxyz".map { String($0) }) + let semaphore = DispatchSemaphore(value: 0) db.createFunction("sleep") { args in - usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) + DispatchQueue.global(qos: .background).async { + self.db.interrupt() + semaphore.signal() + } + semaphore.wait() return nil } - - let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) - try! stmt.run() - - let deadline = DispatchTime.now() + 0.01 - _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) - AssertThrows(try stmt.run()) + let stmt = try! db.prepare("SELECT sleep()") + XCTAssertThrowsError(try stmt.run()) { error in + if case Result.error(_, let code, _) = error { + XCTAssertEqual(code, SQLITE_INTERRUPT) + } else { + XCTFail("unexpected error: \(error)") + } + } } func test_concurrent_access_single_connection() { @@ -391,21 +396,17 @@ class ConnectionTests : SQLiteTestCase { try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") try! conn.run("INSERT INTO test(value) VALUES(?)", 0) let queue = DispatchQueue(label: "Readers", attributes: [.concurrent]) + let nReaders = 5 - var reads = Array(repeating: 0, count: nReaders) - var finished = false + let semaphores = Array(repeating: DispatchSemaphore(value: 100), count: nReaders) for index in 0.. 500) } - } + semaphores.forEach { $0.wait() } } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 8d60362c..2e491b01 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -100,15 +100,6 @@ func AssertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclo XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } -func AssertThrows(_ expression: @autoclosure () throws -> T, file: StaticString = #file, line: UInt = #line) { - do { - _ = try expression() - XCTFail("expression expected to throw", file: file, line: line) - } catch { - XCTAssert(true, file: file, line: line) - } -} - let table = Table("table") let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") diff --git a/Tests/SQLiteTests/fixtures/encrypted.sqlite b/Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/encrypted.sqlite rename to Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite diff --git a/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite b/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..36890365583fc0fa1383f4377d7c9b844059872c GIT binary patch literal 8192 zcmV+bAphU50pp8hy(yqK&QH?T=pj>XQ#YZ$vEL0D1VwzN+29B&{r~vU4y>H|8AqoW z{*8r8;(f@lGt@4B_LE(PFQy0U)mKADn*2FfTp)@TKrkyr;d268(^B`MTGF;I{~9M8 z={X| zZMb3wOK)gC40IPXuAl@g4Tq=HjOHgf3w`#1u$k2CJ5JtzK~Xn@NvcIS7?^1yi*#XTgUG^#R9l%5t)eHjCM)WoVt2TGR%D zj@Qgn6qMyz2#hdA-p9%`ssCozLrq^b)R4ip|FK zV})XFFON|d?<|eZi4bQ%mXB?rXQtj@K{I>#N$vlgOT1hk<~Pf5E8;$P`!U85n?1mF ztp(ooX%X zbJ2=>UG733Qy?jpQKubErRHfA43!i7AHEbr!N6E43cP^yhrv&PtcYwL`Xx=O}O9*|MmolJ+`h6w@x@GH~4!rxI-F;`v~KsE_ zdf^s@P6}fv5p&yb?5d!sUZGug-1JqdSLB>?a<8m~OHtv~vD0SEl)*(%WmG@o^8VWs8=#vVLEQ;@AoQeD;ke zdYp!6J|mNH6e^lFt|ZVmzI=nz_@XQ6m=y<==p4M5WU^Sq8YhSsahJP2ZVx4cuS6pT z=zhzBDD2zVjlfS7B3>1Z8d%Lb4cB^b|4Q*8$2)YBywK!;g5-~&^E&1BlF$|fO`8qO zK3c^bKiwbLyo(9X=;J^$>|boEz#QMO7?29YiaH-YbyxmG6FCc7D3tk)XR5mDUT#i! zTf;%gMx7YlBM%J9V^T*Y~E%&;iWLbC{f;E1vl^+z&j2cAL3 zC4vvV%t^4?MKhw1Xr%~TQNmvf^XX>kY4QngA9(t_L@wfWo8FI{^%#4Sps=uDc)`4ED^*#{7dv-z6glvtOMsiyOMu?SE z%6T?QJ;p0I39*Wer|6=;Izd=+a5&N0C$6yhE}C7;T~e~U9hIf|b&l06$G<2eK0B$l z9pnj~q=&}lV@FpNm@9#bv)FdFtkJ%D>EMlLT6t3FDsF=Y>ez8DSc2bB+Uqjk$$}lA z;%yjBE1Acd_kut)- zEsDu9=6yD>RFNVnBjqNTk8qm@Bmz{tUEWEv9`@`Z*P@DPu{z)r3hwu`-#(9`fMre1 zn7x?LLd*Mtw5RS>$LtdpikcATx)^$;YET{LYQ$hKWkiIxfpu+rO%8z0Jg0uu?E|fP zps;C6=3X?9M)Q^)Mb3Vk!7^S<`fE&&WN8vz+W){n>htZ6h9+S%80q%gJ9&6Nw;mlc z*c+pFkqI~>lrqy0OC^XAA})eD&A5r@9uFcJT7FwatT--I*Vm*ML_Kkd#U?Rf=}dn2_&s zbP?CKJSOTU4J}6>EMfI;TU!*qo(pg*buWVhST1Ju^bDB=UB^^s$qT_}_z5t(DbAge z+H3bw&eHxrvg6jQf~GtZ*_zwCWVAK1}wjw)ACrWEeH*~!6^71Gzr>N>~LVqmRmu=g0f zf4Sk2qDN8M##(!Q?N28=nGzXOvc}9$23FwYizPxP8@^H)V1ywCqvrU>phB!`OafC< z-W{EgWjNh}RPVWC$-eO2$VlxaQ8HRAP4Tv&9t`1gg~nbha)E}$6^n#K8@ZKh^$6`i1Bj)`}@U?!gTY>Rm4^u&%oCMS@U#j4VNb4Bxh)_V6$eWT!?n2n8bZWlMJ_CGFT8&%I7mGMHpvjImD#_@d4p^2W%!rJ z4;LVmaD2e2nqBHBuVNMm8P&cCu#5+{PigBk*}E1a`dP=L1YeX{fn2_p?Wt*M^J~i2 zGpaZmeT36Lv8}F~cVI?dZF476ZR#JsyyZd#mw$9Qd4sL1nkY+cj0;Hyi5C)2ugHTp zRsjaMZhlFC{blfg`$&;QP04%zRUc4QnP<0ns$JbF2+zS;Sf-ymJYZNbFM;jULUOjA^6pw)`ZWu4Gka=<`>T5E8m+1fG8=3MD4m zHup4y?wFq4orqK5Zy#b?`G7<^)l|RH{++V$r#vvgJvT3AtnN(&=|Sw6c^E&MFJj{y zQWA2XMrn5jmAbxG;A&IR3$0e=0`toI3N96gXV2`cb$SU7i!30b4nxF$z_FBc196H1 zH`-D(I#$AbcQm0@DlHUfl4@$FNwNliJ8rLSTy%e$$wz&Bz?DS zDOv%XL*_dQ1!NsI#Mk|7DSdGgKRynxEy1?aUy6(f158_9Bn-aii>A(BZ0SKny$%)1 zN)XEYKL~N9r{MZfedFQ?P*877#x{?AnykKW25 z2;B#|FCl7xbo=!Ct-ir%7{HbF4u=GY&#sbG=PQrRywtg*09&=Cgf!>yCQ^ft?)81Q z7@Oj+4X;VU`8w5)@ zo~g@sV;up~00Q{KZ?u}dJ;Hwbl=F8w>wb6=aB9+}iG;iZzv{k-y#4~o7P=K3bMn&X z&#?hh!;#(tq+?q;&r2GR(I*%_2w=x*0RjRU?|n3=j3L6&BP;8mo^~&4v+<=jtONPq zE>kQ`t2CGlbH~**wPueHwYPB*kE4=u$BfNMp7>TwQ)tXAr~c4W0P3n#K?e={$t~uQ z^dzA0KD+Jg)E^d}kMsYzbs_Ed^mwb>E=@~-mDR8mrYoRFaWG^T3DeCUii!4c+=0O= zBDZDOiOHni0exRfL%DEY&`yDZG;ee9v6&!a+^)~13&|Kw$6@HC>qwUv6rs=A1A^o2jCz-d`Y0}{VNJWEX^7BX4ozfjdHq+d==fabkZa6VihU5V{NH1v8p^%$y!XJm z>1oAHXVmdSH!-uQj^kJy%3BShWXahXekpn99-#L8qMJZxNAQ<9xdkhw4+Y5%FHA>? ztpJvl1Erb*4S5O?c2rT7BGVet2dc!C47gIR6Nf+N&&dClUtz5lvnc;!yxO9#4lcGM zt779cK)SkvM>A|Ms|hX0=*{u}=^h=|$UG~q-&{Hja8x{`Wi%!mFp)Z%0G#zX`o^L_ z-@d%NoC(KSbF4tQtzX*7NYXN2!`bPU(CmjtXRw?kzHHk46wKcqIf;k{kgqL^sZBoK z72QW8>6D{tde#_HXI04Y7ICXd%R}Nbzz=@D)5F%(nDTJSdgg3GqT4;lI=3Z0w3?=Qh&iXddM1Mf_wsikcNgDH=HZo3QTooiKIW**l_{XO z-&@fVd0x9q-er)$*&E(bcXT7)8u&9#hDFAWS$1BiVwb>ito`Kl>y~=M04lYg^2s>* zCIE(403LoNkhiS&siZg2Slum>3|36cr>YwrV6arzXawR_e7A1?v8~x7bH$HRCBkz@ zTd6ey6Di|H@9at1(LX;i&@REzso% zK^!rvR}Vc_hSP%atdEfm(fi(u@Z{p-xT=WP#?z4t{4SdJhI>S`Nv-?^)mWJt$Oy1u zCPmYBShRMp(}gJym`Tn@W-_TUUJRUgr~s3+&wykw)L6SEGg#{fg_!fozBf^XFt+iC zDzT8udDS(b<0V~VGIHbuHX56I~1a-zNpAEK}Nw(hvxR03t8FMcJzP?x0$nh zumoo%v0E;`q2o#Yg)d173vt337vLM%L#n5po_Ra9x=(z?RFv|bhlW#NJ4uZBG)VV1!(6$$$#wccU?C$HLFI zFRD=r(6L{JgLjGZ1~-Zmb1(PWFZ4trww66duC$_okg~uw*~0a;11uwYp`xq65=k$o z7XNspcE(tV!|1bi<*%mly$xsSIdGQjc*s zCu%E@{1q_{#xzSLi;6!I=c6U8vkSqTERb9(!y;Uf=_C`deu_L+@wPQobKOk4YBhk% z?RSZAfzL#Jy^DHHshp@>J@8)_+$ITk#}Njwrw+YbG^J0gD; z{Jcgl-vq%lw@0jGeApXyZXu3~f!;r-SUk-^(=PK#+-gA48cR_w?3t_pze_rycj6ii zML7lqaTf$*l5cWdY{hnHhrdpfrU{-a9U*JOQiT`ADE4u_yIGJP?XhU7<=>X1L$g_1 z7pY;`_zzJz2|Uv3{alqsgVr87;V++ExMmS_=b zoagsh`BS`oC*z1?=t}r*K5T->$W0h~Dt3=uo1@KA;q(q)cd_bnN}>Yr?h*Qc6uA1$ z!{IG}y4vw+0NRS(&}b*D&PB|-eJP){d_uZ0ROYfv^0%S%TD{>I@q3GBo9U-bIaFu( za5=+L#$rKQZi6+ttF3#+NmOEW)esnq!LR?@A>NMs*j~sf6ycRWkV$JEo0@c)4iKtH zGm{YNPn@Y*+1`je1^vv`Xt4gnBC^OZpuM)r4=z@`&3CE9N^C0QzM_&{3NADx;swFl zBRq(uo>^MaP<4v0WkHc5VX;eKbQbMBt-16ZvmB`+IX-b2=(dKf;!^Ka@l2?W)04lW zBsoId{O@x^-PhZ|Hq?N(>)Qxl3gLpGfr=;kdmYC7EvnL)+;Yy{VLcpxNj~YKwZTJC zr9ab*fHM+ax8c12SQsH8Z35|z2k4AkXC-`>HN=Ef&8(bXuGIF*#Y$d}$DHB#vVjzqn$;^k@$?E?KkhWQF_M(B@%94mHH?pbQ$PzuBzj`W4CU zN$&w7=Z^vri~g!&$|;(?_y_)Y=sonZ5)xpH1yE0AlDsi3x?c_NR~$Jg%DE7NawFNg6>vz(Xl zq6TXNnI(i|D3X#nhCr+C_vKuW^8~`Dh+wPkrHLXp@?%lAC`8Fp{VK(VuD?o>Kgl`) z%k1^;r=0oY4v=>sC4iPtlG5}g45fC#phBjS+zH0QR3g_&i{*u_ll*=6<1C&bl9T@M zYx?B)8Co7EM@7+X^M)xX7v93+G!j`_yfy*l0Ta*B$)nZQ+ccf3uR{cx&ri$-2uPz! z;Y~c{s4s}ad`xDz1`b&eN%vW&Vk_CLWh1-@&4e(P@Q!m}Wdw%mxmDBe7 zU=C<&HEyMf(8h9#L>3DZG6_-u81D6bZ~BpZ-#=b|vgW4;8~PZwl3)zuz$Lt&M$#!Q z83EY%=-(b$JUQP%zNObfmnu-o7J{})p;)We&7K{5yCz(XG_U8Hu$b(HtBpeA?!-&f{wmDH4T5JC6zXwl`Gpfzz>8H)%_QC)feLNbJp~ z>{jd_`9d@~ElMdq#kwO}_Q~9N@0-&#i2YhU;km#=IVbO0M~alZf30TK_05t z9DRziuYMVcoLH*ea&%riJ>64YNiF~@;m48cu2Y|Fu_p=J)yS*$Dd9e&r22Uzo6Xli zT6REL7a;Beq7vHc_{ZH1F`%M6vC)7JCn~IY6NDcHt1U90)r<*)*HL=KJZB9%K+IM1 z5na#aEeCfs=U8>Rof}6WI8$_;Dzd{rvW0^n!DW?Hm}|^1+n0r4mHen?VezonwS!M6 zzmY_S8yzd$DEN)XnTk6^lHI0v1%tx8V%G*refWde4#Xes3#?oS1BC}n!Mh)NS678Z z3}O`XL+?q%M7fRV>6ft_6w7g++}+jlX^-a@-1BR?TRAi3n?ahN%RF0g*AhkprHWQ} z_HFF9Y$CkPZiTVQzBPpQxbzmL z!nZQ^TsjA|109XbjvcJTl>h{p%(bnG&I%+S9`mwi9w_52i;-E}rQL=M`t&Y?e30Ax z9h)hDZdXFrS8Wij9{sW77Ox6l@N}Wchig}o`Z16XBZH!rz#(T1`MeUOW^mh4A^r?U z^N1LRcr=5)IU0x&e85B_Bt2jbEZqConzzz-0AsAU1497zx{~?k90!g8Ag>I&=6+S} zfg;lWz#;=G%@_H^^3Z_sv@cshqh9f4NMZ^TJno_+&SgV#rf?ql+qvQK{w9?I0A4~dBIw7BVe|2B0d!GuTQTw8Z;`<;+ zML7qP$OrYGqYarP2@pcZ&Lss^x<>ul|8xl@9%U7CZ+^vi9G)yVR9(p-jA;cQy^I7$ z~}86 z<}O7|#h&LBLJ&y|je|9sD;B{9tw}!YV6n*oO4Gqc!j~b!-5jBO{%`~RY}`0whr4o_ zv{e8TGLCM`I+_L;a{+Z*DZDSf6Cv(8nFPIMt#j8GrSuUch?6EVc5@kNJvc}?+^&36 zGI@ZP7;`C;38(&F$*3ezia@%-I(nUIa)25YzN)2?LiepnH{o-F$+nFq{OI{H?_V)k zev_pJG{WGy`)0JVw-Z%#41vm0n!r204pGs6+n8w;YiOMEq;@OiLK=)rz4C^4I1Bfc z81sv;S6W6Cd)yz;=Y!&tmjh39$_(=mdQ7x?8K#QGlQX@b^Yw0=>?6tsM2zH@X%J^) zf#s)4V>o3OSeLEk$vla#iHp3pg(+yU1wu!}p{JoNIkTt%r<-Ks{)Wo5LkGvK>}caM zWXKVR`g>u+NibNJpN0_8u`EEW4TQixAAp=w2zVaTEs7-CT3z8O4c0u8!v>V`yc?lO zsC}-mB~`-fLAB0s*w`-w4)zT8J~5nQLvaVb7`fC)5<`#2VDH&_^p6^ zG#KX+*0^V6fXLl9KQ_#x7vzDr?`$>~O~$ku{q&yh4|%&NV`z7U>u~Xuj~h7pmbO9> z%6^c)>g6&mhcvlKG8uZ!MKNT(=(pf5{5P%dyK7mJWATi_u$TcHdV5!)X0+?63Bx_RdPo+^4a;8_23J*sf+9xg|9HXGgVx$zqJ*RWL1D!l8( z50nY+{EkxNN4DNGxmMNbyPe=Z8lOtt&}v)@env$2+Bu&Dg@+mO zC11xDFqet(!I)i6TGq_=E%fY#vk7(vRO}XdqTuN_Jo2ivg`Df zpg&g*3fl_&`{z--N|UxQZD3!I97&=T9-fdx)?X$no=J-nKz#d=zUi4`@?@tyancy% zlVS~AxKnu9R6a$SBO-8nHS){e9{6!A+Eg8QM|SM>+&65~oT~R1A;7AcyS<l&~e$OP{dDTIcp9~=hdLJ6