From 11c4b5db3ededb23df37964a41cd28637f57c70e Mon Sep 17 00:00:00 2001 From: Jorge Antonio Diaz-Benito Soriano Date: Tue, 23 Jan 2024 21:45:02 +0100 Subject: [PATCH] Implement UDP exchange in Kotlin/Native --- .../samples_multiplatform_kotlin_jvm_jvm.xml | 4 +- library/build.gradle.kts | 2 +- .../networktime/internal/HostNameResolver.kt | 25 +- .../internal/NTPUDPSocketOperations.kt | 217 +++++++----------- .../com/tidal/networktime/SNTPClient.kt | 10 +- .../networktime/internal/HostNameResolver.kt | 3 +- .../networktime/internal/MutableState.kt | 2 + ...Exchanger.kt => NTPExchangeCoordinator.kt} | 10 +- .../internal/NTPUDPSocketOperations.kt | 12 +- .../networktime/internal/SNTPClientImpl.kt | 10 +- .../networktime/internal/SyncPeriodic.kt | 9 +- .../networktime/internal/SyncSingular.kt | 12 +- .../SynchronizationResultProcessor.kt | 4 +- .../networktime/internal/HostNameResolver.kt | 4 +- .../internal/NTPUDPSocketOperations.kt | 32 +-- .../iOS.xcodeproj/project.pbxproj | 6 +- .../UserInterfaceState.xcuserstate | Bin 30768 -> 86014 bytes 17 files changed, 149 insertions(+), 213 deletions(-) rename library/src/commonMain/kotlin/com/tidal/networktime/internal/{NTPExchanger.kt => NTPExchangeCoordinator.kt} (77%) diff --git a/.idea/artifacts/samples_multiplatform_kotlin_jvm_jvm.xml b/.idea/artifacts/samples_multiplatform_kotlin_jvm_jvm.xml index 278a7f27..4f270c13 100644 --- a/.idea/artifacts/samples_multiplatform_kotlin_jvm_jvm.xml +++ b/.idea/artifacts/samples_multiplatform_kotlin_jvm_jvm.xml @@ -1,6 +1,8 @@ $PROJECT_DIR$/samples/multiplatform-kotlin/jvm/build/libs - + + + \ No newline at end of file diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 2834882c..42032d7f 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -11,10 +11,10 @@ kotlin { macosX64(), macosArm64(), iosX64(), + iosArm64(), tvosSimulatorArm64(), tvosX64(), tvosArm64(), - iosArm64(), ).forEach { it.binaries.framework { baseName = project.name diff --git a/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt b/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt index 6951eb24..79629afd 100644 --- a/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt +++ b/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt @@ -1,11 +1,11 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily import kotlinx.cinterop.BooleanVar import kotlinx.cinterop.ByteVar import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.alloc import kotlinx.cinterop.allocArray +import kotlinx.cinterop.convert import kotlinx.cinterop.memScoped import kotlinx.cinterop.pointed import kotlinx.cinterop.ptr @@ -51,8 +51,8 @@ internal actual class HostNameResolver { timeout: Duration, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> { - var ret: Iterable>? = emptySet() + ): Iterable { + var ret: Iterable? = emptySet() try { ret = withTimeoutOrNull(timeout) { invokeInternal(hostName, includeINET, includeINET6) } } finally { @@ -70,7 +70,7 @@ internal actual class HostNameResolver { hostName: String, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> { + ): Iterable { hostReference = CFBridgingRetain(hostName as NSString) cfHost = CFHostCreateWithName(kCFAllocatorDefault, hostReference as CFStringRef) CFHostStartInfoResolution(cfHost, kCFHostAddresses, null) @@ -82,11 +82,11 @@ internal actual class HostNameResolver { addresses.takeIf { hasResolved.value } addresses ?: return emptySet() val count = CFArrayGetCount(addresses) - val ret = mutableSetOf>() + val ret = mutableSetOf() (0 until count).forEach { val socketAddressData = CFArrayGetValueAtIndex(addresses, it) as CFDataRef val sockAddr = CFDataGetBytePtr(socketAddressData)!!.reinterpret().pointed - val addrPrettyToProtocolFamily = when (sockAddr.sa_family.toInt()) { + val addrPretty = when (sockAddr.sa_family.toInt()) { AF_INET -> { if (includeINET) { val buffer = allocArray(INET_ADDRSTRLEN) @@ -94,9 +94,9 @@ internal actual class HostNameResolver { AF_INET, sockAddr.reinterpret().sin_addr.readValue(), buffer, - INET_ADDRSTRLEN.toUInt(), + INET_ADDRSTRLEN.convert(), ) - buffer.toKString() to ProtocolFamily.INET + buffer.toKString() } else { null } @@ -109,9 +109,9 @@ internal actual class HostNameResolver { AF_INET6, sockAddr.reinterpret().sin6_addr.readValue(), buffer, - INET6_ADDRSTRLEN.toUInt(), + INET6_ADDRSTRLEN.convert(), ) - buffer.toKString() to ProtocolFamily.INET6 + buffer.toKString() } else { null } @@ -121,8 +121,8 @@ internal actual class HostNameResolver { null } } - if (addrPrettyToProtocolFamily != null) { - ret.add(addrPrettyToProtocolFamily) + if (addrPretty != null) { + ret.add(addrPretty) } } ret @@ -130,6 +130,7 @@ internal actual class HostNameResolver { } private fun clear() { + cfHost?.let { CFRelease(it) } cfHost = null hostReference?.let { CFRelease(it) } hostReference = null diff --git a/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt b/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt index ce2f74b8..7e204723 100644 --- a/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt +++ b/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt @@ -1,158 +1,105 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily +import kotlinx.cinterop.ByteVar import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.StableRef -import kotlinx.cinterop.alloc -import kotlinx.cinterop.asStableRef +import kotlinx.cinterop.allocArray +import kotlinx.cinterop.convert import kotlinx.cinterop.memScoped -import kotlinx.cinterop.pointed -import kotlinx.cinterop.ptr import kotlinx.cinterop.refTo import kotlinx.cinterop.reinterpret -import kotlinx.cinterop.sizeOf -import kotlinx.cinterop.staticCFunction -import kotlinx.cinterop.toCPointer -import kotlinx.cinterop.toLong -import kotlinx.cinterop.value -import kotlinx.coroutines.delay +import kotlinx.cinterop.set +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.withTimeout -import platform.CoreFoundation.CFDataCreate -import platform.CoreFoundation.CFDataGetBytes -import platform.CoreFoundation.CFDataGetLength -import platform.CoreFoundation.CFDataRefVar -import platform.CoreFoundation.CFRangeMake -import platform.CoreFoundation.CFSocketCallBack -import platform.CoreFoundation.CFSocketConnectToAddress -import platform.CoreFoundation.CFSocketContext -import platform.CoreFoundation.CFSocketCreate -import platform.CoreFoundation.CFSocketInvalidate -import platform.CoreFoundation.CFSocketRef -import platform.CoreFoundation.CFSocketSendData -import platform.CoreFoundation.kCFAllocatorDefault -import platform.CoreFoundation.kCFSocketDataCallBack -import platform.darwin.inet_aton -import platform.darwin.inet_pton -import platform.posix.AF_INET -import platform.posix.AF_INET6 -import platform.posix.IPPROTO_UDP -import platform.posix.PF_INET -import platform.posix.PF_INET6 -import platform.posix.SIGPIPE -import platform.posix.SIG_IGN -import platform.posix.SOCK_DGRAM -import platform.posix.signal -import platform.posix.sockaddr_in -import platform.posix.sockaddr_in6 +import platform.Network.NW_CONNECTION_FINAL_MESSAGE_CONTEXT +import platform.Network.NW_PARAMETERS_DEFAULT_CONFIGURATION +import platform.Network.NW_PARAMETERS_DISABLE_PROTOCOL +import platform.Network.nw_connection_create +import platform.Network.nw_connection_force_cancel +import platform.Network.nw_connection_receive +import platform.Network.nw_connection_send +import platform.Network.nw_connection_set_queue +import platform.Network.nw_connection_set_state_changed_handler +import platform.Network.nw_connection_start +import platform.Network.nw_connection_state_cancelled +import platform.Network.nw_connection_state_failed +import platform.Network.nw_connection_state_ready +import platform.Network.nw_connection_state_t +import platform.Network.nw_connection_t +import platform.Network.nw_endpoint_create_host +import platform.Network.nw_error_t +import platform.Network.nw_parameters_create_secure_udp +import platform.darwin._dispatch_data_destructor_free +import platform.darwin.dispatch_data_apply +import platform.darwin.dispatch_data_create +import platform.darwin.dispatch_data_t +import platform.darwin.dispatch_get_current_queue +import platform.posix.memcpy +import kotlin.test.assertEquals +import kotlin.test.assertNull import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import kotlin.time.DurationUnit -@OptIn(ExperimentalForeignApi::class) @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") internal actual class NTPUDPSocketOperations { - private var cfSocket: CFSocketRef? = null - private var userDataRef: StableRef? = null + private var connection: nw_connection_t = null - actual suspend fun prepareSocket( - address: String, - protocolFamily: ProtocolFamily, - portNumber: Int, - connectTimeout: Duration, - ) { - userDataRef = StableRef.create(UserData()) - val callback: CFSocketCallBack = staticCFunction { _, callbackType, _, data, info -> - val userData = info!!.asStableRef().get() - if (callbackType != kCFSocketDataCallBack) { - return@staticCFunction - } - val reinterpretedData = data!!.reinterpret().pointed.value - val length = CFDataGetLength(reinterpretedData) - val range = CFRangeMake(0, length) - val bridgeBuffer = UByteArray(length.toInt()) - CFDataGetBytes(reinterpretedData, range, bridgeBuffer.refTo(0)) - userData.apply { - buffer = bridgeBuffer.toByteArray() - exchangeCompleted = true + actual suspend fun prepare(address: String, portNumber: Int, connectTimeout: Duration) { + val parameters = nw_parameters_create_secure_udp( + NW_PARAMETERS_DISABLE_PROTOCOL, + NW_PARAMETERS_DEFAULT_CONFIGURATION, + ) + val endpoint = nw_endpoint_create_host(address, portNumber.toString()) + connection = nw_connection_create(endpoint, parameters) + nw_connection_set_queue(connection, dispatch_get_current_queue()) + val connectionStateDeferred = CompletableDeferred() + nw_connection_set_state_changed_handler(connection) { state: nw_connection_state_t, _ -> + when (state) { + nw_connection_state_ready, nw_connection_state_failed, nw_connection_state_cancelled -> + connectionStateDeferred.complete(state) } } - signal(SIGPIPE, SIG_IGN) - cfSocket = memScoped { - val socketContext = alloc { - version = 0 - info = userDataRef!!.asCPointer() - retain = null - release = null - copyDescription = null - } - val socket = CFSocketCreate( - kCFAllocatorDefault, - when (protocolFamily) { - ProtocolFamily.INET -> PF_INET - ProtocolFamily.INET6 -> PF_INET6 - }, - SOCK_DGRAM, - IPPROTO_UDP, - kCFSocketDataCallBack, - callback, - socketContext.ptr, - ) - val addrCFDataRef = when (protocolFamily) { - ProtocolFamily.INET -> alloc { - sin_family = AF_INET.toUByte() - sin_port = portNumber.toUShort() - inet_aton(address, sin_addr.ptr) - }.run { - CFDataCreate(kCFAllocatorDefault, ptr.toLong().toCPointer(), sizeOf()) - } - - ProtocolFamily.INET6 -> { - alloc { - sin6_family = AF_INET6.toUByte() - sin6_port = portNumber.toUShort() - inet_pton(AF_INET6, address, sin6_addr.ptr) - }.run { - CFDataCreate(kCFAllocatorDefault, ptr.toLong().toCPointer(), sizeOf()) - } - } - } - CFSocketConnectToAddress( - socket, - addrCFDataRef, - connectTimeout.toDouble(DurationUnit.MILLISECONDS), - ) - socket + nw_connection_start(connection) + withTimeout(connectTimeout) { + assertEquals(nw_connection_state_ready, connectionStateDeferred.await()) } } - actual suspend fun exchangeInPlace(buffer: ByteArray, readTimeout: Duration) { - val bufferCFDataRef = CFDataCreate( - kCFAllocatorDefault, - buffer.asUByteArray().refTo(0), - buffer.size.toLong(), - ) - CFSocketSendData( - cfSocket, - null, - bufferCFDataRef, - Duration.INFINITE.toDouble(DurationUnit.MILLISECONDS), - ) - withTimeout(readTimeout) { - while (!userDataRef!!.get().exchangeCompleted) { - delay(1.seconds) + @OptIn(ExperimentalForeignApi::class) + actual suspend fun exchange(buffer: ByteArray, readTimeout: Duration) { + val toSendData = memScoped { + val cArray = allocArray(buffer.size) + buffer.forEachIndexed { i, it -> + cArray[i] = it } + cArray + } + nw_connection_send( + connection, + dispatch_data_create(toSendData, buffer.size.convert(), null, _dispatch_data_destructor_free), + NW_CONNECTION_FINAL_MESSAGE_CONTEXT, + true, + ) { + assertNull(it) + } + val connectionReceptionDeferred = CompletableDeferred() + nw_connection_receive( + connection, + 1.convert(), + buffer.size.convert(), + ) { content: dispatch_data_t, _, _, error: nw_error_t -> + assertNull(error) + connectionReceptionDeferred.complete(content) + } + val receivedData = withTimeout(readTimeout) { + connectionReceptionDeferred.await() + } + dispatch_data_apply(receivedData) { _, _, regionPointer, _ -> + memcpy(buffer.refTo(0), regionPointer!!.reinterpret(), buffer.size.convert()) + false } } - actual fun closeSocket() { - CFSocketInvalidate(cfSocket) - cfSocket = null - userDataRef?.dispose() - userDataRef = null - } - - private class UserData { - var exchangeCompleted = false - var buffer: ByteArray = byteArrayOf() + actual fun tearDown() { + nw_connection_force_cancel(connection) + connection = null } } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt b/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt index 6107da81..ca0b6404 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt @@ -1,9 +1,6 @@ package com.tidal.networktime import com.tidal.networktime.internal.SNTPClientImpl -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import okio.Path.Companion.toPath import kotlin.time.Duration @@ -14,7 +11,6 @@ import kotlin.time.Duration.Companion.seconds * [ntpServers] to obtain information about their provided time. * * @param ntpServers Representation of supported unicast NTP sources. - * @param coroutineScope The scope where synchronization will run on. * @param synchronizationInterval The amount of time to wait between a sync finishing and the next * one being started. * @param backupFilePath A path to a file that will be used to save the selected received NTP @@ -23,17 +19,13 @@ import kotlin.time.Duration.Companion.seconds * packet has been received and processed. If not `null` but writing or reading fail when attempted, * program execution will continue as if it had been `null` until the next attempt. */ -class SNTPClient -@OptIn(DelicateCoroutinesApi::class) -constructor( +class SNTPClient( vararg val ntpServers: NTPServer, - val coroutineScope: CoroutineScope = GlobalScope, val synchronizationInterval: Duration = 64.seconds, val backupFilePath: String? = null, ) { private val delegate = SNTPClientImpl( ntpServers, - coroutineScope, backupFilePath?.toPath(), synchronizationInterval, ) diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt index acec66cf..f798f5b2 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt @@ -1,6 +1,5 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily import kotlin.time.Duration @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") @@ -10,5 +9,5 @@ internal expect class HostNameResolver() { timeout: Duration, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> + ): Iterable } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/MutableState.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/MutableState.kt index 61368da4..5dc27eda 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/MutableState.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/MutableState.kt @@ -1,8 +1,10 @@ package com.tidal.networktime.internal import kotlinx.coroutines.Job +import kotlin.concurrent.Volatile internal class MutableState( var job: Job? = null, + @Volatile var synchronizationResult: SynchronizationResult? = null, ) diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchanger.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchangeCoordinator.kt similarity index 77% rename from library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchanger.kt rename to library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchangeCoordinator.kt index f37940be..89d29d0c 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchanger.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchangeCoordinator.kt @@ -1,34 +1,32 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily import kotlin.time.Duration -internal class NTPExchanger( +internal class NTPExchangeCoordinator( private val referenceClock: KotlinXDateTimeSystemClock, private val ntpPacketSerializer: NTPPacketSerializer, private val ntpPacketDeserializer: NTPPacketDeserializer, ) { suspend operator fun invoke( address: String, - protocolFamily: ProtocolFamily, connectTimeout: Duration, queryReadTimeout: Duration, ntpVersion: UByte, ): NTPExchangeResult? { val ntpUdpSocketOperations = NTPUDPSocketOperations() return try { - ntpUdpSocketOperations.prepareSocket(address, protocolFamily, NTP_PORT_NUMBER, connectTimeout) + ntpUdpSocketOperations.prepare(address, NTP_PORT_NUMBER, connectTimeout) val ntpPacket = NTPPacket(versionNumber = ntpVersion.toInt(), mode = NTP_MODE_CLIENT) val requestTime = referenceClock.referenceEpochTime ntpPacket.transmitEpochTimestamp = EpochTimestamp(requestTime).asNTPTimestamp val buffer = ntpPacketSerializer(ntpPacket) - ntpUdpSocketOperations.exchangeInPlace(buffer, queryReadTimeout) + ntpUdpSocketOperations.exchange(buffer, queryReadTimeout) val returnTime = referenceClock.referenceEpochTime ntpPacketDeserializer(buffer)?.let { NTPExchangeResult(returnTime, it) } } catch (_: Throwable) { null } finally { - ntpUdpSocketOperations.closeSocket() + ntpUdpSocketOperations.tearDown() } } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt index d31e8945..49d1df4c 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt @@ -1,18 +1,12 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily import kotlin.time.Duration @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") internal expect class NTPUDPSocketOperations() { - suspend fun prepareSocket( - address: String, - protocolFamily: ProtocolFamily, - portNumber: Int, - connectTimeout: Duration, - ) + suspend fun prepare(address: String, portNumber: Int, connectTimeout: Duration) - suspend fun exchangeInPlace(buffer: ByteArray, readTimeout: Duration) + suspend fun exchange(buffer: ByteArray, readTimeout: Duration) - fun closeSocket() + fun tearDown() } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt index 9d31a207..2a5086ea 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt @@ -1,15 +1,17 @@ package com.tidal.networktime.internal import com.tidal.networktime.NTPServer -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.IO import okio.Path import kotlin.time.Duration -internal class SNTPClientImpl( +internal class SNTPClientImpl +@OptIn(DelicateCoroutinesApi::class) +constructor( ntpServers: Array, - coroutineScope: CoroutineScope, backupFilePath: Path?, syncInterval: Duration, private val referenceClock: KotlinXDateTimeSystemClock = KotlinXDateTimeSystemClock(), @@ -23,7 +25,7 @@ internal class SNTPClientImpl( OperationCoordinator( mutableState, synchronizationResultProcessor, - coroutineScope, + GlobalScope, Dispatchers.IO, syncInterval, ntpServers.asIterable(), diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncPeriodic.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncPeriodic.kt index 21ee6806..84326273 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncPeriodic.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncPeriodic.kt @@ -9,7 +9,7 @@ internal class SyncPeriodic( private val syncInterval: Duration, private val referenceClock: KotlinXDateTimeSystemClock, private val synchronizationResultProcessor: SynchronizationResultProcessor, - private val ntpExchanger: NTPExchanger = NTPExchanger( + private val ntpExchangeCoordinator: NTPExchangeCoordinator = NTPExchangeCoordinator( referenceClock, NTPPacketSerializer(), NTPPacketDeserializer(), @@ -17,7 +17,12 @@ internal class SyncPeriodic( ) { suspend operator fun invoke() { while (true) { - SyncSingular(ntpServers, ntpExchanger, referenceClock, synchronizationResultProcessor)() + SyncSingular( + ntpServers, + ntpExchangeCoordinator, + referenceClock, + synchronizationResultProcessor, + )() delay(syncInterval) } } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt index 302672a8..7dbe2e9b 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt @@ -3,22 +3,21 @@ package com.tidal.networktime.internal import com.tidal.networktime.NTPServer import com.tidal.networktime.NTPVersion import com.tidal.networktime.ProtocolFamily -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.async +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.withContext internal class SyncSingular( private val ntpServers: Iterable, - private val ntpExchanger: NTPExchanger, + private val ntpExchangeCoordinator: NTPExchangeCoordinator, private val referenceClock: KotlinXDateTimeSystemClock, private val synchronizationResultProcessor: SynchronizationResultProcessor, private val hostNameResolver: HostNameResolver = HostNameResolver(), ) { suspend operator fun invoke() { val selectedResult = ntpServers.map { - withContext(Dispatchers.IO) { + withContext(currentCoroutineContext()) { async { pickNTPPacketWithShortestRoundTrip(it) } } }.flatMap { @@ -45,11 +44,10 @@ internal class SyncSingular( dnsResolutionTimeout, ProtocolFamily.INET in protocolFamilies, ProtocolFamily.INET6 in protocolFamilies, - ).map { (resolvedName, resolvedProtocolFamily) -> + ).map { resolvedName -> (1..queriesPerResolvedAddress).mapNotNull { - val ret = ntpExchanger( + val ret = ntpExchangeCoordinator( resolvedName, - resolvedProtocolFamily, queryConnectTimeout, queryReadTimeout, when (ntpVersion) { diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SynchronizationResultProcessor.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SynchronizationResultProcessor.kt index cabe46c1..aecff91d 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SynchronizationResultProcessor.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SynchronizationResultProcessor.kt @@ -19,7 +19,9 @@ internal class SynchronizationResultProcessor( if (value == null && backupFilePath != null) { try { fileSystem.read(backupFilePath) { - mutableState.synchronizationResult = Json.decodeFromBufferedSource(this) + val readValue = Json.decodeFromBufferedSource(this) + mutableState.synchronizationResult = readValue + return readValue } } catch (_: Throwable) { } diff --git a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt index 8c7c8449..4c52465b 100644 --- a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt +++ b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt @@ -14,7 +14,7 @@ internal actual class HostNameResolver { timeout: Duration, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> = withTimeoutOrNull(timeout) { + ): Iterable = withTimeoutOrNull(timeout) { InetAddress.getAllByName(hostName) }?.mapNotNull { val protocolFamily = when (it) { @@ -24,7 +24,7 @@ internal actual class HostNameResolver { } when { protocolFamily == ProtocolFamily.INET && includeINET || - protocolFamily == ProtocolFamily.INET6 && includeINET6 -> it.hostAddress to protocolFamily + protocolFamily == ProtocolFamily.INET6 && includeINET6 -> it.hostAddress else -> null } diff --git a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt index 9dfcdcfe..8874155f 100644 --- a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt +++ b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt @@ -1,8 +1,5 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import java.net.DatagramPacket import java.net.DatagramSocket @@ -10,30 +7,25 @@ import java.net.InetAddress import kotlin.time.Duration import kotlin.time.DurationUnit -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "BlockingMethodInNonBlockingContext") internal actual class NTPUDPSocketOperations { - private lateinit var datagramSocket: DatagramSocket + private var datagramSocket: DatagramSocket? = null - actual suspend fun prepareSocket( - address: String, - protocolFamily: ProtocolFamily, - portNumber: Int, - connectTimeout: Duration, - ) = withTimeout(connectTimeout) { + actual suspend fun prepare(address: String, portNumber: Int, connectTimeout: Duration) { datagramSocket = DatagramSocket() - datagramSocket.connect(InetAddress.getByName(address), portNumber) + withTimeout(connectTimeout) { + datagramSocket!!.connect(InetAddress.getByName(address), portNumber) + } } - actual suspend fun exchangeInPlace(buffer: ByteArray, readTimeout: Duration) { + actual suspend fun exchange(buffer: ByteArray, readTimeout: Duration) { val exchangePacket = DatagramPacket(buffer, buffer.size) - withContext(Dispatchers.IO) { - datagramSocket.send(exchangePacket) - datagramSocket.soTimeout = readTimeout.toInt(DurationUnit.MILLISECONDS) - datagramSocket.receive(exchangePacket) - } + datagramSocket!!.send(exchangePacket) + datagramSocket!!.soTimeout = readTimeout.toInt(DurationUnit.MILLISECONDS) + datagramSocket!!.receive(exchangePacket) } - actual fun closeSocket() { - datagramSocket.close() + actual fun tearDown() { + datagramSocket?.close() } } diff --git a/samples/multiplatform-kotlin/iOS.xcodeproj/project.pbxproj b/samples/multiplatform-kotlin/iOS.xcodeproj/project.pbxproj index 5717cb35..298db90d 100644 --- a/samples/multiplatform-kotlin/iOS.xcodeproj/project.pbxproj +++ b/samples/multiplatform-kotlin/iOS.xcodeproj/project.pbxproj @@ -291,9 +291,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"iOS/Preview Content\""; + DEVELOPMENT_TEAM = 2J8542RTUT; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; @@ -326,9 +327,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"iOS/Preview Content\""; + DEVELOPMENT_TEAM = 2J8542RTUT; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; diff --git a/samples/multiplatform-kotlin/iOS.xcodeproj/project.xcworkspace/xcuserdata/jantonio.xcuserdatad/UserInterfaceState.xcuserstate b/samples/multiplatform-kotlin/iOS.xcodeproj/project.xcworkspace/xcuserdata/jantonio.xcuserdatad/UserInterfaceState.xcuserstate index 1d9413c7cf0df7fdba14e2e3e274bc5c3caf8827..534722444c7bea2f8feca0b137fe087adecb895e 100644 GIT binary patch literal 86014 zcmeFa2Y6FQ^DlgM_Z;;KX+UV9CPCm{2qdV2K(>r!8w>$O*a94kL1$poN?)^Xa&Evem?EH3ic6MfVc6QIQ-tMk=qO9yC0SQ72S*OLyw?G(Ua&!^b-0CeT}|B-=go(_vi=oBl-#bjDA7CV}vo* zVLi6vA$T|*fh%zpuEsUE7T4i=JRR?WXW*IGiRWNHo{O9CeB6!~;l+4wyaY$_a(oaz z7|WRAL-3*aFnl;Z0w0Nw!pGrL@oD%Rd@kOCFTxk&OYo)mT6`V84c~?D#`og~@Kg9{ z{0x2;zlh(&Z{fG`r}#7cIsO8FiNC_%;h*q-@bAP%>|_WTO12~0lO4#8WG6C=j3i^p zcruwxA!VeB)RB7PCLZD?J~EdCNfVh*7L&cn5)vW%kvTI#@bHI$SzRI#xPCI$1hRI#W7FI$zo-ZILdKE|;#7u9a?(ZkBG7?v(D4 z?w1~t9+jSuo|c}IUX)&uUYFjI-jzO(K9)X{zLdU^zL$QIewBWg{?Z5LlZ)M)B8GcwQ8QoDqFJC>q*8mc)=bEM`N&GDL(G^c9L(44I~PjjJWv*u#WWtuBB z*J!TS+@!fxbBE?`&3&2&HIHZ>*F2?pR`Y`9WzB1vH#P5Q-q(Dj`Bd|T=4;J&njba4 zX#S)5Q}d4&YbC8tYt&k_cI|fB9ks)?Bei3+XzkI){iJx+U~_7v^u z+OxFhYA?`k(q5#!RC|T?YVCE}8@0D+Z`a{6!uA8ZwrE}|iI=?QU3+kG6t-6J}#k!@sh^|w&LbtDOwXRne*R9d5*KN=p ztUE+^xb7(3vAPp)i>ORnYtouy&rS2Qu_qv~Szv_P1{iPT5M6c27^(MVlKSaO1 zekc71{V4rd{doN({cd`PzD!@KuhG}*XXy9RyYycD9Q{0fqkg`=MZZA5NWVnCkG?~{ zT;HYd*2naH`hNX7{Q>%e^i+SC{z&~X`s4K{=}*<4p+8%Hp8i7pX8pzb%k)?3uhCzx zze#_q{to@!`up?`>L1ZRu767Zto{Z4%lg;!Z|dLCzpwvD|Ec~9{nz^M^grr<(f>#P zr~V%UHb@4Y!Dz4;?1t?OI~s-?MjFN##u+9Wb~Q{fOfys%stt99Jq&vqoCc3!wqdTJ z!O&y~8QKi(hP@48!!kqEu+p%~&|}!okT9$@>~A>GAR7)f9AP-xaGc>p!zqT-4QCn7 zHC$lWWVpz1so@I4)rRW~HyUm++-|tbaIfJ3!^4Kh3{M)KF+6X0$?&S-4a3`p_Y5Bz zJ~4c5_{#9D;RnOdhTjZ-82&aQqiEC`4MwxkW*lnV!8pvgvvIU>7vn_ZB;yL>Dr2{? z*SMdt-?+xOzwrR$!A9A5nDKDq(Z*wpCm2sOo@zYJc$V>O8lN-1WPI89y73L;JH~g79~wV0erEjK z__gsH;}6CkjlUXyGyZA(%S23~NoTT|b}$V$jWX?Gnq+dArkW~Db*6gLUZz1xyU zrdv(7neH(?V0z5-xanEbi>B92ZeCRCATN+PsH(FSFO|GY8B~<~H*J^AdB!yu!TFyvp2TPMFu34>BKYKHPk? z`9$+c<}=Lam^Yd?nJ+b8X};ckgZVb|UFHYO51Jo0KW%=|{F3=q^Bd;(%paM*Fn?+O z-u$!q5A&bqe=OLdvzRPHEJH2BEF&%BEaNScEDlS#rPfksnPu@`z;Sx9<)4edD`-voRMHb%k}cbw6vuO09=mkFcI&J=c1k^?d6E)(fp0t(&Zyty`?O zTko*mX}!yOxAh+Dz1I7z_gf#ZK4*R2`hxXE>r2*`t?yeuuzqO$$ojGM6YE#judUx( zf3W^))7o@4z0F`V+DxXQHnVMnZD-p^+bG*;+Zfvf+eF)Lwi;WlZBLuW=C%23b8U^b zpewgYVk*$%cHZaczur0po%3APh$ zr`k@lont%KcA;&fZIkU%+hw*ZZCBZDu-$08({`8bZriK2*KDub-mtxCd&~B=?H${@ zw)brB+di;;WBbuD4t4JK2ZXhucTkceanTPqI(8?`ofE z-_yRAeU{y6ciG)`kKJqc*=O75*%#aQwlA?SwTJEd*dz8;_HO%Xdyl=(eu({O`!V(t z?WfvLv!7)@+kTGy0{dqBCH710SK6<#Uv0nMezW~{`vdmJ?a$a>u)kt|)&92q9s39N z&+MPuzp#H}|JMGC{SW&;L&y+uh+&9vh;7L5AtQ$DJY?dKNkb+N*>%Y7t>b(8ySrZ$ z%z{O*3N~S=u)~7J(9~7Y_3=gU|H*i=ZG1;}Bp%-^*o7f7mdR#eJ7IfSq@qKf8MbZb z_jnh?`c}nzBOOsstfPN*v?md^jW4gRsj6{%YaP`U)fJ8^S4EA(S?BaQ>gwt}b(NLg zn#xM2+VgN>qEKV=O(+w}Wt(iLPf_%9f__r5Ds^SZNoj$<&pmGgtxOR5$oeYa@-f|Yi$n1!!~66>3zy|F$Jn^l$Ig07y<*jng(xbk1lzP?D$3h1pT;fXHmU*Ydr9t+z> zH^<`9L?V*ib6Z!uYgtz}zye?>+6|k6XlDv=CuI!ENY(DxK=tZ$%o4mp#YKWsa0za~ zBM*^>%G=4?UnKa1*}@#bFYh3~Nb%p4Ji-L(SlAKkj7|kDnAeqb@)_eijR@Q3{2QKt zH)n6J!cHc7f}@WN+m>z%`f6B%UA-W><*~lij#aTlcUMng5oGqcp7%LAY-|6o_E~I* z2l4L4hHZ3fy6f&*))(npKP}$ZF)h*y^V<-Cu_cGt5nDYi(bXC0RuzHk0T`)hUl07U zv8Aci(-ewztcoUrz0p2yVB%9(B{HROC}(S2*ml7GF1F^x;)zC3z2;~<)(v?G&XD3v zju1{^CjCg^DB)<~7~xpqIN^BV1mQ&CB;jOvf;>^4Bu|!im3NbOm#4@Md8$0^O5s#6 z>Zij$X9{NtXM;gMT{useB$t6vuZDkW)Y$UnV2!|Hf&Ekl(bN}R1M9p2SaJ5R11k+k7g4nNgAsnzaC&zeY_DMZ*dBAd3UtEUGd3q@tZwqd!X zLD1#Cu1=<_qjUc*BUEA7M#Bh{$aEIw;bGr z1i49`nFY}v=}*K~gJtXBlY=Cr3EPG?h8p@45m;{NS=xdmHcvLlA#z)T^vDF9A_FqY zEplsyKC~_emCn@!m1-y&B~)C2wnN*a9ng+wCo~KVMgs)(LDAngq5flC({oV4Ij_>iVB&nF?6S|IOwGRfD-fHS)5gxk2?{ zZqRgL8vBtuK=ACR%4Se!)`{GpXUHW-HzAL_yfmwV=Ae1ps-U@GRaSr&p$4!jE9IFv zmc(|H+D|iTRrIa}^lsnc6l(j$Xa)F0v^QFUmZC7)2Sv~_)PXut6fKv#<<)YJ9Fu$H z{p3D5E+^!EdCiq*Wm4<9g|ko(*Sq~d@75;u?f}p`$~EtV|9P7Cf0a)}GE-72uTz!u zFy<4dp(EhuD43!3@UuTCsn)iest(7a6PcQxAaB@&PLdBSQB6-nXELQbU3f@7NUqk@MrWh5U}6r=n;I41W%M5NK(C-z z(QD{+^agqpy@lRJ@1S?(ljT$7Q{~g-)8#YdGv%}7v*mNHK<_7Q!N*At^f_39bCZ_f zf}{u9{6EhQ{2$VuB@5AS}ukZo(QF zLZDLOT5P~(rlb#H6L_FaOn(|cfAT$$+RIS91J^gaJ?Pt(!Q1bQCxO1RMc?og?7&ln({UN-+qFr3yHUPP zz8&nt9sf_?c*<~U9K!hj)&>jj3HpZjlCMkZ8+L)dVK?@$ANhLu2L4lJn`zrw)rEPu zfoWVozG)L~ly5FkO z+XwXTPKbYS8_?dGN3FJBf%};Lt;Ah;U%U!;ku zNPbv;5>fc(t4zCCOI{@_W(WL%8AwMfW2m1H?|I@$!1^Rb1)4yZn$5j10p6TBS z!Zh|HKMuBz{Z!fJs55&yhM;5bHJlatz4rS^FlzEaV?D?s~RC{C@mzaHNtRBXmK;2Y(awt2TEhnADCow|;tQ+gq{ospiy_i^Y~!?w0_VSR^-vXA#uwdosI}JSR}I=mFT!v$P1L4hl8r*@Res zyoKii6bLsvCJNhjFMu9ae&_1%>h5e>2|4n(Lc&8-{2Wua=jFHMs+aIf_+|WxV8yTE z*YNB3jrLSdubrht;|WNOFIH1Kkmi=(li!u!kw5!y&Dc2S!{@BI{>s1AC>_6pKVaf~ z7r%$!m*1B^kU!jlKg1v5kL8c#kL6F~`EANb+7wBIZDWcIHAA2~7HJZKjI$zi#|cL8yRi%6y(3!9U|)!nToFlFpsEE%;abo3LuIW{_m@w6)|Chi%1}RM*oV zL=q}4$A99#@Zb0!A`nC{Aw-nFl)sX{mcNm|mA{j}mw%9dyqsu+_lS<@iGdi2iI|B+ z{t5OKKg++!zsdiRf0zG||73c(Q>DAEqOzveRpF|va@W*(%gSoKURPyVajE*cx*GqTXFhdOCG*tJ11YR-I6~`WS?oBVR7{1w#wXeE;;UPAUIs~|N6|Bx6(Hj3;J9l$>nZJ)~< z9QeZVRbo9`Z?O2_q;I{F*dC@%`xw4R$0QqevDEeWqI_pTjLT@I_R|*W?$7QhX)}2* z*^*Ki8Rv$>nMg0#gltaMkAXAe=!#e$uw+Auz}AxkHj(`)8bZ-_FTjdBkQ_t~Zimy~ zc%l#VkfNcm??a)k6+Q5$4I4b(Hg6!<)ZlGw3AcEi4a(A_noU(-+&JI)*c8($kTx&<4kfX^logBsY*_;AGx1AKprFyk%!46~qzsWzMAR-ZqL=;6y z)QDP9C+bCmXcSGNS+s~&(I(o(A>vSRJ8^q)2XRMnCvliKTpS_pERGaMiKE3a;#hGP zahy0_oFGmVCyA5AUB%tR-Nh-QL!2s36U)SMu|ljAtHf%tMywU<#CmbMxQ94HoGI=p z?j_C=ouW&0iyqM{`o!7d9MLb%73YZou|aGUgJP37Uu+gbVvE=+wuuYGg<`w7NL(!L zEiMt4ieYgdF(NJ#JH$>gDlQjSh%3b|abIzj*e$LWd&HR7EAA)uiE%L@_KR!8wc%8^ulHW^s#nk$ACqiFm1anRvN)g?OcSm3Xyy zjd-niop`-?gLtEOlX$aui+HPen|Ql;hj^!Wmw2~$k9eg7~8NlK8UtiukJdn)tf-hWMuVmiV^#j`*(lp7_4_ zf%u{Lk@&IriTJ7bnfST*h4`iTmH4&zjrgtjo%p@@gZQKPllZgvi}XueZJrwOg z(T)`DMA0ychEp_xqMa!kNzo{ZMpHC~qOlb1LeV&i##1zbqKOnuqG&QjyHd0pMY~fp zg(3$7SfqCF|vi=tT+fyZ`H znK`J5t!lwDB3{LffOA?(ZLkS6j6!}q3BSG4x{LBijJV@ zNQ#c4=xBE~n@Uims&SDvGYA=o*TyrRX|} zuBYe*if*LnCW>yR=oX4@rRX+_Zl~xDiteQ7E{g7^=pKshrRYA2?x*MhiXNosA&MTR z=n;w@rRXt=9;fIDik_tCDT~{ zir%E?EsEZz=pBmQrRY71-lymTiaw<1BZ@wz=o5-QrRXz?KBwplioT@iD~i6R=o^Z@ zrRY0~zNhF1ihiW%CyIWi=ogB9rRX<`{zK946#YTbpA`K?(ccvPL$N?HgnMw)flw?` zEK#hXSWB^vVm-wMij5STC^l1Uq1Z~Xjbb~+Lnt0f@pcq%Pw@^E??~}Z6c3|#IK?9< z-kIW&6px~KG{s{m9!v2q6py2LJjD|zo=EW|iYHUNE5*A}ygS8HD0WahmEvg>mr-0! zaRtSd6jxDPO>qsywG`J;TuJlVTUeZi+n=dnxu&Je%S< z6#FTjOYuC40~9w<+(>bd;wFmcQ`}5(h~gHCTPbd%cmc%=DQ>5D5ygus-kahj6fdPX zOz}PxM<`xKaRmEK^J=K7`^!DL#zi!zn(3;v*?OisGXwK8E6B zDL#(k<0(FY;u9%8iQK8NCSDL#+l^C`Z7;tMI> zNbx3$H&eWY;)^K0nBq$)zLet2D88KHD=5B_;;SgWn&N9HzLw(aD88QJ8z{b!;+rVG znc`b0zLnzJD88NIJ1D-B;=3rmo8o&YzL(jxA=pN&OkWi znFkww9ym(a-LcEZYjVBcHwuGCU?s?$`XQSWe4Yh+JyKQNi7VO!=Wax8^qvWgwCF7V61)5zSGUgvCYGmM~d zp1*OnDg$uc&~|)YTSfcaL60}=4gx;VswCj#fn@hw5;jBr0QBH*^mrG7$b!w@u%g8( zB}4^4IXagzsp>GxZGP{9)PNy$DAZ4~bqlOO7)LndWh)(4W=q2Ms5^gS(i9c#4gwz_oR!;M zO5KwP;W=I>tiDj#*BopBv*8bbYAZ%0Ye_)(1aNt}=xk~V_}yId!`_80-bSV?VP}&c zj7y+h9R!42Ks-5@7hh}0%e(Z1L+)mO6ZAaC8A?kG0yH42$R!K8ozMjsoQ$yp|F8~{ zV@l031a<)6$OTBwGW6c8b{v8==n5{(P+o`yfp$maiS~BK*0XB^V1i(kHTeP=b`AnX zXti@zD`%j^+uX>ebar#FwMi8Mgpva^4K4sRP@6)q2~9^7L2bxT4~Ryg<>VqQo4m~p z{t%lIkPj$iN-z++0@;`>GLN?@5NzkUl|WFLQk5TwaH09cV$GX^p%%~?a37%z0YWqk zMB{Ua(g_^Zb7ib8h@109<7xJTiJ0ea0biA3P_e-(dk|#<+3>8MRHu|R1rUW7ZJ0Fy z5T!%IQ3Vm1_`^&I)b1gYhsN7wHTHn2WCRot<3p2SSxr<9nRQX2U<&~499ivBzB%c6 zz-K`o%w9m zr4ZBo8#eH1Rt@ADinH4C-YER5C(*q=xSZYXioh+N9=Hjk zGBJ@OI!ZxY5J_~b4EA)dS7~?Uv^9g%LJ}A*kA&c=+WKX&NM9$Mf<;x3DI8>v!9fD- z0#A3Ba?G1dZm(}y-)3qEtBi&%RBYj23y^Ld(v=ZH5 z4*DX+n2K_s86`0V*NP+XW=}Dwl^n`h3KZxDyGhD!aYg%z;jQ9$C25O#{tow`2SV)G z8pO3d-LXjLAc*^M#1iHq7*cF>vB4%v1!@fW;PSfPQw(S=2P$b6e39;M4_x==SIUav z?a%S%lrpQCW(7(Y;?bUtqKn}`4qK98@xkS|IJ;2TUsUyEjy9_lCTGv{!LfKxX8`WE z7aQ}T9ImAlI0*Q<6D!?tnGZZ?xAG^rv>xm0jE7c6`ihSKh|bdoCAeqs^Rd-YxJ+LR>BQ0*WBJGKM&)XGF}PDWoU4?HQPY_N`;XH(VB^*U zI(s@7tc>;yXzsEjOC+?adia;DrK4Y z+$?&_}QNF}LZV>HsYY`ve|wg+d-_H*6cv9-YgOzm|XyrkL911~o88#!i43o*H* z+hX1D+)7akehUXI$=Nrpj3r_L<%I%gXJ;`MZ|8_5`GicwVsLkHxRRo`<|te=W@&(8 zSod-)Pbq5yuwrZ2Mqv>7Jy1G(A-L}!>jR?=`-nA>j`dA3*jTTJQ!v#WJiHB9#fJJA z$0}ie2GyNieO<+1p5!nk>2Rnw8tqugd@z)<4YYip;g}^wHlg+K7|v>EC(J8^4N8un z7^}~7*pf6^fla-@Pz>=UjySgzb!mewT`bhUjBVtWMf;o`9gtNh>YZQZz$Gma_7qGD zSe#W*9axOdH#l5LbKMs6Hx+|YA(f#_n`l6j4c!vTW?TPe8_*+6y_NeOe zdmOBUG*t>L?t^!xlzf8<@*xMQ9DD*={YsqbiuBFt>V(vritq_XC}DS>fe=cpPiM+L z=K!^XvjFP|o+Vfn?PCEWOjaPeCfcpC0p&4}xGuqL0~^XaN6}91YL>&EBgo=r2*bhW zq{RCz2P|R#Js4oPt0|Joh5W!#{eus-1XLCYsKfu650Xn}tE`apF-oJt^`lII^Bw}hphoyJUd0V!Wt4IK%8PcJ;|!c#FS z!+&$&l7gPX3@3rKhy#^i-U6wS!z)0ns8|&xWTuN)^@|HUnX$dtD&1&_Hc838$;p|^Mp3)BE zEk_jr&8}R~?#vsHMeqy{^A}?rW0cf-_l6BhwM;k=WJlpD$D?`sX@%N*+M!khj_DiV z?9T6kL;IHYoH_~ZE}VMT0jOJ>n^_%u4wn-+-K2taa8l1}1C%qmru12>b~2|bFGR&^ zGT@vWN<^Sy1xin#-e-0I4$U%a6STW?`kF%YP%r@J5wwx0elvj#aJuzjhtFu(a>2@#FsnTO6QP>6L z1uA7Nr<_oT5_;JP%N$-VHQZW>_W*g3a@?mwJQ~PbtJq z2C-6$)}Bq;xmBc@Ra4p(oV4IF1rfkeG;3X`-b%YKrz|M*khjUnOQ)127xqF~s4An? zoUEW8sWY3UN4>oLxI$tHfz~9oJEQbR8!tYj*2dH<6tV_uiubj^+1$uVzyjG?zMfMQ z6at7ZL(!YE)O!Ob%XAA3QFwWb?cB#Gxe5sSz+HQxqLCZrAImAhs0>WW>o;5d;bf_=%J~VL8XQX&wNmWq zZ2$`n<4<;;G7%?pnq5&7Y-S^HLl!Q(DeNhQTTpJJ)>1&wkm3GLOZJsP$4c_rVM#%` z7mz`~0Hr;d#b4SpInC6}zTC}TX1CJ4Fr8QkNVVx z=ktb>vl<2=-bpEIy0c7+ypfZQ$t3eG^txMHxPGQe4Aow?@a7|nG-nEuDxlV0!rShi z**47sELaxMD7Cdvj?L;nQ?qk9ryQF}nHomZyx9yTYu#Oy&sEwQ=LCG$^0wm&VyD^5 z6wwWwB5Mi?H@ulQ%$n$QL&ckdgp!qeP(GPu5O2%pD}%Td-ctov3RUgSl*zqwpzg9d z;u^zC_){XjXQ1wsc}$k7s#AYIr^ph6qJa(0Y%BN>Z5-)QQgI*U%_n9~2wOwU z!a!pl3x~pUd{9IN_C_Uof)h>3BvOL`wcg4X48ZP!7Za;gPjjkWGO5x-Q0X(L{<-{a z3N80!mufdJa;gcL-6TtVllKI|@OWxti{Ix@?|xq4G@}dA6xiLp&Rg$PsCA~bdW$z3 zpVf>Ph_Nw-A+F6=s&_d_)@(Gjx}fGbUAUYt*AF;RmhH*1rpcbXjZkR~^Qy|}V@{T3 zOcpeQd|}q|49{o0?IaX}laG#-W11p~mEcL80bbsptQ+SA##v#|mz-*s94ckH3PwNQ zaDs6;1Zs3vpgvvuJ*OC*L(%MAR1p6s-g-t(>(oUTxEs+5<^Qnz%jgRHoeM5Y@Te$# zTS5D4L8w%r`qn`G&Y`B{a20BYFunl_)z!E*gQt`o`HPe8T9~xBpmhSL+c}4h6{ssj zU{sQ7q9eR%?$q9)^hJ6UTFt0S4S+rZ5wj$!GU!He5?F4kWCG6C zM%dK4p&-7c+3AOH1a=T0ewC-OoFHS8@(DuTfLbrB8_%1ACKSg9kI@9Z`Qg5m+Q}qN zl`(L2IkWsz25)Nn-FSOg(P}S>>rN{~rr*{%c#8PJf^kWT(oYo&(eZz`BHkfB0bx zTr%0PAr*$fTtY4ic7QC7Z2@!5L0ORBn%ckXW^j}#c_=OH!b)F&i?h9y^w;rXRd7G$@DQ|3=hQcS$hg3K=9gk$rVRaO@#&stcEc>BqD?UTDd zrno7s1AS6uvV@b3$|K_)@avb5j#V4)!y8X3+&BQ4+g7+M2Rg0X7*we`2I@kkQX4Pl zjq``i`GLKO-`CD|aw(R&c>D3FjeUIuzPQ1*rVteH+_kz^vbQw4?j%2%B*`Xxi?1jQwyjrfilMUm=K)wB)ayG z_Q8CxNe;ucXt0=&4uLzwE&fFiwSu2(0WZgLpTLt3Zm+7G9LAAnWFxb$HzW^5*m+!l;XY+|;yFHc_2(Wi}RH!4j0%~Dh4IFr|bQ<7+3x=!8>~fk4bqt597=$Z!E0u-w zkjsTMk~8DZneKQFFtY@Jl-=f|gDpIptIS-#?j(-2*VbTh*QH)*%??FQ%G4=MK?ix;7T>@##9EiU>A*YW!lNNt^Hm~Ox^(A1aneDttlX8?ABS&cjA6Hn;D$us7+-f} z1?=CGk#&~ezq_DB2BdEaw8Cj?YbXonK8};CXN7UpV*qy+!h?lI5&*Bz56>w=EDSo~ zhTT)xAA*a0Y-b7kA@&PZ5|42Dso8UqO`lw%&QJrpv6a$9w#ER5iKE zMl7Av(7nu2au#Fuppy~aI~s{gp&qgco6VpXJ2*ibBF;av>@F_ zgLj>jD2s(aEMXsP*|VaAPdP-+f-Wf8CT9qwm}~IA;Pg3OCYzq`eZgO~rm7osUvsMI z1IbKrE=p_;@eO~WA(=SK5dL=@D96I8-9uH1d-vVd$jiNOVeuvPBgb(TW+)pcDN#4u z)wh7Ixxx7=(O0lJWpjVwctuu>ikIQW85$%Cz2ML?Lb(5Mlp>Q;1f>~HTU6EjlhZFK zIx3~a2uyV{=H*YaG=fX!S!rhc6uI-WuhgbGc{Y_A>p#4g*`@ZPWW3pX7$JTPrMkSN znNISq#~iiDl83S9@SdENWI8<+hpK22N2@87$1F5;pn4q#sVfyk9jLRlCCJR9%8QZ1 z_=>_P-$;a7LWf(OnIu0@bm3;AEgW=KX`mU3$074HPjBaNIWa@Vx+=gU_W&F& zJJ+b+j$`dn8f%Is=8RXB+>RV6CpsROt4yQ?UT10&hI6Q#1qxT%di!D>up#6-^CWF@ zd&Gy<5SktGHmZJHKa!&~W)Cd~g$Wz1H-x9`O%L|K5If>k4(woG0(syayXy8Cla6>&xG}!gE#p6d6uny>pyxe&{p3jb*t#cwR{D zWE}5g(SNy<#@0Z-=hpFE6!HNl<+en7-0d>hHd7OMVJKun>b)jcv2FVeo%J58*Gpd zj1cVdgRQgG^ngCZQQg}hOuj=Yz-Jpr%n3nDOJ0E4 zy*YTv>pWxXQ$ppZwfRaF=E&Y{5G>T8Fu&@B%oIOpPrVa7at=EO>T6PF zZWV`|ztxZmk18-6azcq@hFDVgJsiJe^PD3C6(546mLSVKq;U7+xFy?|BDjs-1?+%O zov;K4&B-yAwAA38HKoX)72eC|r}X-@9BfONfMoWFCZln|-x%eYGJww0ZV zL&*@lh1>>JBRPAxO&qsmZBDHWc7+i{2{Sv1_{Z*FOYXxIarbaf{0<#0I($^lq{q#bM}SP@=< zgt7hsj#gYRICH5P%;G7PeO5VpxD?J(SabXyC=pBzQB~N~l6s7z6;7704RWs7nxT9G zu3*6lB;0Cr2HHdZ6x5R(s<`XQg@Vk2>S-FF=t3ReGaRP)BF~nU62bZ#K|TGRaM0(2 z3kg9Vob|#@6&3M$j#%6Z?3O)Tx94_ZnVgS{EqlvPG>9XjQ zyk6xv#e?4hSy-<9Ny}sZ&7{FI`kc_%??v zzQ-yCt1zj0&ZJ-=>VZP>mh|DF{=IGFn!~G`+59Nw!+*nd#;%2N+s=<$Vb_&b_)oTm z>(q+m_j*|ztYq8RAtY>zAZ%4-{W*s%zDFKd)~vYG=Y%JZ;eKW6#7h4a$1Ltlieaik zXbgJce6T6CNWa}i$!6pLQ>ww!mr4|+ir@zhUpy4aVmBq(WKazSvP#`lmInXKA&YP0 z3q!JGI}7q4WKv;O9)IJ2#jQ#fU`7ze$IV{64#O|g8@N9>T=AV%2HcoPPv;moC{oXl zxK-e=PyndpwD=R+1P5w3n zxWv>)E|Mom$x}&4pJtYc8w?yPOW_8x@EJiZy%z^eC97sLRgzrIw;jn;R zCW126tk2CDMsu(kC4+&ZX;v&O1i0|RxIdse8pAFes&R0r!n2a93*~>EQxj^Kz`>XP zYjEW^-{wW3+|YoWEkvx$va3Z6D!iY=w{0`~NiE`^3`^hL zWi=b=s|kj{zmrk&J6B1Kg~TZv2$bro;j$sf@msca5DQqm!meq#=dq8pz~%dVcw;qDt0kSvGVkuG=`vnM)cBK$|B6C#(a5A-OnNQA-*S~C_a zM_`6lj@`KR*su*B6nr5EpP{}k#vi^Y7#py!aO9o#^KV_Ly0Vx<%^e&HU|5+I+<1bU z5sHUlMvaf32h!OW91Kgh30T?TsJKcuIlvJPTvsB;?4BS?eDO=F?6$Nz@Jt5DScRGxC(uz{(%vSmybq4HcC z98{}=SJlag1s2etL^qKz(UFGy(j^)h9;!nzf9Q^TkKYLn7 zVN@NcQkno-!rra%Ho8-$^bn4>*H+_s{B2;x)h*3=q8V=`ZuB}FE@HPbD0UvlF z4t5Q!;z%9tQ5?`+DqxG>10@HovqATn15|4qSbkMSJeDKYm5P`aV6c&ucc?HYaF{uz z!eleX!btYS8w}%dK!TT+YGgZW}n6?az`#EF*+g&=t$rI+xLjl7>9Cn}uJ1D0t`O7QDR15X!zh|}3 zih6>>4y65|Xh(lvS7LphO2PYVtEO9-N38ixl!d-u%Fot z>@=Gez}s`F0%gN1yoaX$S`Q8L*y}{80OxfM|L^8Da{zEeo$^U95K0@ z)QWJ!yBvKW@3eL3ZD6F;swl$;9DE?})Y{V@hn+rqiy%+y*%QS4*&FsB{3Fn*w9>~M zI55C;769fR1x^Fm&2SH7AXs@foHnsn6u?WVy7(D~ZrcXvR1BPkhf3V!3q;B{?A1=b za-4&*94Q;gq|}Sy!hO?y|O+KXLNudE}|rw-p-hB>5{o{Gd1al2&H=@mCI1IT#Ro zvw&rbIbc>1rr~#vFtt>KtRh##U!1sVFk;>` zMRplQ+2%vdHze5ddC+j6`oVaBa4H#6vv|PC-a5>B_QR;>I2Cy~>fJVYz?6T;H$AUV zT>}zT_q;|^5k_(WlIKVK z4&gBQ+Lg;r@;n|mQC`7utA^9KJx7>4C?nZdQj9xsvV1{izbvQ-Q$0q=d{odlf&=W4 z*KewNCw)~-L176c{!&a%R^2#?Be_aP8sw^(aV!TM(Fz|oaCnqUfbiUKOCbGr#fySj zun1P;IOBMsV&g`6K71y7VFwuhN}k3FMH30QZ5Q`;cEKl7)y9*JvxSOlZKI648h119 zZk%Ft7^fPi8Ox02#tLJlvC3F&tTEOa>x}ir>Bc>bGmJBhdm8sL&N4cUE~DG%F?x+Y zivOVmQ9>w@DA7`)r^HB!nG!1{c1ng)vOOg`QZkH^5tNLiWHcpXDH%t}1WG1RvMVLK zQ{tdx8YShFR8mq+Ni8MyjIlXJzj3Z{o-tr-Fg6;4#wO!@W3w@2Y%#VP+l&j03ytl@ zMaIR(y^TwZOO0XUKE{Y~nX$vzX^c{`2PGa#{FF3O(o9J^C3{m6p#=V_i;~rp>_E z2_43LWkDWBi4e9`taGemB6q+6_Vj7fytN*m)8#97_-dWCj;eA`ox|y^aXVb)6+UNm zWu@0$TbBtGQ-E+DP<=(2uiRZ-=BTT#s&Z6S)mJ&{TxC8-d6~P?T~S?LTT@?|36xNP z#5|z7GPke3!d304cGi_Ss@&chhtpl*a+G^3YOAVhs;k{)zD%I?3Xmob$nB~2RMyv3 zIV!zAx1-7fL#wNF`W$ucx;j^NU1eEWbwwu7K?;yA56D?rRbAuu)H__Rx@s6&Er`ce zUE^@qmqCb9;i;*tsLcdAQ~@&N0aex4Ra7}^eU1uGMTMivR|V3lt*>$TJQa2I&N4_O zS9>ynj#7Y3c|bMJ3U5_ib*+Q(1+xa?an||j9lo-P^16!p`f^`YO(xLs3Xmla$m#Ty zm(|vkIcm#5GB9b?4wu(e@2IZ^Njrh5N?+E{PEmktc|h)RZ+Tf+J;QL9JE|({D;@BC z4L8W%lC1!^MIVLdS6{lZH1$vj;*zFrw5iujnh$C>8q%y^?7||<>i?`H!DD+@_@Wm zWpK9V1T#`y2~$^HU+-`NBaT{kxzAHpU+r>cFWNg4pfPzs)ipJB<#leK!{e){gCzn( zt8>=YI9#>$Ww8G1tIK`fOpfkVfOg3P@>V;`U9jF96=fAQj;e}^a)-03%IhdAud9cj z%35zt)>?Z=0UDnNZ>X|mCh^+`M3f!F%QUB z>vMZ5>gyaH&>YYsFae-hK1WSetq06nxx3tzWsjawfF|bw)snkvxjDmU25vNA7t05=HD3&z%&wff&yfTrdF!P2j)b5&G2 z!0&*toiKIvaN7e!R_Cs*_SMu>)@RA+0|lro56JCtm6yXJag^~{s{kOE$5rKk&7`-^ zT~_BS_hy-qPZgkwJfLcCH5leHuw-Q}SRx>JFeo)%Hgzx!V04|eSxe+A1*j?ysJ7hW zt||9aga2_lnP!8{t^yNKSLLg$tZ-NRs%o?R+V=`jO&$<9S+A?Q7FL+A23EhTyw*`) z2cEXt4f3e~hgntY%3LD9C_r_2K$RYsr>4wV?WnEv!t~eGz*_Ub)VV9_yskQzud>RQ zHEVw;K-2SpYCN#mm^br!-2hZwUJXG6c**+eI;Yd^t75KA4d?zg2|~ptBgA7fHklwK zo5`cV5PWp1BLSb$gD)+whObz$&nYB7^9BK~X1qEh%kV2|<%`F8dJ%ysOkZc5y;po8FtlD%Fu*-b+LaHwfJ)AnJT z9S)DxW`StWip0vW&6@E$4;1>@FeS4nalK&L5eRoO4O`F{nhM`8j=1m;O5ncQ3Sp&b zglXp`@UhnZ1pC|uquK($8Y!%5YYeeJDUC*(#w^YN-)tHytjhd_(rTOuY|svHP8Eoy zK3JxTV~c5mX`-;I=--EJLp^ORxk5~e-Za^?8;ITnv8;EK3Bp-lDWadoL|;b9>;j@U z)tDfDR-`)zq?@N0r3Hn2Q(&5In!&h(czEt6(@aX{mBO8qap$5WP>8!ZY3>>daaUTT zsc;uCHKw@>QWBKszrgk~re?-Lh?1s49JHr7m|uv4(n3^)gC(Xg<6tQzp-rZJC}}B0 zKAns^*h;h(;%=3xTb;c&N*3hJUP*CpYC*+Ju!UNfS}1*{VJ^HIED%-u`b}$r|K!47 zN68{pf(I}jH&C*;0FTB)(-JhW^{eb4O^2I~WWqRtk|mo=M^Uo06k#04Iy#<`aDk3Y zr=~js;pVGyX(6{VRcD&cX51+Y?Ho#$sY5-Vac}`89fdf!D9u4jKaZRrhig0FbkCIpX9(yfCIBg$pHm8FdNK9)ii-+I55vNm9zs=OcO?C3o}gz zsiw(nHw|;+ArKL$w#mGmc?ZTMU$ACK1Ia3r!x@v15u$~d9Ge!?A%(M!3hOrP|`+F;b5V8QJRCr zl$@k;u#|BSrsU*897N5_Rb@Sel2h}PwWJ-S>PO98Oj%D$DQh?Lqo9~6U5%OdOY_)A z$r&n-{fx&ol$=?J#|`OuI;+q;m9{xmq$HaUVH|L?W`>mHIVuN7Fb*I=d2S&Nj!$!N zULg)j+tn%@oNPXoaiFYMkkth$2WK)4V1O4E;^2Zb2OA4ugu&BE6NH!_$HMD=7Y`P zgKsXx!GFxZt73x5y){ovC8ZNmM&&OirrT68Sp;TO;Cw?hDi&gq*i`aZ$)cg;jwFv3 zJ@9CO$otL$JX&mNWr7GVKTaquQ^BQV+0MdUFn2!|aKZPe955Gb0T+C4Ar8h`c2T7S zA?5vfQYtCMVX|8$z+*{kAu9km0b9lvy)Dw+9|fr;w;h(r7QTa4n1mhlgDR6#8I#i} zd8iPR)oC$3Tu4k?kv_51TVR*G*)rV%Qw2-z@y(W*mOUwf#rAI2iH`+#y__RlC8eE20m5B9D>xm}T#`;z{5?!mK)8diUz1`9Hw60)? z|G;CRYr3LqCsdTVV?7DT%uGxFhF=DWPhHm$iN~Xfct+02GDqqCiLh9BQ}PleFH`agC9hKQ8YQn&@-`*!us!6C@nj~-pNOtbe^5QXI3DZo>xi=7#z99q zqlrjYHxo%;tQ+2x58HO??Tapt_Vq$AvGc#@c?Qc0GH#>%tMcFb^8XIoM#2lE?&M|E zV3H)w)3{m7?-SVnRPBvfdX-~W^%rqVKdgES>@wciWP!-xO(p@Wl1YnpatV0iN|*av z4gjC|7Ee);NtOdQlSwQOH3HI2tX3x45?dARY3%CeE2bgRw<@Y0;96u0&F=Ov%kf(^ zeJ5B>w44OfcZ%gy%W0O=DS3|)Fx(&U>HCAv7V`s-rg?we^AwFi@Rd+C5!ue;1T1#c%*yD;_|YJs&(vNOBQGRqYV9ghIaua zA2Ph{3gHbl6RNzoSRiO%=icfsF11{qo`5ST`BWXl)s_P`S-`D*1~Q*Nb&;ZOOf^)> z8!b0wL)~h*Q`ljP8mh3wiy5$MMYI)M~6w9zOYc$UJ@Jp|~W&8h{yAQA^&b8s=&%%gC zgq@{`qT)&s#Rk|r_Fl1KLCUHqEff{&j=d)uHMXc=iM{t;K`f})uqJk6>|K%g{bv`j z9Fud-`+nbbeeZdHQQ4WN-1j}R&&&>Ri3vP2Ri15{?J4suRYQ_(n&_X?)*0KgxovT5 zzfi+y`ssJJ8K#F4YBXx(%V&@ceacn!ZOCVd75PM|dL{3M<$QfBR_5auK0|3-H83n% z{bsY+u!#8ZARa&n42p@U!UuBZbIxVkt7@vQD3-4hZ8=-KR*#kcMZ4z7-mpDko@9v; z+?aZn)7fp?yP{yCEw%kN(e|EV`Oa9o2hnXE6k+g>i{V`2&-poL~8;r52; zsF=78VR34X3jNCq{1gcFH+y~SrR<*y5h#2A%Us7 zK8jROk(?B%&|+;xZ6z&RGH+*QsZLHkt$aDTFFJ1u1~-^E>h41u|^AHA){rd#w=?L zp=TWn+By|3qMnEQ8xmpz_%nMniG_?(qO&oN(Vp{gW8On9xD?lyaJ6-F_bBOEim&29w-l zTpA)eHdFhk9~B;Y*0xO9a^)+qLNoXKsomVVb^iL5&t_N9&ZrqJ8S|+#^Ql~9v2Vpn z#_x>zjn<2K48vH!Xk&CV+H?6?ehwNH^J(SD zgmYi*kPvP7^olHGtMt}w+O`WYr!$g>b}|3epysw=7^j4&NRHN|w(UuY`CB2_qeI7@ zxt~?mi-`$JSlppgdvb01JC$ahs~U9ERM?|yfa$tO4Z)|lCv|K8jjytGkDl4Jk<_C- z|Ln!0*Y2}Y3pE#EQZT!JtN z#WJx*Y!Um#L2+0li=*P0I3uo$JL0j()L3b3GzB$)c%+bu#{G!>UxuAKbd98V?d9V3kVQFDykg(UB;_o2$ak51y0Yc!us$AX$UpF^TjrfiHd0L-<}13RE+v$ zgXvO_{BuOtQ5)d4L2PtTh@nGJuzEd_eJxzor%I(NeA7m`iaagF;~+fF!vjDK%T=k` zz_*@n!-n;Is`J2(`o3-+7kk$ais>8_+bJwoU0Zezi|gyp+f=yPZlGM84NVIG!{78L z7yi^#R8)9yP)xI&rp)V{&%62TrD9cm_1=1)DwV6~c?hyPf9GFSeXE)Om6Mev+O@)W zg?6cSnf6!ha_tK3O6@A`YV8_Ds-{TQ6{&_I)l{Tfid0*X>L^lOMe1Nh!jFel z_)^EXuyB46sKNX;YTmP1WNftQP2YWPvR)&bEn1m)V zLD79beKltcxjm>C9OfsnKfCVK9@3@?-=*5a+9TRzZHo4&BGp%<28z^Bks2vdV7IkNtQbH9hQIo`ze5#g)$;Wcl2@2FgYMW)ssui9ybT zLb*Hhe$^x0 ziquSznk!O^C7QL`Yuf9YwZJ}MEf*QcIJGe9>i_c6)2Yp{9;)a@22Z`MdCfDZ5y5D>6hzMm*OS6 za-|hvnk?1Esxhu!i24hNcvOqiL{HXs-2ZAD6-!Y@MfUjo=2`4As{*n^A?NhG`M$I6 z5Y2;F=0~!chDF9D< zZNIzuuSxD6ZhjMInEv7BH+8@1FNvOXSIUdAm!^!f$E+Thq;Hi^|NN{=b*U0tHM_gO zPX2)veLihxst12gl#zWtErkPf4vx24o;FyfeW!h|&D4I-W@-OYB!5Nfphz7Rsgojg zR-`V=SU77$1>q@6Vx}T>Ripr8d-ePrV}8R_k0p8u>91b(s=s}iDm1+vvJ~ef)0xQh zoM9M;k7+oZa-160Z<2f1W@p!Y5->H#$-PZvcmj9B)ZZsnKRha=f2{fzoc)f+T{!M~ z6jM9S-m)1rP2cMG!(_LXJw@48cHj&wYh_v1$#ydL0JbCv?8;S)|CmNehuW$+$3)2bKFJ56YUI*7=|Q8BFz1KS3vH>FLp z43vG?u4&K!Lo@Z4Q3r*GC*-u#plwIiq$ey3dNPSwA1ykhKoAcdErl&{S^v-GX(`!as37zH-`~hveOQ+ceboZe`((draa(=Wc*pc&&K@1idqkSP$dOYkR9%|rW6fzd z|JbiK{&nm}d^G=`dDH=Nw?MfRqc$I9+0pwng)iUGlH~5{4F2QoyI1X9wRe?}ioHYm zW*EjaWQccJJ>R=DtgL>BZ&ly2ED626hw&WJf3dWXgJc7DiR558M5YAd6={GX4O}eu z)}D~b%OKOzC_z0gJ#tIqi*=D{T~nUi#G7t)a`pC4+nK*vK2R?4PaBzz#Q*(5EazMl zE%O1=68U>MMvj$P%w&EV!d58LiR_*%WsE>;~Q0StOU&4@eY#x7HfKsG0k{M z1#cg|Gw9!J#o6~$Gp3IpKeh0$ubHB1YW;8SFZB>1B9ePdBgI^?R-EEq(phm{TojkM zr}T$-U|MZ`(B$F1lFWT27fo?Z2~9~&W$rB1;oeex)?K|d{WX)hr?gR%_SLG(H)p+N z{!{bd9~SI6B|Jf%q?YhRMH-zbbImyBA4`~JzWk&1i9G$^FZ0K;%%AXex!eBFm-&m- z_3*Xt&g{-g%4ePQUH)&ZhYQ%a`F(Gm`=|A=Hl172j$dCG*gL2f27&S%|ELIqp1UIy zc|@#!AWH$>8RGe|;RCrlppWZo(1%2Eb=4=H1Bf?IeN=CKTu^L(y=RT=M;OX|>&8pD z*uFu0L|?-Y5zMXh$WVWN)THUhdztQn4CH1GTpMS(EmK$Nmd2Ebw!u>-(S#i`)^lNcJb_KmSpFcWbg2e z;wk9#jf<(mjKSGDDSwgwpDX2yI%&-f$S!=pQmE)F`f*J+QcUAavzWWsE5t^zOY9XX z;)AVd`0?2krpV@LPc7%N>ENQvrFbiX1UF#Z2QMS7}8 zuM~-E$B#-Lp-$YMg(pALAc%XprX`g5R-0FS10Q-D)ayh(xsHvBG+puWm_$r=c+B_D z%-4b1?8`%9Z@MQQ@ae^#t%3%HMevzZ&V`oaryFnnd|;S+mFX586Q+Lrn)9;hqrlKF zm%iLxFb$D=eWrcY6tUvV?#+BN+F*Kxk51LbS_UOV#dG;NJO&jM_#S8hZ+S^O{mHb+Mqe#Ch z(sD&wp-3y2P<$0QvOFoj_QsmoRcw0BfHxNI5*r4Z>aKmzK-1!Yi(%(4U(J3T%s+&+ z8Ugs?Mbqwl+WzS?G4?O`#U`fK)#Y9GSap^<^+`*eq)4k1bv$CRMlCO$`WTg(XthPt z3eR-yr^|0SBuQtjE1*amhMT#t&1prev*&3aovhR8>=bF8BCS`X4U2US!c$jJkv6Iq z{hN4Zgl0co7Is(v_36gA%s3@8tVg8@#>`~eDfrfe>F@3C|e%haVX%xyiBQ`4BpdOYj!-8YfL&Us3 z&c1V@e!B6^t<{gHV&iz|@yqSK{%-rW%^LE9q`us^?B8wNil+oa`LyKA&DD$2Z;ypC z`Et|09kzdbaIEPA@-Mfq@OMqOHUHXp^+WeB_xRhf=e}Wl7RP`#(T2!ok$fr>%dK1W z+aGmJx$C0ihWVk-inFE8pPO*HR=U=@HoCUDb~=vn!-{l7k&+cDMUjpwQtC1}P1jM^ ziT`#{Z^CirJ*G&%oBr}dP4H)DM-C738Oi)PQFcb{&0lNEX^6KqzQ`T59@!^3P7*#9 z%XCu@q!wY3p{D!u#W)hd8@wx$oa-eRIE+N-4ysGxoEoTHB+qY!z zoPT{9ivKrv8ghS*7b=+>XBcKkh9ada5}&&e zk28vN@$c`G<*o%xw?z!0e0y~4XZOi;lf;@N-DDk~)tptN^SO7&bc4;eo6ebTH<_PQ z%Dxk&cXh$^l0VlSx%ZsTT3k@S&)j;HZoUXsEb}YUB_kEjw|eUq>UbLGTi<4# zq)SxqIeqITF3)s}bxU+h1Jut!IQY3wsz_HA=}Ms7=D&I_`&$<^Uw)cw9_4b~if)|X zxyUx%y-d=rmC?6W9ekNDm^b{9i&0c_+Z%TN6aXZO;faToo zEdSZJsGGZ8t6Rq>WHk20(WxFFNxJoFbiVnT`LNiio=P?;(hW`}>AJ0iY@2R7SIYcU zO!emQzqF`GHx=peH;*9QZrvV^pnd8QWUfg?y01vLOhw6qvEM4n!`Vg2*@>dO{aI1o zGmnq&YDv-^*KtO;qeyrErog^gTA#|wyhhe#=q{^^fJ=(>N22bEBJu1p2YSx=BPYB$ z4?db7!Pfnzo;El|{`rmXG0@%3{T>5}XMg|oTQUBVA3em)aZ?F!A+0{yspo(&XZ26r zL-TV4U*y1a&P~!iQq!s~+Wz%LPJlV*;6U@MU!Mt{We1(rHbH;(S7T;J4 z*%cI?c7+t_ox1XR&sr!q9&Ucq^`EbW?26fy_*Mh1Vy2zDB7IO7Q(5Z8zm_I)7V_%4 zhFd5`Q$xJ{WG~s(MRSKJ({h*}Y+?Q;7ERs)e5+5+cdXczwJT?SPMlrUXK)I`6=mUr z8mNnQ=!EX*DMbEI^us`mz(h>LEX>ASEJPxfU>Ww{Hl7J#O<&ge!B6|Jwgq=htnJ_c zZh}}Bf-|_dwRS;eR7G{v0%NkSk49*U7HEaG@JC04V*-A`N*usTAqrT58wmy2w?Jpa zAQncjeSt}sf*-(~3NV)f^RWQTrvURQzT*o73N;y6wUVOtC(;RE`z^#fzKZH;c|fj|UdHFkh@ zY>Bt+J|VPvltTs3hPEP@o0f5C8Hbi}Xc>o0A2P4W#9AiSGO^}bUC4)V1=sK!Zr~Q~ z;2!?KpLm2PAip~Dt0TWU@~b1iI^v`wzg&R`9r@LfU!60Gg8b?jqpmi(!wANsOU6SX z?3_>&0Z0HfX15S*V@K@lh@IU!Y{yRQ#$N2lK`?$h+Oj)^i?|BrV)p=y*^V*WJqNX6 z_gV;hdoUh*4|t+9%EKGJs02T>K^Jrdd9d$^K8OS3v>%BdFdxL(ei0b6J!7_K%=XK% z1;p5X2gsQ{IkP8c_6KkX8F(Xvg9GT>ft));gZ3T9g1T~;0{U^70b=7Y8*`C}wb%^W zcOW(n)Rx0hP+tzzmjmrPkXr}h;qW_d;x@>kL#7ap5}Z*295ar@z>)fJtcvQW17hJ= z9}UqQUC|puFbpH0fMdduW5RJ9CV;U!GImGC?nq1=iHRdIaa;-N*pab2uE$0&2gj}0 z4(8!_5#*{MV=Gt-y+Aw)vTs4=R`9+MPI+Jp9muJZBV0gEoye(E8I(f>_<-7XVlGY% z&C8Bt>C>5bJ6C`Yh_y4Zb|${gt-yGkyMtIe_eTVx@I7J?kC7nm z&cxlBxH}Vf=gF9gAF&jNaRbD!2z6VeHX6epT@V2JDH4Pb7|;iCP(b@dXurq=Oak#K zG8eys+!k4ZRUl49_Fx|lAQ?w-48*O-72F58EW%idYCtZFlFOob@g1yT11)qYiBj-F zS&+}7-XNbv$!F0jsD>J-1&+0%gD@X^aZ`w5`M|!#TA&}sf!GxL2{Z9C7J+#bV;;rU zU>!DK6Sm+Wjvxihrx^1oMjaIU6K}zsiZLe_=Hy}p`gF;U0w@CJ;8G41K(1XFw+rKT zVcaf^+l8^YbU_5ju?snNA;&J{*kvFRFa*Oe0_4SI7G{H7xG-Or1z;X7NmznqSdNug zja1whqBvtJP8^H3#~`q8@m;tKjyVn|p(ihT@}eg%dTLi+7|cUo48>6bZt#F7$c4Tt zs)PCH>%b2^K+N^bNza`0%t=q5`oS2A;g|yQp`U|!V9a`QpeF}Y>m}Z@ z6l7o5ng{@SaW!HT#(=tVWgf1~!?#+rWHWnUCvUTmiXpWlpZl z$(1>|zJ}W8dwdYW%^J3FLSZmww=yV)3Lpn=RZtBzz#QBc_nU=)=7Wh>80IY{y+N5BKMIiPu7S zP)i;<6ah8kQ5uZjgYkPXehKQ3@4C=GswUdFxOHya7&2N zb|7}8zX$!5rd~>uuhJv&6UbNTIiRo7^i}$e5MBbrz{>&T!K)C8z!e@~?p|!pRfaao&_$m}q6YqO?fJbR?j2MNnn1IRn0n;%PvoQ|~kccJt6)Uj@>#+%2u>-rY4+n7sM{yi!ID>Pz zh|9Q!-*F3f@dqB_37+E>-Xar!@zX;!kdPPoVFMZVD2T!+isEpE2TGv~%EJehP!%;$ z8-8ej#%P9?XoL3Xh%V@co(MuH`k)^o5RF(2Kmvwh1V&;E#$ytuVj5=PXUxSfScJt` zh80+ib=Zh4*p6M;ivu`}6dc1zoW@yPz$IM8Z@7s&xQ{>a7|-w$Z}1*jLR1v6gcZI+ z0cc?dM-)O4xS#~w;R!F4gEwfTViiLY0M-*ZZ zk3krM;ZQId<1i6Z@FRZ0EX=`tEJPBPVmVe}EjC~?wqYmsU_TBa8L2pdQ%J{oWZ(*} z;|6Zy9vh`|^JBSv8?CSWptz;w*SY|O&~Bw`7E#Y(Kf zdThd0?7(j9!$BOuQ5;7a&fpv_;xew`cih5V{DFsfg6DXJx5&g_LR8j3LSE#D4P@A( zAPS=>io+EiD1|a84O|$k4cz{X_$eZF&DpJ5f)<^R$w*OVI#I+J9gnPQb3F<6PwD!rV8_~!n~_6?tKC8Q<9K6vQoe_Y+7=^J|gRR(s zdw7Z$+_H9r3rc_-)gU)D$W0AmT!YxwAhtF3f;iPU2Kuc@zcuN%Ci$(&J~hc}P5P-h z89#t=);t8pSu<0JTGUsqyr>F4G(ZpZ1~IKgU$y9~7Jb#)gx%N&YPr^3ke}KPCxW{r%Y2kA3~v*YCX$_1LeT70Q4))FYPldZG{5zutO~+j_h45U=o7i26RL z0p?tv@zrOZ^~ZsB>t6;jZeW9gU~Ublp9Y-~0P4KKJS@QPAP)^_yJ2bgqB3GJ6x3+L zoj3&Y-jKcfx=#}!-`qOl&d+qf)Zz*rj($5AlW#^;1+;sQ^2 zfjKv!?0}SOId|^f@xYw#{myA)0{LG@Aly zpc(x&BR0*LcXMLioR~K!=FJnpvDkbJ7+Z73)|_oyuuTiLX~8xv*ro;Bv|yVSY}0~m zTChz^I}`!)Xi45%GKZGrz2!#i!d@X-X;BDl*NWF#F~3%{*J?Ws;II&_3xMOcHQTlB zieMP94C_F9t)GA#w9XWwO?A`;vnwrL(m&*u?^ISe;(LE2lC_J3vB044g2o^ zx%B@iM2CFf*zM35ZO|S+VLq5chYZ}nZ6P{3!wn_T4Wa0RM6ARbJO}aUNZva&MLTo= zwb6;%=tOOFA|{<)fS7b9W}RDrI_f+ROYke`uk#~36{1TO)CJpiAtqf$gP3&Lk5rrx zqANM=O0K#VL3fa=u6;2UKVUjGfY@~1gDaqquJ?opAO-=%DZl~MP!A0;03$FG#5RD~ z2C#ns`vowz0LIqM9p&JSFvK7p%did`aUJy2jd^!xjNMCs{kpeAM|8nxOu>&>4|3F< z9CiN<_wlC?JzU@kFR*V9_U*yGJrckedyE0&?7=vDFwP$I+k<|4&~MM8@IWaL@1EqO zCw0-2{d!KqRHTEr^klz4;u*+T1Bqu~bNHhZ=p&GA1E*mhj^a4TW#C64dRc(_=v4u} zV0^uT5Q^!ThXpu{lQ@k`A%Z02MNKqB6AT6Y1&s%D3L@V@ocn^_2@y;`!Ff;-wNMw| zV=y@X1uq4$2qx#joMVFT;twH0$a9D@ilH@#K}Y}w!iZ5|zYzKhA%-FB8^XRJ>>K(W z?7(>;v;oLLXdA?Wv4)PoLM+EBoWW(#M(BGX43@A$RrrB64AhJv2GopU8K@b_rNWfn&7K2YeKwuNR1QU)tzP9rO)EFeYOrW@8VMk%}jH3vwGqtiwv8H2Q+^ zhQ(nMc4HqN;|+*YKep{h{q*xiI|QHy#^Fc&gsot|eg{FE`aQrSA^I1E2TFlD>fZ`& z!Cde*`g#=nwJ~F&b0A93qY&4fGvJej**LH4Hh@zh;;ujd#5JZL=sRWx zsKc0DV6HJKLc}`41&lkk7dU2O!$BXho3RZK@d|H+h@(LLf7yx2C`~awl;aNhAumZ6gL5@eX0)3C5&k;$W_D9^uGrSbS zNPouKAjZbw7>9`-Jq>8M}-(`gMugw@-&v397~^LH-YWO?h|4hF&bwFj-heg5Q;va z&c|&AaUY)#jC(w>A5S~uX=gm`jNb~{7=KWR3G6?CF-<6e-iSgBc0hfdF->F~6J6nf z@4?(A4g>L-NPH$Tze&{CBx-C@afEm3F4m&Q#j@f!g_@6S`s< z)?p+565_|a$d7giKo2a!3arL+WP)v{)kAa8-n1#0g*l*ZrrpIKLQE&;(|u7H)Yfzb zqd~5w6Tj)?^e1bOho79ln0^X|0ZXwK8}I@j@KJ~vbucia+UW=T{4 zea|GXGl|7aax?P~PT-Ufvz*}uww;xLk)XXge;9hX5rKQqtS zULfYPiTUga;Pu%v!Tz)Ffa7vbIq>?N>KF?8nKK@YcMfry^H7Mn#B(llnoB(ACV_gL z%lzkQkRLYSwRz-j9&wtt0Ka0T5c37TLjmyGd}27?fNj_h=KYH;i19DX`g%B*k z8mz|?yahR4&=MWd1v_vMM}%0Y2YFps79+qs7EZ$NU@i-p%c90;gZ7~PMYO+&_7}Yo zB2mB+%r(&;%qNk!CeFq@oX2(C5F&}XNvZ>4lQaXrU=iL4vDgB6z_;k22(h#doImpBE zzl2!P0OVr@FVV^GHThXR9pq#+{jH`pR=)*fTGJHmz}VMNd~23sIe2}o1`-;g721L^ zuceK(^ttvPp5lcN>l&doXlot$U&rg~h`~DgT1Q*!=xaT5U(a~gGxzm#u^7w1nAbDr z4UBmMZET>XHw0h{_TqpL8>#n=dbnW#Mqnh;aRt|f*yN4sAaAO5%@(%VV#EYYMk?rU%XuNTT7#T#Wn5c{ z;a29ol^AZFgcF{JPXr+peb5gPh(;{P-OdDT$1d!}0USmOj^QLugK_S> zfJ?ZF-*6Lma36o-F`nTi-rzm5grI4$%Mw=j4h29CcGqh}|w?w~M&#B5u2O;t4*0I@wJhyXj+hH8esK5S!h^X19W=_z}}F6U=S*MKGV; zS8!d3J@(LpJnV4?dEe6=VdxKHw};s6S&EGyk9)Ra2blYwOfcU)96x(4K+g6y1bN!q z6fI!H6#Rf`n1NIf+r1gMjB7&dBR2b}iG9RoAFANTwE&El~(XK>x|)HktV*6U$^`nM^E`$!jumOQyz> zW567f={K1^lj$>g4Cdk&Fwf*gSd3-30^*wdCzxmQGrYuWAySBO3jLMcnz9OOu>qU075i`y#5#rgNuhpIe!~rr^OQSa zY$>ns7MUO}M>ViSag;(Clm|IE+6Ya-F>|y9TB98p+fk00qq8sv^Fb_*CSoPlfaBrl zMlhD6$8Z8CaT;fZNX-XFkdIX2l1f}sOMtkfmPG~lq7taT)Rv(C)HZ03j+ls_K|WH+ zM=H5UB^RknL3~n)Pb%?AB|fRgkp}utJ%e-LSU*+(g+cuvBL>H){bL^R1Ti>9T^_3f za&@c~+JgR%`J)r4n`2W!ZjLPgF*vpazhVW5!Lcpaj-5D#bkP5?^T@yzA&$!^20e(u z@scPF@^PFP9IuKRs0DIxyaVX}cxMEl2d0BO98UrOFI3`XIgA>Hy1pS|&{}a^ai6&?cVsV04oFEn_x}hh65Q0G%f?*gA z1*1X#Cz6qh6Cf5Rh{cI>xQ5?x3wQ7yS@ft1@I7uu{5{r|8 z2!;WDFcc#||0hRc48~&*sL7KweDXAi#Ytjul31L)f!nx;Kk%0jX&TUfnuNT_kGh~H z)2PQZVvt5XrgZ_iNFxSmy%C1~Fk%$wKW!`~U@{JXnoOe}(}+PDF-Ri@X}9ncFYp@g z@BxhF6#bu~|5G;5!X2I{4KI`fIXguRPQ@S|O4=N!H1CRjnahiObCKsovhtpH= zBYwh6ti&47|LOJEgsu1!Z;=UNaE5%GAs1(;hcm?Bj2#@|gtDjr`ak1~%BY4248|}J zgEQpg47oT%J)9v1XJ+7M%)wf00R5lYjBVJ7Cm;`JJ_?ae4AO}~I{l~9e|mAa!UM!2 zomiw3i}Wg}j#{V#e{=%1lHL{F5eWKEPs9@Z3SyB?EYjCuJ9c3&_TxMWGXJy8|19%A+Xzk30hd^E`c?w}L$!L2aCO#%Pf5^FLuG zW`i-Gr;Q8jbAfSQ$PXLHVE+rm^+I731v$Oojf$v(>R?_M>Y_e~^Mxh|LIkL_3-iF3 zFYHDduHp{p_riTV!*je8;-UkJ!3)&TMe6xtdDKK5)I&qi$3lampaJ8CuwZ_+}8_ zj7}i08Prk+IU2#$i<$~cP)xCG*qaUFl)HQs=+WV{#R z5_!603mu5rCHlYQf)a3tCrYC^MuPq?ZO1O`0msoLVsVMSFOic=^m*ww+{B+??w9EA zGIPC5Uzfi_0Wjyw^m&ik&<#Bigi!PbwQ*%U60s8} zaU1W1xN3<)pti5pM+eNrJZu25yGp#So(8$ON^Y(aud9sz>K#1BGcb><#O>;Pd=TOq zwQ-F$uQASR!!a6@F%>^!0T|D510Ea>BuBG7&&Vd+SV+_|VK-<^5!vOMd zJq|-aU)L4T-}OnDf*rC z@dDK1O=5PF`Q7{|#4Qast>&>~1~7OVIW$ z+Pux&ZcE4u89F$i7<^F~)ldVqP#5Iwb|W+eW4YZLZP5dP2tp_r&uxyi+gETMH*g#G z@Bqy9HgmnrT<lhiEYGJIwJ8@wrP)-K_|J^g=8K zAOS-$0wXa7)aG4sb$2Qfu^7v+0;|D!<1WY4-7TO-?h@O(yKxFPLG11^&U?h@o);L; zJ^H=JyzfPVKJU@zJ^H*ypZDnV9(~^X39~Q<^RW;~Sb`MX#d{&{7XY!pUl+vqJ~6&e zjPJKY2N37`fe3~HeGmpCW?&7D;ueVS{TE=&_ut_I{u1Jk!f*wB{81l`g!r>M`1_yz z5d)4%%0@i2fisxzLp|I;9X>3LvS7c5wLn`B>!AS}gRwts4#xO!1m<8f4uP>fWNZ&J z!FV2NKn*;~i~JzxkI3mG`h8Rs#ld(Vxr4qQg<>{#;5Xa{_4Mcop5rC5gm`QPa`l*) zKIWYIxD;xFaXk)3Jf?x%JSI1fY4b7je!KwG#^dv#b|2rv1JK7~#{HO>K7J*{6AKWZ zC-x|a!eG2l80QmW^rQmF-4lNh(w$o!TwL#|LLz-3EFu|J5QI0X!99kd$tIRu?)+x4~*#<#qm5Z9KiAUoLoJpMxIk6&&k#E+Cscshhw;i z%eaQ$LH=JdA3`WzF`ie%>J|0$ih6p@oL?s+6(?{C={Scccq_ykV(^CgcvD1(w~s)5 zzq5oDXzv~Qey4>Fl~D(EQ6C&n@4BEHdLjrRpibY70y%j%6V$>xYT+I8f0qPe`;HjA z+Y9FKj(*=uAO`RA!v^H%y*;Rf_te7sq9_jH@!kWqz&PI5Lqjw{bF>2ac<+x+=!))O zeDC8xZr^VLeZM~mVwY(R+RkK7nT$P?{ADuk%mrZkOval@Eo4#)ne3ZMJ!KNx%w3?J z%>5w7nKwW#Gl}5`V)%jBeIRxp>_D795a$mi;0EUK!4u@_Lm4zfZ%hC&`mhcgu?5>f zUmy1149Ne7-*6L*|HFN}!Uud5BFh4KK>u0vpGEGnsI4qwndJ^25VI^|l|_uQ$XQlD zL?9Zm7>r>Ufsq&u>N)FY5RWXzmBqNS7*`fC$U1<-NWn1>+pOy#zgdhk>o)F!v1UC0 zamsp(r$YSY0*>v!hJYCVbzX>%jQ?X*5R;GJgE4&^3dZx1@q8SE=~##)ECt8Z$5mL1 z4Paa!8P`X~^^tLXWLzH^*T+*hgR6KdG{OoF(8CX{&;gwhfF1}%AB3SlA}|s&@hjG2 z6SiVIF5#8XXbPhYnt^sS?5`PziI{>PFdej`nT>f^fNj``J=l*!NJc8MgvNp~TUf&u zI@p8pTe!gkrBEIC2KBS<;6k z?OXbw3y6W`BqV`2Snfp%j^QLugIHL;#2dU98mS0ePy+7ogcr)e8x_$KZO|SaL0?ig z^h6(oBMLEyg964cjRkX%W?>G9r?dc!P1+3NDiK$SxJvtR5Z7@J5AX<2geH%Ky!a0J zVFMYBz*zDGApyfdUwMdEo&z|H6fplh%s&tF&vO=6aRawOUwQt(YrI1y{t_B1Vr)e| zt?0*!d|DMnX_Q3;_@XkZq6IpjE4m{P!5}791HjmH|Vq!&1tQfx)=CvM!NEq^A~OVtN>xz7J#vqu?mAMnj;err&)U|# z+FEO^dsXY!YTbK(=OzU8|MMHauQbWcn|q)0p64CsocA2xFQPU)1k)=a+%=;rL|;Ud z6BR@a(Tk`hMiQe4J;4!(7)^{J#uDR*3B(j)8Zn)iLChlN5{rn%L<_N$SVpWOb`krC z{lu@tZ^R+u2yucqL!2ef5f_OY#J|KH;y&?!ctyM>-VkpA0azde7Qg{G0ts*e&cFqD z06Fjme!yQ3l0XO05p)8bK{7}IsUQucgA9-fvOyl`21-B$r~*AeKhPfx01aR;7y(9t z@n8a&2quBaU>Y!hZ^0_C8ms|p!8))WYyca}4uV7A2sj2lKmjBn z3x&`c+CVY1hfYxM4BepzltTqnK_3_Z17Rps!$=qfV__Ujgh{Y7Oor(&19pYkFdr7c z?ywk^!wOght6@*r3)aDU*Z_ya5pXp88cu@WKm(i$=fQ<=30wu&!7cD-xCy5L*EJi&ZHlVGV}wP1~4li&xz z4#7^r9>IRWA;A&B3BftRMZq<}b-``HeZgbFOTjCWA?+Oq+(Ygq_mTU_1LQ&S7xGu~FnOFjL7pSelUK=W?koMp*$#0N=hjy6%|Z{P+?R!6+v~TlBpCbl}e-1 zsSGNU%BKpb5~`HaP(7($6h|RyG&P1AOO2zxrp8kfsEO1h>KkenHJe&MeNQc=mQm}d z_0+G_Z`2{`cj_?p2X%z{lR8TMMIEC~QGZkVi_|shI`toQkGfAipdL~$saLdsCTWVM zX)9Vp+tV(z2Q8<4XkR*jR?}g0IGssn(Ou}SbT*ws=hAs}K3zZ;(%tAHx;tG=m(Znj z8QqJnrN5$k(|zc^bRAt!52W=pM0x2ph_(*)TSOjbx+PShhXemCa^z*jzS`&1VbPLRQ08vRbx^ z?ZNh9d$aXy13R46vt!t?>|}NdJCmKw&SB@Xi`d2N5_Sc8=b%1B8LXC}FfP#!zF?o&v8Ch%zj*_|_e4Mqm#D`w=*Wz#{}6Ban{3Qv{xI0hH&fb3D)k0gI0E#CkSi8hOi}ah- zLx`coFk(2Te6FazYE|QDJ zG{tc3xOgss?-=hEyNU5l#2zk!Q)xu;!66;`^-Hg=uM5`p>sO0E4FP9EEr|pCJqNic zPG#tMw!NPCojCltPJa@AwRQ9u7i-2oNi;VRr?@ySBsaLEM%SpV?bBg^PHXPTdE&z7 zFqeocyo>%rT;>wF4$Z_>;u_b9tNq+XZ_`HKNiu{e)9gR>se49wN5ewDV#ChsjDmPb zJSD{CMqm+2ha3|goADW2wTpE{u5&!^!fUpH5 zpa2aR1EZ8PnOp{!&K2PHx+{Psu*I0b3RnXhE{p5Jb!`D6U;dVjqC5}) zB8jk-AP@wBU=RXAff|H?a1g;2aoxFMu7oS)%D8f_g43)7Q6L(`fOa4j#DRE_0NQhv zoR+KNs<|3Y$MxWPa=oyMk9_aJ`s51HhO~IJgdDxE%=K2itEkwX#sseUr@*O zaa)b8wF86?Y#=YX+Apj6y5U@bN_Os*wp)m(cHT2ydZPzxcLB=SZ zX83vgr&24UbcSKsFP}>7j8cOkYsaTjiBUSs;Is2nshd$c$I$OHsmv&yYxwCisnRH& zZz%r#Qv`pb^m{}3;ZLO@M(HAhYWJto2&1&gQ2&{WR$KqTsi(3aG6oO(Qskk zrx04BbhCju`Pt1qjN+|^{@bISjD8JkAo^>PWn8@#Tx<({P@0^@f-?MXl^}xQmx<fwC8rwh3KU+O6XhnYG#joaRu2;fq zg0V8g%=iei6>j5UVvNN9$o*!<+0Nta;0}EWXCK^eQs;N>Fpiz5=r+!Ofr$IVO!px; zt|Rp4cA7Q*1OCZ3b%gt~2_EH+nmXnPDV6j1%|I7hnpYzZvKI zk2o@cg2&;>7I<-2%s46@$A`Q61sp+;8Ri=97*0p69a9U!+cb*cZkUmxd88Qb<`VBYWj+WcNHOfaIYrIFlob4wiG65?KbsipDej=#hm4?T)IK3Oo8 z2Qk|78}79kWCjm1lY8?eknhYOZ!rkY>?tNkT_9N4rp_Yny&0*QM{40ddXcD$lT-u zzR8CO*nO$V=dH>kyyn=>kCxp{#J?s9B5bMPjo_`|o#4IT0|E{RNDy#Fz!d@ar6eFB zDIiIbLckM&E(la1Fad!DpUt313w{_zz)>UW(ze{UvaxgcWTi#gn(lEjXl=TP+ooBh zgGuaS*lkno_)+X)663{wF-FYt4ynz~$IHy%hQYNxJ}nB6o~9c;G@{}7#y~?aQ8y1# zP6iQSt4RgvMS7D;QbqcZzN8=NPX>^I26?DFW;Gd1_>iIaFN_Q)BZv@u6;1dc;LnK= z2sZv7fdi8{;h+ndg1IJB5eVS&jZYn%Bhg4gbcgz-OfD}6{@6In*~CF{s~vLD%>9DqPO z1Y!}0Lm(c31O(bIBO7s-2a$t8G&vN3L}QPW5J={<2&Ca5b-HW50k|m)5{H-&uf&@> z)PFshyx-iy81idg+p*+01Uewlv6&oCVz20gKxbatc@9#&`POOV48F_L5lCqwXCi=E ziz=(BZNQ6>zO{AQy2fJdz{dK%xMoyseq;{$9U)n~7~d$u6@8FSg{8+)0bT}Eys!d8*X$ra>EauvCnTtluU*OBYV4Tg20 zp5d#=O|8E00~v1ggr9j&=>E|YN{ya?t7q5^DtR|B$n5?!Sa^)iAb;aM;Sd7FW>5Ho z_k<%D2EPy}F?tXGjy=J`tk6mFG|vgH%at{eIB#H^8{I%pULY@-+~A_o4J!C@+JAo zaL*VeZ(5o8KoJz+xuHm&o4SwOVBZ>o0Cpi0Jzuv)Nfu>~xuI}Asc+?ma>CpYA(RV# zxfvhmXMFFExnV^9t$I;1N`cv-#vI07RO7>U3r1oX?O1l-3& z5KVQUIwHUsO^*1O7=!TYpAU&z1a1~Cw|`T|KSwor)sH2?DW)ERBx&e)t9QH>ZyKIe`)~L zV9==XpPH5+ysHY_zAn-#whm}Rso z|BekSFe@~Nn#(ir9RhQjsCfwB3zO4R3#lfPS#ikY+&35Zk9T@()-O1{8E!eX%EZb_ zBP;X&pB~ykZRbhZNNu7vQ(LI5)DP4)n);FYiTc?v-^j|2R#x^2l{9lVE>V|xR{lX?brW?3 zfi-Q;euFZ`bQy(X8nd#NxB49;E9>}s-YUE+s7KUO6A4d@By9Tpp8t(!yr$k0k{0R> z^_F^vz!n6yBJe{C^?@d6fWS5cenwy?--O?}iF%r$g?PGwXB9b3G%lUs1@&>-ny{s9 zXj^^_+*sRJ$CuBX&rEuTbNo-np^3Jm#imuSu9>zYs#`ltJJ6CgEGGoE^HUdF+Ld;r z-HS0+L*szjx}FH^(1_wXw2SH-5f!eF2nq{rml6~a-Z46;Lvkek8XXf65tEz}q3+yS zQ`@gseVwkMw(mf^=}4&6nM=5_+Q+|6fWNS@NT8jqIG?=6wvaV{R^x4#rle@g!ky6ODaiAEvt; zFZa9R?GTe<`FOsA@0RlAb~=JiBqXcoNIHs+reo-KbSxc5$I}UPdxI#tHbDWx{3%}LRZil zx{}68@H7H=xH^l#xg~TpT|?{W9tfOA-~s}FBXAMt$G3k^=vJtX>=YF#x?`Lg(f#Pg z7P>z@fNntG5(3u|zz+S;%)}si7_PL^gXtkO9z$>#;K0AqLJy}$(A5ZBMc^84fH#K9 z(Dj<9;q}GFczPT?@e9vT3G`$dCnXMn8wlLw0;&m_5)v|F$%dW|$7S1*mWB?G^9^q= zWwFKi1FNfbx|+>h{#g$7D~S!nkHmiBAU;WO2=7xIAx`35h1+-+;5|MwAi^gF-1%BO zkOMDVA@2mr!7zLP01pay^Zy$#9n8d=|KH)t?gFq7mvDc;CD5n1H2DU+!<+py-src) zTm2%u)$af$cum_C=D<8y1gl_gTzwi2$9lqv@H@BwZ`A()x5Gd2=KOW|FMJ4}z?TAR zfygl9sx(L0wtS%t^gKL}Va{gJv+1Gqcl2B%bGH%r7n6qo&JuT*(evpA^!M~a{JsEz z{}8x`zK11VL&C2SDvj$ko@WeAod3y4QjtX~zj`;MDy;u(9!1+V)U z2gZ?+Fiwm!g0=|a$LtUkBWS;rab?^XcgBP9M9=|2DS}A|c0e!{0|Y(lXt;SRKa5c^ z0XRD_K8!Ep$M_@Yh@b>PCj_0BP#u{dCYYZ>jYH4{L08l4srEKf zu?V{JW6k=fZbCd5FzuN{CdnXt<|%}@X^(6>aMFOEO+H`5b3hU&|&7 z!~Q#Yex@HGFntoO-&?;Cpvo8qOedzZq2pbDnn`9-a2a;M-C24jjmgH*$fPqFOeT}X zbYZ$8C__+=paMZJ1icYdF2iQYB|@;N3YbEpB~<)802AIG!9+g2dEo4y->ZIbc3q#L z>2-~|0lJ1pJQ#MW$Jcd@8j%zJy|b=rV9$a9+Pa2XEX!}yHtHG-`u|c4S^v3oXmuf` zg2DS_OBoGQ$!M7>rW!#X1bq?oLlB!NU@4;`e6Sw9m|6q_@dO6zfE@$x>>9l9?di<) zC&o7eJEnolK`@9@b;rYyOsSNqjk%l|jH8zsf*>BU9-d}~Gb5Oh*k+>`JyF6S{2IfI zWyTqN@At8WY7R$AJc6MJ#u=LKw<}~O;OP)Ek(tCyW~Lw*hF}ze*a_O|W3d?;qF5)U7`QJN{?fLF2FHXYj&=e+Fkj{u#qh50nf(s=%yb zRvT_S2qc*`xDCgL1%|vw-T}-;24}YxW)riS*@9q41Un(vxrOg? z1=uLHw>9o&)BeRA=L6>$f?b-J6A0qfhcR%-Y&+PQAK8Q}EUlc}Z!iU{_#bf-YK|DwCpdruTj9u@ zV=nNTpGPpaiTNAByiY^^G7kBxe8{tW$meH^HW@;nWz*|?H`XjpHjI0w%5RO>#jKrM za6)EQVfWISzWs)dZf(OHv$mhNO^h+~c7wUaw|*1B!Y1Z6g55rC{Xg9L{kGPNK5M;@ zZ@u*Yvvseeeo01+%xXMl@X3NE<_Ur&P0TX{OR;(FUik$uuS_FgTZ%G2@Q(SwKky#G ziYCIxxY*FWQhKr^%it3aEXCpxp%Ov7+Q)?~fpiJu&#J*&)Tvg){Ygk z_N)Wz$Vylz)|quN+zXe-u4dhc5Y~hBWTmW(m9q+b<;^M)tTP5cJ%arZ?2q681ScRk z%lNek!JW8)j&GEPL9f#cx;I{iBd_ypE4pZh;9AZwU5zm%87f{o8{}`~`V;1&COHpR zwRe?d_tVv-*A-~18rsS_Y&0A5$&NJ}hw}@IlLnqDnrDb?BAbHqE1Sf2U^}v%*v>2t zuSNt1A~*=a!3g4XG;|r8%BHdD_&pOd0f!+t99hHB#;fs|8LaiO5B2J{#VFg2Ex`fI z7O~w~JVuT{a3q4GTG&#YqeBqX^Bmy(95AadNbJm3cgCNJ{A0uqpo(Lji0%B{~ zukbRb>%cyJn%P?ZI0l}=uzmPG^hIzCKdE8+VM%{>z?^de;2sahBKY-5wvipk4#KBs zO87r^m_}q*ryW$=lRsv}AMoK90eI!oz~}dm$Ae~_1MXe%yaoRuIL_4m2zI1K;Q_WDn=Iy;X2njO!K zVJGseOh<4Mf-{X-eIlkuBMRwWU0(&a15pE~+2UyP==9nqScjtZp1Q zKxZOoD)FI({f3>!V!+7=PDSvWwjqG+WiaJoJdx+~aM@>NU(+!0#r(}KFg-WT^qg_| z1o%TakaS_2i4p|d5S+o`G>5Y~yOdovrw#%ql3k8FkFO*ic=ExnWLL4POZwq`S<@m6 z!EX^XXhhkDy6f2;OpmT(*OzwgR*+KLM`zr?WjEjr+)o6p!OL!9x8UGqHzPQ^iQS6u z4lUk^e(TbK#WUb$_GflGg5M#CtC?+%zKh+5*X8VPb`QH3!MO;|LvVfzyPxo34`O4mG0aH>7x6H7z8y8^7!4fs1GvwgJ;z?c z;}d(Hy}UPQ1N!4?FUAh>i1`wx4WUcg>Oa2bND`Benm!Uc5C()DQUR;wFaoLAek zm+`73OFOiFU}G2T85)rz)-%6fpIYND#`()9XSvvaL)iO#dw1Bo?0+mqT#n!h1Xm)s zY6%&N%Z(bo`V!2W6rWt;>#hNBM-{WLgm{N^1^b$P!@gzTvG3UrLP7`-T!Y|R1lJ+B z9>EO=ZbWbsf}8P7o&Ph{bv|ZFJ}Zb!Nz?R>r|J*i4FYbh1{699ov;Cgct*RmN$8B= z4<-ZZh3?pZLJuJxtF|Hd^G5>;JmOt{0N^TCTUB2f>?~pGA_6T|LiI~pI$-OHR{?&AZtZ}BL_ zutI#W)*25#&a5lzjt3k$n}rk02KF+0k9~|!xjx61e21+F@DW#uP$Bdds)WA6nZo(P zWy1Bst-_y$`-OiAj|)!^DWj{9I`lLamV7VrG=%vrGur!Qfe7!8E)CmvZG}u%VbNPWu4`4 z%Mq3%E%lZYEEifXwcKR6+j5`f0n1-44_O|zyl8pT@|NYlmX9r8SUFlLti1GA-c~BB zaI4N%DOPD#8CF?VU9EDg@~jH1bXGmBYOQ)(^|h+E>TlIxHPC9X6=yZZYMj-0t6!{+ zS{=7KX?5D_p0%~LtF@c8yS1lvfOW8SsCBq?q;<4)l65ERWb0Jxbn60Zt#y@kwYAQA zfc0SOq1MB#IqT8ZW39ioHdxQH*3Yq?YkkG~zV$=v$JS46y4aN1Xl<%(bT&P0>TLSi z46tdm8Dz86=A`1)@gLaJ^{0Xrt(` z=&b0j=$`0-=#l7&=$Yt+=#}V==$)O&PHg93C$V$3bG37~^R$!MDeS!My4dxzn{4-! z-DSHsVu@HS&JyQ~3&dJ+mAG206W59B#r?&j#CkC&9xa|Go-UptHi#FCo5aoHCE|_Z zP2$bst>S&+{o(`SU&Q(o;`8Fa#h1jF#ka)&itmc=i67W|+IO)ZZa>z3y!}M`$@UBE z7uqkjZ?<1zzt;X|`@Q!2?GM`jYX7JGU-rlCPuichzi5BmfpL&K_&WqSggB@j+B+mV zBsp|+D08TF7~wF=fpZw`FxKH~hY1do9HuyYL;;__Vxx-3_)edX*4(lB@I&5~> z>hPDt6Gt1zc*knTQI2yRw>VyOyyJM+@ju7=jt?B)I=*v!FCipCiIv1gB9gdCd?kJo ze@UREog`KgCrOZGNHQf^lCF|cNtvWvqLK8G%$3ZSd@orfX_B-^mP(dOR!UY&)=Jh( zHcB>2wo0~1ev)jL?3CztOZH0kOCCxdOP)%eOI}J|OWsP}I}uLMiFBf!SSJf7D<>N# zk(1cT(aG7#&B@bA<`m=<;-q#8ck1Od&}p#KP^aNe8=Q7J?RMJhwBPBaGv&-U3!N>U zlbpLaXFKOQ=Q~eup5;8pd9L$(=f9jUI{)K*#rc|xvy0Nj$Hhc zG(s9JZ6}SB=1B9T1=2!kk+fLaLs~2CEv=LGlMav$myVKh($Ug6(z(+4(go6m(#6tK z(yP+z(wou;(#O)LG9V*mv`i?olv&ICWMQ&MS+uO3tg9?XmM6=X70QZaQ)F{x3uFsr zO|ll*Qkj08Y@=+mY^&^d*&nh$Wk+SlWGCdT+)?f_@{97z@~iUe z@_X`!^2hS03ZcSMVXd%Hh!kRlRH0CKD}3|{KSh8dLJ_TKr-)PJDDo5qib6$^qFC{b zV!mRbVzHt{u~e~Ku|cs}u~o56@rUA1#b1hJiW7=cimQqnid%|1ivJY%6)zO86>k;q zz3jaly_~$9yh)OytH1`UOl{ed41*8;5Eo=h}ST$Xxh^B zyw^pqf4r`GUH7`_^}y?~*Hf?O-j?3h-nQN%Z?U(7ce-~s@9y6C-xqlI@b2Y3(0hpY zFz=DxdT-=C$$P5zH18SS%e+^3ukv2)z1DlZ_jT__-cP-sd%yC2bXq8hFmshXsks+y*nshXvlqgt$L zQ7u!gQ0-LhR_#^oQyozKqB@~Etvai^pt_{Gtom2=pX!0?kq_;|`dIi_`si(Z?0iyu z@_l;v^z!-2XRyyOpAkNKpV2;JeWv%7x#%G<+dY?@`TYUcYdF~5*Nngg- z!q?hYOnMZQhGOMD;rzV&_YNB9Z+YW({9 zHTVtm8{&7;?~30wzZ-tH{d@Q~==~@8PxYVfZ}6Y(Ki7YO|04fp|E2yb{8#($^FQeS ztN-u*NBsZtKjDAc|D6Bd{{Q%2@xK;81V{rq1?U1M2CNS_AMifVBQQR&IIt>E7uYNC ztH6f95rO)^(ShRvCj?FkoD%p=;IzO6fr|p01D6J_2wWZbTi~g{^MMxwF9+TTyd9{& z8+bqPb&xPf9AqEl7~~uz4^jn%1jPrX2W16i2jvA726YcA4eAxtJE$(Ge^6u4prD~a z!-J*=tq=Mm=zg$Ouy3$BxP5S^;FRF>;I6^B!3DuZ!4<*1gZl>$3LX>ub@0UCDZ$f% zX9h0{UK#vD@V4L|gSQ9o4E{a%Xz=miQ^Du;!54!s2VV>RH~3`;72+D=5h4rm3Q>jl zg#?C#goK4ehQx$)3CRh`3n>ig9#R@o5uy#L3F#S98`3*uLdf!v!y)%V?LwnNOGAf+ z&JFz`ba&{!(1W4BhW-_LF7)rve?qT@-Uz)F`fup-&{v^vLqDjYno=v&A?gTRJC0Q+ zs`VY!$?7z9k-AvjL)}|lr|z#Fq#mjsq1LOvQO{RzQg2oNsNSyLrQWMPp#D|;yZVUw zFZBua9rZo+1NCF|GxbaL8}<7z5JrYEVZt!WFyFA$u-;))!#0MU35VgT@Z|8Ca1=f% zd}{dg@R{NB!02&QYmR8BtxLa-#C03ZnW)O^@0Vbvo*Gv@E)FbanLT=ta>hqE|<+i{23ZQ}n** zgVDc5ACCSr`mgBY(f>qWjlL0mJNj<){TRy_XMK!Ej4Z}0#y2J)COAeNlN8e>CNHKi zrh80zOl3@UOplm>F{5MV#LSENK4x)DOU$yEl`(5#*2ip$*&6eE%#oO*F~?(0#hi_~ z5OXQ!O3d|`n=!ZBQSH3jrM3I2-8b#Fv^(4GMXV$?JT^WyF}7oD=h!Z>-C~Pl%VIUL zRk1a(J!16@v4diV#*T>9$BvF&6uTyNW9*jLZLvFIcgOCFJs5j3_Dt;M*sHM*VxPvo zhAW_b#4@7siX?9pe@8%6Ol6|M;NzkocJRj`7LyWAyP;;upp*jb9hPJ$_gG-uMIY zzs4U*U=lJtVe3``i3Fg#&Y0!kR0Fg{^Y z!lHzxge3{f5>_UxNm!q-DPe2Ej|tlob|oB2xSa5+y|}%yeSG`e_NDFnwV%*_Ui&re z@3nuLXpv}_=$@$eOAJelOpHm4OKhK*nwXK;B{3&4Kd~^eE^%VwlEgEK7Za}~-blQk z_$={d;+w?xNg&B4$uY?}$t}qMyA$Qm?1pO1+bMFIE3A^-1dU)K{r*)2!1(Y4&N3Y0hbGX`X4aG_N#eT0&Y@+K9BB zX*bi^bno=U^vd+U>0J7l^smz=rcX(qmOe9mR{D48^V8R*Z%E&q{zLjt={wSQr|(NY znEqS(@97uQA7+plP8k6iF&SMmv>EjoBQnNkOv;#=F+IbOF*~CvV~IXvdB&=YwHX^S zHf5a3c$&#(x@IP2cFF9PS)5sxsmZL$?44Pc**~)}b8zO+%;}j+GIwU4&3uwYWC^n9 zEMb;amTi`QR(MuUR(@8uuEAZSyH!r?XW1~@Hd~zSnC+bHmhG7>&koL3XGdg5 zXSd6a&rZzlkgd%go;@pDzae{f_WA5v*^jfIWxvdRll?vicV_Oa z-0yP#$UT*NDfde5_1s&zcXI#BeU(S%S>##giSq38-10o~RC&pH8F{t(yg_+G^G4+9 z^CslY%A1onFYo)jRe5XkHso#2JCb)g@1J~&eB1ok{M3AHK9@f=|J(dI`SbF>&tH_*U_q;(NspO6*JAOFT+sC4MDQCGAS$OA<>u zlyoZ5meiFrlng2vS~98xm5eR`B?Pa-!U!+`l}gymNU?d5`jj@?qs8%k^COnDR;GQ_H898_H*uuPWbJezg2-`GxXJ z<(JEEl;19YRQ|I3T?Jhstgx!Et#GaguZXH>SCLUsQPHnrO2s!7(<^3H%&J&ev9;po zienY$D;`(8(Aa9^npjPOCP~vtlcGu2WNAie#%sRO%+P$RnWLGjS*BU3S)*C6*`Yb4 z*BsXTsX3;(q`9KGuDPYTt9hw;qj_IRR0=DlmEM)Hm7OZPR_0a~R4%JrU%9<WPQ!}GxPtB2cy>MC?vU5&1{u1?oa b*Pz4yE?76wSo0zvG<^!&nLf?$x`qD>!VOx`